当前位置: 首页 > 工具软件 > pyrasite-gui > 使用案例 >

Python GUI开发包——wxpython

缪志新
2023-12-01

Python GUI开发包——wxpython

使用 Python 进行界面开发相比于传统的 C++ (Visual Studio)和 java (Android Studio)都更加的麻烦。主要的原因是 Python 并没有提供方便的可拖拽的界面设计工具。当然这也可能是我认识上的不足,如果有好的基于 Python 的界面开发工具恳请留言推荐给我。

个人在网上查找,发现很多人基于 wxpython 包进行开发。因此后文也将主要基于 wxpython 进行介绍。

直接使用以下代码就能生成一个最简单的界面,其中所有内容都是空白的。

import wx

if __name__ == '__main__':
    app = wx.App()
    frame = wx.Frame(parent=None, id=-1)
    frame.Show()
    app.MainLoop()

此时如果需要生成自己的窗口类——Frame,可以自己编写相应的类然后替换以上程序即可。

frame = MyFrame(parent=None, id=-1)

在自己编写的类中可以对窗口的各个属性进行配置

首先,为 Frame 类的初始化是在内置的 __init__ 函数中完成的。同时为保证窗口的正确初始化,通常在 __init__ 函数的第一行调用 wx.Frame.__init__ 函数

def __init__(self, parent, id):
    wx.Frame.__init__(self, parent, id)

当然也可以在上述函数中通过属性指定窗口的效果。其中有几个通用的属性,这里介绍一下。

parent:当前控件的父控件, 子控件的很多属性依赖父控件,比如可见性
id:控件的唯一标识符,如果为-1,程序将随机分配
title:标题
pos:控件左上方的位置
size:控件的大小
style:指定控件的一些属性,比如Frame默认设置为 wx.DEFAULT_FRAME_STYLE:
    wx.MINIMIZE_BOX | wx.MAXIMIZE_BOX | wx.RESIZE_BORDER | wx.SYSTEM_MENU | wx.CAPTION | wx.CLOSE_BOX | wx.CLIP_CHILDREN
    其中分别对应了 最小化按钮 | 最大化按钮 | 可改变窗口大小 | 系统菜单栏 | 标题栏 | 关闭按钮
name:窗口名称

可以通过以下程序进行菜单栏的设计

#coding:utf-8

import wx

class Mywin(wx.Frame):
    def __init__(self, parent, title):
        super(Mywin, self).__init__(parent, title = title)
        self.InitUI()

    def InitUI(self):
        #创建一个菜单栏
        menuBar = wx.MenuBar()

        #创建一个菜单 1
        fileMenu = wx.Menu()

        #创建一个菜单项 1-1
        newItem = wx.MenuItem(fileMenu, id = wx.ID_NEW, text = 'New', kind = wx.ITEM_NORMAL)
        fileMenu.Append(newItem)

        #添加一行线
        fileMenu.AppendSeparator()

        #创建一个子菜单 1-2
        editMenu = wx.Menu()

        #创建三个子菜单的菜单项目 1-2-1 and 1-2-2 and 1-2-3
        cutItem = wx.MenuItem(editMenu, id = 122, text = "Cut", kind = wx.ITEM_NORMAL)
        copyItem = wx.MenuItem(editMenu, id = 121, text = "Copy", kind = wx.ITEM_NORMAL)
        pasteItem = wx.MenuItem(editMenu, id = 123, text = "Paste", kind = wx.ITEM_NORMAL)
        editMenu.Append(copyItem)
        editMenu.Append(cutItem)
        editMenu.Append(pasteItem)

        #把子菜单 1-2 添加到菜单 1 中
        fileMenu.Append(wx.ID_ANY, "Edit", editMenu)

        # 添加一行线
        fileMenu.AppendSeparator()

        #添加两个单选框 1-3 and 1-4
        radio1 = wx.MenuItem(fileMenu, id = 13, text = "Radio_One", kind = wx.ITEM_RADIO)
        radio2 = wx.MenuItem(fileMenu, id = 14, text = "Radio_Two", kind = wx.ITEM_RADIO)
        fileMenu.Append(radio1)
        fileMenu.Append(radio2)
        #PS.单选框 只在自己区域之间(两行线之间) 相互作用

        # 添加一行线
        fileMenu.AppendSeparator()

        #添加一个 可选中 的菜单项 1-5
        fileMenu.AppendCheckItem(id = 15, item = "Check")

        #添加一个 菜单项 1-6 并注册快捷键
        quit = wx.MenuItem(fileMenu, id = wx.ID_EXIT, text = "Quit\tCtrl+Q", kind = wx.ITEM_NORMAL)
        fileMenu.Append(quit)

        #将 fileMenu 菜单添加到菜单栏中
        menuBar.Append(fileMenu, title = 'File')

        #设置窗口框架的菜单栏为 menuBar
        self.SetMenuBar(menuBar)

        #绑定事件处理
        self.Bind(wx.EVT_MENU, self.menuHandler)

        self.CreateStatusBar()
        self.SetStatusText(u"菜单栏范例!")

        #让其在屏幕中间打开调整大小展示
        self.SetSize((300,400))
        self.Centre()
        self.Show()

    def menuHandler(self, event):
        id = event.GetId()
        if id == wx.ID_NEW:
            print("NEW")
        if id == wx.ID_EXIT:
            exit(0)


if __name__ == "__main__":
    ex = wx.App()
    Mywin(None, 'Menu - Test')
    #Mywin(None, 'Menu - Test')      #可以同时打开两个窗口  果然体现面向对象的程序开发思想
    ex.MainLoop()

其中还涉及了状态栏的设计,消息事件的绑定,以及通过控件 id 对不同控件加以区分从而实现对应功能。

而对应窗口内的设计,一般基于 wx.panel 实现。一般 Panel 的父控件是 Frame,而其他如按钮,文本控件一般父控件均为 Panel。


import wx
import os


class Frame1(wx.Frame):
    def __init__(self, parent):
        wx.Frame.__init__(self, parent=parent, title='注释股票代码', size=(380, 250))
        self.panel = wx.Panel(self)
        self.text1 = wx.TextCtrl(self.panel, pos=(30, 20), size=(200, 25))
        self.btnO = wx.Button(self.panel, label="选择文件路径", pos=(255, 20), size=(90, 25))
        # 一组单选
        lblList = ['注释单一文件', '注释文件夹下所有文件']
        self.rbox = wx.RadioBox(self.panel, label='运行方式', pos=(30, 60), choices=lblList,
                                majorDimension=1, style=wx.RA_SPECIFY_ROWS)
        self.btnS = wx.Button(self.panel, label="运行", pos=(130, 150), size=(70, 25))

        # 绑定事件
        self.btnS.Bind(wx.EVT_BUTTON, self.OnClick)
        self.btnO.Bind(wx.EVT_BUTTON, self.OnOpenFile)

        # 设置窗口标题的图标
        self.icon = wx.Icon('123.ico', wx.BITMAP_TYPE_ICO)
        self.SetIcon(self.icon)

    def OnOpenFile(self, event):
        # 根据单选的索引执行
        if self.rbox.GetSelection() == 1:
            # 选择文件夹对话框
            self.dlgd = wx.DirDialog(self, u"选择文件夹", style=wx.DD_DEFAULT_STYLE)
            # 如果确定了选择的文件夹,将文件夹路径写到text1控件
            if self.dlgd.ShowModal() == wx.ID_OK:
                self.text1.AppendText(self.dlgd.GetPath())
        else:
            # 选择文件对话框,设置选择的文件必须为txt格式
            self.dlg = wx.FileDialog(self, message=u"选择文件", style=wx.FD_OPEN | wx.FD_CHANGE_DIR,
                                     wildcard="Text Files (*.txt)|*.txt")
            # 如果确定了选择的文件,将文件路径写到text1控件
            if self.dlg.ShowModal() == wx.ID_OK:
                self.text1.AppendText(self.dlg.GetPath())

    def OnClick(self, event):
        # 判断text1的文本框内容是否为空
        if self.text1.GetValue() == "":
            wx.MessageBox("请先设定需要注释的文件或文件夹路径", "提示消息", wx.OK | wx.YES_DEFAULT)
            return
        if self.rbox.GetSelection() == 0:
            # 设置选择的文件必须为txt格式
            self.dlgx = wx.FileDialog(self, message=u"另存为", style=wx.FD_OPEN | wx.FD_CHANGE_DIR,
                                      wildcard="Text Files (*.txt)|*.txt")
            if self.dlgx.ShowModal() == wx.ID_OK:
                self.RunSingalFile()
        else:
            self.dlgdx = wx.DirDialog(self, u"另存为", style=wx.DD_DEFAULT_STYLE)
            if self.dlgdx.ShowModal() == wx.ID_OK:
                self.RunOneFolder()

    def RunSingalFile(self):
        self.msg1 = wx.MessageDialog(parent=None, message="运行成功!(是否打开输出文件)", caption="提示消息",
                                     style=wx.YES_NO | wx.ICON_INFORMATION)
        # 如果选择“是”,startfile用来打开一个文件或者文件夹(像日常双击一样的效果)
        if self.msg1.ShowModal() == wx.ID_YES:
            os.startfile(self.dlgx.GetPath())

    def RunOneFolder(self):
        self.msg2 = wx.MessageDialog(parent=None, message="运行成功!(是否打开输出文件夹)", caption="提示消息",
                                     style=wx.YES_NO | wx.ICON_INFORMATION)
        if self.msg2.ShowModal() == wx.ID_YES:
            os.startfile(self.dlgdx.GetPath())


if __name__ == '__main__':
    app = wx.App()
    frame = Frame1(None)
    frame.Show()
    app.MainLoop()

最后给出一些比较好的参考资料

最后针对遇到的两个小问题给出解决方案:

1、需要根据用户操作切换 Frame 中显示的 Panel

其中通过 BoxSizer 进行 Panel 的管理,并通过  changePanel 函数实现原有 Panel 的销毁和新 Panel 的显示。


import wx

class MyPanel(wx.Panel):
    def __init__(self, parent):
        wx.Panel.__init__(self, parent=parent)
        wx.StaticText(self, label='MyPanel')

class YourPanel(wx.Panel):
    def __init__(self, parent):
        wx.Panel.__init__(self, parent=parent)
        wx.StaticText(self, label='YourPanel')

class MyFrame(wx.Frame):
    def __init__(self, parent):
        wx.Frame.__init__(self, parent=parent)
        self.panel = wx.Panel(self)

        self.sizer = wx.BoxSizer(wx.VERTICAL)
        self.sizer.Add(self.panel, 1, wx.EXPAND)
        self.SetSizer(self.sizer)

        menu = wx.Menu()
        myItem = menu.Append(-1, u"&MyPanel")
        yourItem = menu.Append(-1, u"&YourPanel")
        self.Bind(wx.EVT_MENU, self.myFunc, myItem)
        self.Bind(wx.EVT_MENU, self.yourFunc, yourItem)

        menuBar = wx.MenuBar()
        menuBar.Append(menu, u"&ChangePanel")
        self.SetMenuBar(menuBar)

        self.Center()

    def myFunc(self, panel):
        panel = MyPanel(self)
        self.changePanel(panel)

    def yourFunc(self, panel):
        panel = YourPanel(self)
        self.changePanel(panel)

    def changePanel(self, panel):
        self.panel.Hide()
        self.panel.Destroy()
        self.panel = panel

        self.sizer.Clear()
        self.sizer.Add(self.panel, 1, wx.EXPAND)
        self.SetSizer(self.sizer)

        self.Layout()


if __name__ == '__main__':
    app = wx.App()
    frame = MyFrame(None)
    frame.Show()
    app.MainLoop()

2、TextCtrl 绑定失去焦点事件

直接绑定 wx.EVT_KILL_FOCUS 事件时出现无法正常再次选中的问题,可以通过使用 控件.Bind 的绑定方式以及在对应响应函数中添加 event.Skip() 来解决。


import wx

class MyFrame(wx.Frame):
    def __init__(self, parent):
        wx.Frame.__init__(self, parent=parent)
        panel = wx.Panel(self)

        textCtrl = wx.TextCtrl(panel)
        textCtrl.Bind(wx.EVT_KILL_FOCUS, self.func)

        button = wx.Button(panel, label=u'确定', pos=(200, 150))

        self.count = 0
        self.Center()

    def func(self, event):
        self.count += 1
        print('Lost Focus: ' + str(self.count))
        event.Skip()


if __name__ == '__main__':
    app = wx.App()
    frame = MyFrame(None)
    frame.Show()
    app.MainLoop()

具体两种消息绑定方式的差别可以参考:self.Bind vs. self.button.Bind

 类似资料: