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()
最后给出一些比较好的参考资料
- 控件属性详叙:https://blog.csdn.net/shaxiaozilove/article/details/51638054
- wxpython 官方文档:https://docs.wxpython.org/
最后针对遇到的两个小问题给出解决方案:
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