wxpython图形用户界面编程
一、wxpython的基础
1.1 wxpython的基础
作为图形用户界面开发工具包 wxPython,主要提供了如下 GUI 内容:
- 窗口。
- 控件。
- 事件处理。
- 布局管理。
1.2 wxpython的类层次机构
1.3 wxpython的安装
- Windows 和 macOS 平台安装:
pip install -U wxPython
其中 install 是按照软件包,-U 是将指定软件包升级到最新版本。
- Linux 平台下使用 pip 安装有点麻烦,例如在 Ubuntu 16.04 安装,打开终端输入
如下指令:
pip install -U \-f https://extras.wxpython.org/wxPython4/extras/linux/gtk3/ubuntu-16.04 \wxPython
- 下载 wxPython 帮助文档和案例。
https://extras.wxpython.org/wxPython4/extras
1.4 第一个wxPython程序
import wx
# 创建应用程序对象
app = wx.App()
# 创建窗口对象
### size 是窗口的长和宽
### pos 使主窗口再电脑桌面显示的位置
### self.Center() 使窗口在电脑桌面显示居中位置,替代pos
frm = wx.Frame(None, title="第一个GUI程序!", size=(400, 300), pos=(100, 100))
frm.Show() # 显示窗口
app.MainLoop() # 进入主事件循环
1.5 窗口类MyFrame
# 自定义窗口类MyFrame
class MyFrame(wx.Frame): def __init__(self): super().__init__(parent=None, title="第一个GUI程序!", size=(400, 300), pos=(100, 100)) # TODO class App(wx.App): def OnInit(self): # 创建窗口对象 frame = MyFrame() frame.Show() return True def OnExit(self): print('应用程序退出') return 0 if __name__ == '__main__': app = App() app.MainLoop() # 进入主事件循环
1.6 使用面板(panel )
class MyFrame(wx.Frame):def __init__(self):super().__init__(parent=None, title="第一个GUI程序!", size=(600, 400), pos=(600, 200))# 使用面板,面板指向父类MyFrame,也就是selfpanel = wx.Panel(parent=self)# 在面板上设置文本内容statictext = wx.StaticText(parent=panel, label='Hello World!', pos=(10, 10))class App(wx.App):def OnInit(self):# 创建窗口对象frame = MyFrame()frame.Show()return Truedef OnExit(self):print('应用程序退出')return 0if __name__ == '__main__':app = App()app.MainLoop() # 进入主事件循环
Frame的内容面板图解结构,如下图
1.7 wxpython界面构建层次机构
注意:并不是指wxpython功能模块的类相关的继承层次机构
二、事件处理
在事件处理的过程中涉及4个要素:
- 事件。它是用户对界面的操作,在wxPython中事件被封装成为事件类wx.Event及其子类,例如按钮事件类是wx.CommandEvent,鼠标事件类是wx.Move Event。
- 事件类型。事件类型给出了事件的更多信息,它是一个整数。例如标事件wx.Move Event还可以有鼠标的右键按下(WX.EVT LEFT DOWN)和释放(wx.EVT左上)等。
- 事件源。它是事件发生的场所,就是各个控件,例如按钮事件的事件源是按钮。
- 事件处理者。 它是在wx.EvtHandler子类(事件处理类)中定义的一个方法。
绑定是通过事件处理类的 Bind()方法实现,Bind()方法语法如下:
Bind(self, event, handler,source=None, id=wx.ID_ANY, id2=Wx.ID_ANY)
- 其中参数event是事件类型,注意不是事件;
- handler是事件处理者,它对应到事件处理类中特定方法;
- source 是事件源;
- id是事件源的标识,可以省略source参数通过id绑定事件源:
- id2设置要绑定事件源id范围,当有多个事件源帮定到同一个事件处理者时可以使用此参数。
2.1 一对一事件处理
class MyFrame(wx.Frame):def __init__(self):super().__init__(parent=None, title="一对一事件处理", size=(600, 400))self.Center() # 使主窗口在电脑桌面显示居中位置, pos可以不使用# 使用面板,面板指向父类MyFrame,也就是selfpanel = wx.Panel(parent=self)# 在面板上设置文本内容self.statictext = wx.StaticText(parent=panel, pos=(10, 10))button = wx.Button(parent=panel, label='OK', pos=(100, 80))# event是事件类型 handler是事件处理者,具体的自定义的执行方法 source是事件源,指定事件源的变量名字,也可以用id去指定事件源范围self.Bind(event=wx.EVT_BUTTON, handler=self.on_click, source=button)def on_click(self, event):print(event)self.statictext.SetLabelText('Hello Word')class App(wx.App):def OnInit(self):# 创建窗口对象frame = MyFrame()frame.Show()return Truedef OnExit(self):print('应用程序退出')return 0if __name__ == '__main__':app = App()app.MainLoop() # 进入主事件循环
代码执行效果图
2.2 一对多事件处理
多个事件源调用同一个方法进行处理
class MyFrame(wx.Frame):def __init__(self):super().__init__(parent=None, title="一对一事件处理", size=(600, 400))self.Center() # 使主窗口在电脑桌面显示居中位置, pos可以不使用# 使用面板,面板指向父类MyFrame,也就是selfpanel = wx.Panel(parent=self)# 在面板上设置文本内容self.statictext = wx.StaticText(parent=panel, pos=(10, 10))# 定义两个事件源,分别是button10, button11button10 = wx.Button(parent=panel, id=10, label='button10', pos=(100, 80))button11 = wx.Button(parent=panel, id=11, label='button11', pos=(140, 110))# Bind是绑定不同类型的事件源去执行方法,这里的事件类型是button按钮# event是事件类型 handler是事件处理者,具体的执行方法 # id是事件源起始id, id2是事件源的结束id,这是id范围是10到11,也就是说10到11的id事件源都会执行self.on_click方法self.Bind(event=wx.EVT_BUTTON, handler=self.on_click, id=10, id2=11)def on_click(self, event):# 获取事件源的idsource_id = event.GetId()# 根据事件源id执行不同的结果if source_id == 10:self.statictext.SetLabelText('button10')else:self.statictext.SetLabelText('button11')class App(wx.App):def OnInit(self):# 创建窗口对象frame = MyFrame()frame.Show()return Truedef OnExit(self):print('应用程序退出')return 0if __name__ == '__main__':app = App()app
三、布局管理
使用绝对布局会有如下问题:
- 子窗口(或控件)位置和大小不会随着父窗口的变化而变化。
- 在不同平台上显示效果可能差别很大。
- 在不同分辨率下显示效果可能差别很大。
- 字体的变化也会对显示效果有影响。
- 动态添加或删除子窗口(或控件)界面布局需要重新设计。
wxPython 提供了8个布局管理器类,如图19-10所示,包括:
wx.Sizer、wx.BoxSizer、wx.StaticBoxSizer , wx.WrapSizer, wx.StdDialogButtonSizer, wx.GridSizer 、 wx.FlexGridSizer和 wx.GridBagSizer .
3.1 Box 布局器
创建 wx.BoxSizer 对象时可以指定布局方向:
hbox = wx.BoxSizer(wx.HORIZONTAL) # 设置为水平方向布局
hbox = wx.BoxSizer() # 也是设置为水平方向布局,wx.HORIZONTAL是默认值可以省略
vhbox = wx.BoxSizer(wx.VERTICAL) # 设置为垂直方向布局
当需要添加子窗口(或控件)到父窗口时,需要调用 wx.BoxSizer 对象 Add()方法,
Add()方法是从父类 wx.Sizer 继承而来的,Add()方法语法说明如下:
Add(window, proportion=0, flag=0, border=0, userData=None) # 添加到父窗口
Add(sizer, proportion=0, flag=0, border=0, userData=None) # 添加到另外一个Sizer中,用于嵌套
Add(width, height, proportion=0, flag=0, border=0, userData=None) # 添加一个空白空间
注意: proportion=0 表示该组件不会随着窗口大小改变而改变大小
注意: proportion=1 表示该组件会随着窗口大小改变而改变大小
注意: 如果有多个组件都设置了proportion>0,则按比例分配空间
class MyFrame(wx.Frame):def __init__(self):super().__init__(parent=None, title="Box布局", size=(600, 400))self.Center() # 使主窗口在电脑桌面显示居中位置, pos可以不使用# 使用面板,面板指向父类MyFrame,也就是selfpanel = wx.Panel(parent=self)# 创建垂直方向box布局管理wx.VERTICALvbox = wx.BoxSizer(wx.VERTICAL)# 设置静态文本模板,放在panel面板中self.statictext = wx.StaticText(parent=panel, label='Button1单机')# #添加静态文本到vbox布局管理器中,指定文本框在整个面板中所占权重为2,标志为固定大小填充,顶部有边框,水平居中,边框宽度为10vbox.Add(self.statictext, proportion=2, flag=wx.FIXED_MINSIZE | wx.TOP | wx.CENTER, border=10)button10 = wx.Button(parent=panel, id=10, label='button10')button11 = wx.Button(parent=panel, id=11, label='button11')self.Bind(event=wx.EVT_BUTTON, handler=self.on_click, id=10, id2=20)# 创建水平方向box布局管理器hbox = wx.BoxSizer(wx.HORIZONTAL)hbox.Add(button10, proportion=0, flag=wx.EXPAND | wx.BOTTOM | wx.RIGHT, border=70)hbox.Add(button11, proportion=0, flag=wx.EXPAND | wx.BOTTOM, border=70)# 将水平box添加到垂直boxvbox.Add(hbox, proportion=1, flag=wx.CENTER)# 设置面板的布局管理器vboxpanel.SetSizer(vbox)'''将两个button放到一个水平方向布局管理器,然后将水平方向布局管理器放到垂直方向布局管理器中 添加控件到其父容器是通过parent属性,这里button和statictext的父容器都是面板,与布局管理器的添加是没有关系的布局管理器添加是通过Add()方法添加,这个添加只是说将某个控件纳入布局管理器管理不是添加到容器中,注意布局管理器不是一个容器'''def on_click(self, event):# 获取事件源的idsource_id = event.GetId()# 根据事件源id执行不同的结果if source_id == 10:print('button10')else:print('button11')class App(wx.App):def OnInit(self):# 创建窗口对象frame = MyFrame()frame.Show()return Truedef OnExit(self):print('应用程序退出')return 0if __name__ == '__main__':app = App()app.MainLoop() # 进入主事件循环
运行效果:
3.2 StaticBox布局
wx.StaticBoxSizer构造方法如下:
-
1.wx.StaticBoxSizer(box,orient=HORIZONTAL)
box参数:wx.StaticBox(静态框)对象
orient参数:布局方向 -
2.wx.StaticBox(orient,parent,label=“”)
orient参数:布局方向
parent参数:设置所在的父窗口
label参数:设置边框的静态文本
class MyFrame(wx.Frame):def __init__(self):super().__init__(parent=None, title="StaticBox布局", size=(300, 200))self.Center() # 使主窗口在电脑桌面显示居中位置, pos可以不使用# 使用面板,面板指向父类MyFrame,也就是selfpanel = wx.Panel(parent=self)# 创建垂直方向box布局管理wx.VERTICALvbox = wx.BoxSizer(wx.VERTICAL)# 设置静态文本模板,放在panel面板中self.statictext = wx.StaticText(parent=panel, label='Button1单机')# 添加静态文本到vbox布局管理器中vbox.Add(self.statictext, proportion=2, flag=wx.FIXED_MINSIZE | wx.TOP | wx.CENTER, border=50)button10 = wx.Button(parent=panel, id=10, label='button10')button11 = wx.Button(parent=panel, id=11, label='button11')self.Bind(event=wx.EVT_BUTTON, handler=self.on_click, id=10, id2=20)# 创建静态框sb = wx.StaticBox(parent=panel, label='静态按钮框')# 创建水平方向box布局管理器,并加入sb静态框hsbox = wx.StaticBoxSizer(sb, wx.HORIZONTAL)hsbox.Add(button10, proportion=0, flag=wx.EXPAND | wx.BOTTOM, border=50)hsbox.Add(button11, proportion=0, flag=wx.EXPAND | wx.BOTTOM, border=50)# 将水平box添加到垂直boxvbox.Add(hsbox, proportion=1, flag=wx.CENTER)# 设置面板的布局管理器vboxpanel.SetSizer(vbox)def on_click(self, event):# 获取事件源的idsource_id = event.GetId()# 根据事件源id执行不同的结果if source_id == 10:print('button10')else:print('button11')class App(wx.App):def OnInit(self):# 创建窗口对象frame = MyFrame()frame.Show()return Truedef OnExit(self):print('应用程序退出')return 0if __name__ == '__main__':app = App()app.MainLoop() # 进入主事件循环
运行效果:
3.3 Grid 布局
Grid布局类wx.GridSizer,Grid布局以网格形式对子窗口(或控件)进行摆放,容器被分成大小相等的矩形,一个矩形中放置一个子窗口(或控件)。
wx.GridSizer的构造方法如下:
(1)wx.GridSizer(rows,cols,vgap,hgap)
创建指定行数和列数的wx.GridSizer对象,并指定水平和垂直间隙,参数hgap为水平间隙,参数vgap为垂直间隙,整数类型。添加的子窗口(或控件)个数超过rows*cols之积,则引发异常。
(2)wx.GridSizer(rows,cols,gap)
同wx.GridSizer(rows,cols,vgap,hgap),gap参数指定垂直间隙和水平间隙,gap参数是wx.Size类型
例如wx.Size(2,3)是设置水平间隙为2像素,垂直间隙为3像素
(3)wx.GridSizer(cols,vgap,hgap)
创建指定列数的wx.GridSizer对象,并指定水平和垂直间隙。由于没有限定行数,所以添加的子窗口(或控件)个数没有限制
(4)wx.GridSizer(cols,gap=wx.Size(0,0))
同wx.GridSizer(cols,vgap,hgap),gap参数是垂直间隙和水平间隙是wx.Size类型
class MyFrame(wx.Frame):def __init__(self):super().__init__(parent=None, title="Grid布局器", size=(400, 300))# 使主窗口在电脑桌面显示居中位置, pos可以不使用self.Center() # 创建一个面板,名字叫panel,面板指向父类MyFrame,也就是selfpanel = wx.Panel(parent=self)# 创建9个按钮,3*3的布局button1 = wx.Button(parent=panel, id=1, label='1')button2 = wx.Button(parent=panel, id=2, label='2')button3 = wx.Button(parent=panel, id=3, label='3')button4 = wx.Button(parent=panel, id=4, label='4')button5 = wx.Button(parent=panel, id=5, label='5')button6 = wx.Button(parent=panel, id=6, label='6')button7 = wx.Button(parent=panel, id=7, label='7')button8 = wx.Button(parent=panel, id=8, label='8')button9 = wx.Button(parent=panel, id=9, label='9')# 将类型为按钮,上述9个按钮绑定到事件处理方法(handler)self.Bind(event=wx.EVT_BUTTON, handler=self.on_click, id=1, id2=10)#创建Grid布局管理器gridsizer = wx.GridSizer(cols=3, rows=3, gap=wx.Size(5, 10))# 将按钮添加进grid布局管理器中,最多添加9个控件,只能少不能多gridsizer.AddMany([(button1, 0, wx.EXPAND),(button2, 0, wx.EXPAND),(button3, 0, wx.EXPAND),(button4, 0, wx.EXPAND),(button5, 0, wx.EXPAND),(button6, 0, wx.EXPAND),(button7, 0, wx.EXPAND),(button8, 0, wx.EXPAND),(button9, 0, wx.EXPAND),])# 将Grid布局管理器添加到当前panel面板中panel.SetSizer(gridsizer)def on_click(self, event):# 获取事件源的idsource_id = event.GetId()# 根据事件源id执行不同的结果if source_id == 10:print('button10')else:print('button11')class App(wx.App):def OnInit(self):# 创建窗口对象frame = MyFrame()frame.Show()return Truedef OnExit(self):print('应用程序退出')return 0if __name__ == '__main__':app = App()app.MainLoop() # 进入主事件循环
运行效果:
3.4 FlexGrid布局
Grid布局时网格大小是固定的,如果想网格大小不同的界面可以使用FlexGrid布局。
FlexGrid是更加灵活的Grid布局。FlexGrid布局类是wx.FlexGridSizer,它的父类是wx.GridSizer。
wx.FlexGridSizer有两个特殊的方法:
(1)AddGrowableRow(idx,proportion=0)
指定行是可以扩展的,参数idx是行索引,从零开始;参数proportion是设置该行所占空间比例
(2)AddGrowableCo(idx,proportion=0)
指定列是可以扩展的,参数idx是列索引,从零开始;参数proportion是设置该列所占空间比例
class MyFrame(wx.Frame):def __init__(self):super().__init__(parent=None, title="FlexGrid布局", size=(400, 300))self.Center() # 使主窗口在电脑桌面显示居中位置, pos可以不使用# 使用面板,面板指向父类MyFrame,也就是selfpanel = wx.Panel(parent=self)# 创建FlexGrid布局管理器,3行2列,之间的空隙是10像素fgs = wx.FlexGridSizer(3, 2, 10, 10)# 静态文本(wx.StaticText)title = wx.StaticText(panel, label='标题:')author = wx.StaticText(panel, label='作者:')review = wx.StaticText(panel, label='内容:')# 文本框控件(wx.TextCtrl)tcl = wx.TextCtrl(panel)tc2 = wx.TextCtrl(panel)tc3 = wx.TextCtrl(panel, style=wx.TE_MULTILINE)fgs.AddMany([title, (tcl, 1, wx.EXPAND),author, (tc2, 1, wx.EXPAND),review, (tc3, 1, wx.EXPAND)])# 指定行,(行索引,行所占空间比例)fgs.AddGrowableRow(0, 1) # 高度占1/5fgs.AddGrowableRow(1, 1) # 占1/5fgs.AddGrowableRow(2, 3) # 占3/5# 指定列,(列索引,列所占空间比例)fgs.AddGrowableCol(0, 1)fgs.AddGrowableCol(1, 2)# 因FlexGrid管理器无法设置border边框,所以可以通过把FlexGrid管理器加入到Box管理器里面来hbox = wx.BoxSizer(wx.HORIZONTAL)hbox.Add(fgs, proportion=1, flag=wx.ALL | wx.EXPAND, border=15)# 将Box布局管理器添加进面板panel.SetSizer(hbox)class App(wx.App):def OnInit(self):# 创建窗口对象frame = MyFrame()frame.Show()return Truedef OnExit(self):print('应用程序退出')return 0if __name__ == '__main__':app = App()app.MainLoop() # 进入主事件循环
执行效果:
四、wxPython 控件
4.1 静态文本和按钮
wxPython 中静态文本类是 wx.StaticText,可以显示文本。wxPython 中的按钮主要有三个:wx.Button、wx.BitmapButton 和 wx.ToggleButton
- wx.Button 是普通按钮
- wx.BitmapButton 是带有图标按钮
- wx.ToggleButton 能进行两种状态切换的按钮。
class MyFrame(wx.Frame):def __init__(self):super().__init__(parent=None, title="静态文本和按钮", size=(400, 300))self.Center() # 使主窗口在电脑桌面显示居中位置, pos可以不使用# 使用面板,面板指向父类MyFrame,也就是selfpanel = wx.Panel(parent=self)# 创建垂直方向布局boxvbox = wx.BoxSizer(wx.VERTICAL)# 创建一个静态文本,加入到panel面板,在面板中水平居中对齐self.statictext = wx.StaticText(parent=panel, label="StaticText", style=wx.ALIGN_CENTER_HORIZONTAL)# 创建一个普通的button,并绑定相应的方法(on_click)button1 = wx.Button(parent=panel, id=1, label="Button")self.Bind(event=wx.EVT_BUTTON, handler=self.on_click, source=button1)# 创建一个ToggleButton,可以进行两种状态切换,并绑定相应的方法(on_click)button2 = wx.ToggleButton(parent=panel, id=2, label="ToggleButton")self.Bind(event=wx.EVT_BUTTON, handler=self.on_click, source=button2)# 创建一个BitmapButton,带有图标按钮,并绑定相应的方法(on_click)bmp = wx.Bitmap('icon/1.png', wx.BITMAP_TYPE_PNG)button3 = wx.BitmapButton(parent=panel, id=3, bitmap=bmp)self.Bind(event=wx.EVT_BUTTON, handler=self.on_click, source=button3)# 添加到控件布局管理器中vbox.Add(50, 50, proportion=1, flag=wx.CENTER | wx.FIXED_MINSIZE) # 面板添加空白行并居中vbox.Add(self.statictext, proportion=1, flag=wx.CENTER | wx.EXPAND)vbox.Add(button1, proportion=1, flag=wx.CENTER | wx.EXPAND)vbox.Add(button2, proportion=1, flag=wx.CENTER | wx.EXPAND)vbox.Add(button3, proportion=1, flag=wx.CENTER | wx.EXPAND)panel.SetSizer(vbox)def on_click(self, event):self.statictext.SetLabelText("哈哈哈哈哈")
class App(wx.App):def OnInit(self):# 创建窗口对象frame = MyFrame()frame.Show()return Truedef OnExit(self):print('应用程序退出')return 0if __name__ == '__main__':app = App()app.MainLoop() # 进入主事件循环
运行效果:
4.2 文本输入控件
文本输入控件类是 wx.TextCtrl,默认情况下只能文本输入控件中只能输入单行数据,如果想输入多行可以设置 style=wx.TE_MULTILINE。如果想把文本输入控件作为密码框使用,可以设置 style=wx.TE_PASSWORD。
class MyFrame(wx.Frame):def __init__(self):super().__init__(parent=None, title="文本输入框", size=(300, 200))self.Center()panel = wx.Panel(parent=self)# 创建FlexGrid布局管理器fgs = wx.FlexGridSizer(3, 2, 10, 10)# 静态文本user = wx.StaticText(parent=panel, label='用户:')pwd = wx.StaticText(parent=panel, label='密码:')content = wx.StaticText(parent=panel, label='多行文本:')# 文本框控件,设置为类内部属性变量,可通过self调用self.tc1 = wx.TextCtrl(parent=panel) # 将tc1改为实例变量self.tc1.SetHint("请输入用户名") # 设置默认提示文字self.tc2 = wx.TextCtrl(parent=panel, style=wx.TE_PASSWORD) # 将tc2改为实例变量self.tc3 = wx.TextCtrl(parent=panel, style=wx.TE_MULTILINE) # 将tc3改为实例变量# 添加保存按钮,当用户点击保存按钮时,可以在on_save方法中获取到数据save_btn = wx.Button(parent=panel, label='保存')self.Bind(event=wx.EVT_BUTTON, handler=self.on_save, source=save_btn) # 绑定点击事件fgs.AddMany([user, (self.tc1, 1, wx.EXPAND),pwd, (self.tc2, 1, wx.EXPAND),content, (self.tc3, 1, wx.EXPAND)])# 指定行,(行索引,行所占空间比例)fgs.AddGrowableRow(0, 1) # 高度占1/5fgs.AddGrowableRow(1, 1) # 占1/5fgs.AddGrowableRow(2, 3) # 占3/5# 指定列,(列索引,列所占空间比例)fgs.AddGrowableCol(0, 1)fgs.AddGrowableCol(1, 2)# 创建了垂直布局(vbox)来容纳FlexGrid和保存按钮vbox = wx.BoxSizer(wx.VERTICAL)vbox.Add(fgs, proportion=1, flag=wx.ALL | wx.EXPAND, border=15)vbox.Add(save_btn, proportion=0, flag=wx.ALIGN_CENTER | wx.BOTTOM, border=15)panel.SetSizer(vbox)def on_save(self, event):# 获取三个文本框的内容,使用 GetValue() 获取各个文本框的内容user_text = self.tc1.GetValue()pwd_text = self.tc2.GetValue()content_text = self.tc3.GetValue()print('用户名:', user_text)print('密码:', pwd_text)print('内容:', content_text)class App(wx.App):def OnInit(self):# 创建窗口对象frame = MyFrame()frame.Show()return Truedef OnExit(self):print('应用程序退出')return 0if __name__ == '__main__':app = App()app.MainLoop() # 进入主事件循环
运行效果:
4.3 复选框和单选按钮
多选控件是复选框(wx.CheckBox)
单选控件是单选按钮(wx.RadioButton)
class MyFrame(wx.Frame):def __init__(self):super().__init__(parent=None, title="复选框和单选框", size=(400, 300))self.Center()panel = wx.Panel(parent=self)# 创建水平方向布局boxhbox1 = wx.BoxSizer(wx.HORIZONTAL)# 创建一个静态文本,加入到panel面板中statictext = wx.StaticText(parent=panel, label="选择你喜欢的编程语言:")# 创建3个CheckBox类型复选框cb1 = wx.CheckBox(parent=panel, id=1, label='Python')cb2 = wx.CheckBox(parent=panel, id=2, label='Java')# cb2复选框设置为默认值cb2.SetValue(True)cb3 = wx.CheckBox(parent=panel, id=3, label='C++')# 对事件进行绑定事件处理方法self.Bind(event=wx.EVT_CHECKBOX, handler=self.checkbox, id=1, id2=3)# 将静态文本和CheckBox事件加入到水平方向布局中hbox1.Add(statictext, proportion=1, flag=wx.LEFT | wx.RIGHT | wx.FIXED_MINSIZE, border=5)hbox1.Add(cb1, proportion=1, flag=wx.ALL | wx.FIXED_MINSIZE)hbox1.Add(cb2, proportion=1, flag=wx.ALL | wx.FIXED_MINSIZE)hbox1.Add(cb3, proportion=1, flag=wx.ALL | wx.FIXED_MINSIZE)hbox2 = wx.BoxSizer(wx.HORIZONTAL)statictext = wx.StaticText(parent=panel, label="选择性别: ")# 创建RadioButton事件,wx.RB_GROUP指的是一个组,同一个RadioButton组后续不需要再加wx.RB_GROUPradio1 = wx.RadioButton(parent=panel, id=4, label='男', style=wx.RB_GROUP)radio2 = wx.RadioButton(parent=panel, id=5, label='女')self.Bind(event=wx.EVT_RADIOBUTTON, handler=self.radiobutton, id=4, id2=5)hbox2.Add(statictext, proportion=1, flag=wx.LEFT | wx.RIGHT | wx.FIXED_MINSIZE, border=5)hbox2.Add(radio1, proportion=1, flag=wx.ALL | wx.FIXED_MINSIZE)hbox2.Add(radio2, proportion=1, flag=wx.ALL | wx.FIXED_MINSIZE)hbox3 = wx.BoxSizer(wx.HORIZONTAL)statictext = wx.StaticText(parent=panel, label="选择你喜欢吃的水果: ")radio3 = wx.RadioButton(parent=panel, id=6, label='橘子', style=wx.RB_GROUP)radio4 = wx.RadioButton(parent=panel, id=7, label='苹果')radio5 = wx.RadioButton(parent=panel, id=8, label='西瓜')self.Bind(event=wx.EVT_RADIOBUTTON, handler=self.radiobutton, id=6, id2=8)hbox3.Add(statictext, proportion=1, flag=wx.LEFT | wx.RIGHT | wx.FIXED_MINSIZE, border=5)hbox3.Add(radio3, proportion=1, flag=wx.ALL | wx.FIXED_MINSIZE)hbox3.Add(radio4, proportion=1, flag=wx.ALL | wx.FIXED_MINSIZE)hbox3.Add(radio5, proportion=1, flag=wx.ALL | wx.FIXED_MINSIZE)# 创建垂直布局管理器vbox = wx.BoxSizer(wx.VERTICAL)# 把3个水平的布局管理器加到垂直的布局管理器vbox.Add(hbox1, proportion=0, flag=wx.ALL, border=5)vbox.Add(hbox2, proportion=0, flag=wx.ALL, border=5)vbox.Add(hbox3, proportion=0, flag=wx.ALL, border=5)panel.SetSizer(vbox)def checkbox(self, event):cb = event.GetEventObject()print(cb.GetLabel())def radiobutton(self, event):rb = event.GetEventObject()print(rb.GetLabel())class App(wx.App):def OnInit(self):# 创建窗口对象frame = MyFrame()frame.Show()return Truedef OnExit(self):print('应用程序退出')return 0if __name__ == '__main__':app = App()app.MainLoop() # 进入主事件循环
运行效果:
4.4 下拉列表
wxPython 提供了两种下拉列表控件类:wx.ComboBox 和 wx.Choice,wx.ComboBox 默认它的文本框是可以修改的,wx.Choice 是只读不可以修改的,除此之外他们没有区别。
class MyFrame(wx.Frame):def __init__(self):super().__init__(parent=None, title="复选框和单选框", size=(400, 300))self.Center()panel = wx.Panel(parent=self)# 创建水平方向布局boxhbox1 = wx.BoxSizer(wx.HORIZONTAL)# 创建静态文本,放到panel面板中statictext = wx.StaticText(parent=panel, label="选择你喜欢的编程语言:")list1 = ["Python", "JavaScript", "C++", "JavaScript"]# 创建可修改下拉列表文本框ComboBoxch1 = wx.ComboBox(parent=panel, id=-1 , value='C', choices=list1, style=wx.CB_SORT)self.Bind(event=wx.EVT_COMBOBOX, handler=self.combobox, source=ch1)hbox1.Add(statictext, flag=wx.LEFT | wx.RIGHT | wx.FIXED_MINSIZE, border=5)hbox1.Add(ch1, flag=wx.ALL | wx.FIXED_MINSIZE)# 创建水平方向布局boxhbox2 = wx.BoxSizer(wx.HORIZONTAL)# 创建静态文本,放到panel面板中statictext = wx.StaticText(parent=panel, label="选择性别")list2 = ['男', '女']# 创建只读不可修改下拉列表文本框ComboBoxch2 = wx.Choice(parent=panel, id=-1, choices=list2)self.Bind(event=wx.EVT_CHOICE, handler=self.choice, source=ch2)hbox2.Add(statictext, flag=wx.LEFT | wx.RIGHT | wx.FIXED_MINSIZE, border=5)hbox2.Add(ch2, flag=wx.ALL | wx.FIXED_MINSIZE)# 创建垂直布局管理器,将水平布局管理器加入进来vbox = wx.BoxSizer(wx.VERTICAL)vbox.Add(hbox1, proportion=0, flag=wx.ALL | wx.EXPAND, border=5)vbox.Add(hbox2, proportion=0, flag=wx.ALL | wx.EXPAND, border=5)panel.SetSizer(vbox)def combobox(self, event):print(f'combobox: {event.GetString()}')def choice(self, event):print(f'choice: {event.GetString()}')class App(wx.App):def OnInit(self):# 创建窗口对象frame = MyFrame()frame.Show()return Truedef OnExit(self):print('应用程序退出')return 0if __name__ == '__main__':app = App()app.MainLoop() # 进入主事件循环
执行效果:
4.5 列表
列表控件类似于下拉列表控件,只是没有文本框,只有一个列表选项,如图 19-20所示,列表控件可以单选或多选。列表控件类是 wx.ListBox。
wx.ListBox 参数style设置列表风格样式,常用的有4种风格:
- WX.LB_SINGLE。单选。
- wx.LB_MULTIPLE。多选。
- WX.LB_EXTENDED。也是多选,但需要按住Ctrl或Shit键时选择项目。
- Wx.LB_SORT。对列表选择项进行排序。
class MyFrame(wx.Frame):def __init__(self):super().__init__(parent=None, title="复选框和单选框", size=(400, 300))self.Center()panel = wx.Panel(parent=self)# 创建水平方向布局boxhbox1 = wx.BoxSizer(wx.HORIZONTAL)# 创建静态文本,放到panel面板中statictext = wx.StaticText(parent=panel, label="选择你喜欢的编程语言:")list1 = ["Python", "JavaScript", "C++", "JavaScript"]# 创建可修改下拉列表,没有文本框, 单选(style=wx.LB_SINGLE)lb1 = wx.ListBox(parent=panel, id=-1 , choices=list1, style=wx.LB_SINGLE)self.Bind(event=wx.EVT_LISTBOX, handler=self.combobox, source=lb1)hbox1.Add(statictext, flag=wx.LEFT | wx.RIGHT | wx.FIXED_MINSIZE, border=5)hbox1.Add(lb1, proportion=1, flag=wx.ALL | wx.FIXED_MINSIZE)# 创建水平方向布局boxhbox2 = wx.BoxSizer(wx.HORIZONTAL)# 创建静态文本,放到panel面板中statictext = wx.StaticText(parent=panel, label="选择你喜欢吃的水果:")list2 = ['苹果', '橘子', '香蕉', '梨子']# 创建可修改下拉列表,没有文本框, 多选(style=x.LB_MULTIPLE)lb2 = wx.ListBox(parent=panel, id=-1, choices=list2, style=wx.LB_MULTIPLE)self.Bind(event=wx.EVT_LISTBOX, handler=self.choice, source=lb2)hbox2.Add(statictext, flag=wx.LEFT | wx.RIGHT | wx.FIXED_MINSIZE, border=5)hbox2.Add(lb2, proportion=1, flag=wx.ALL | wx.FIXED_MINSIZE,)# 创建垂直布局管理器,将水平布局管理器加入进来vbox = wx.BoxSizer(wx.VERTICAL)vbox.Add(hbox1, proportion=1, flag=wx.ALL | wx.EXPAND, border=5)vbox.Add(hbox2, proportion=1, flag=wx.ALL | wx.EXPAND, border=5)panel.SetSizer(vbox)def combobox(self, event):print(f'combobox: {event.GetString()}')def choice(self, event):print(f'choice: {event.GetString()}')class App(wx.App):def OnInit(self):# 创建窗口对象frame = MyFrame()frame.Show()return Truedef OnExit(self):print('应用程序退出')return 0if __name__ == '__main__':app = App()app.MainLoop() # 进入主事件循环
运行效果图:
4.6 静态图片控件
静态图片控件类是 wx.StaticBitmap。
class MyFrame(wx.Frame):def __init__(self):super().__init__(parent=None, title="复选框和单选框", size=(400, 300))self.Center()# 在父容器里面创建面板,名字为panelpanel = wx.Panel(parent=self)self.bmps = [# 创建Bitmap展示图片,图片类型为gifwx.Bitmap('images/bird5.gif', wx.BITMAP_TYPE_GIF),wx.Bitmap('images/bird4.gif', wx.BITMAP_TYPE_GIF),wx.Bitmap('images/bird3.gif', wx.BITMAP_TYPE_GIF),]# 创建垂直布局管理器vbox = wx.BoxSizer(wx.VERTICAL)# 创建Button事件,在panel面板中显示button1按钮button1 = wx.Button(parent=panel, id=1, label="button1")button2 = wx.Button(parent=panel, id=2, label="button2")self.Bind(event=wx.EVT_BUTTON, handler=self.on_click, id=1, id2=2)# 创建静态bitmap事件,在panel面板中显示gif图片self.image = wx.StaticBitmap(parent=panel, id=-1, bitmap=self.bmps[0])vbox.Add(button1, proportion=1, flag=wx.CENTER | wx.EXPAND)vbox.Add(button2, proportion=1, flag=wx.CENTER | wx.EXPAND)vbox.Add(self.image, proportion=3, flag=wx.CENTER)self.panel = panelpanel.SetSizer(vbox)def on_click(self, event):event_id = event.GetId()if event_id == 1:# 设置图片self.image.SetBitmap(self.bmps[1])else:self.image.SetBitmap(self.bmps[2])# 刷新panel面板,不刷新会影响图片显示效果self.panel.Layout()class App(wx.App):def OnInit(self):# 创建窗口对象frame = MyFrame()frame.Show()return Truedef OnExit(self):print('应用程序退出')return 0if __name__ == '__main__':app = App()app.MainLoop() # 进入主事件循环
运行效果:
五、高级窗口
5.1 分割窗口
wx.SplitterWindow 中一个常用的方法有:
- SplitVertically(window1, window2, sashPosition=0)。设置左右布局的分隔窗口,window1 为左窗口,window2 为右窗口,sashPosition 是窗框的位置。
- SplitHorizontally(window1, window2, sashPosition=0)。设置上下布局的分隔窗口,window1 为上窗口,window2 为下窗口,sashPosition 是窗框的位置。
- SetMinimumPaneSize(paneSize)。设置最小窗口尺寸,如果是左右布局是指左窗口的最小尺寸,如果是上下布局是指上窗口的最小尺寸。如果没有设置则默认为 0。
class MyFrame(wx.Frame):def __init__(self):super().__init__(parent=None, title="分隔窗口", size=(400, 300))self.Center()# 在父窗口创建左右布局分割窗口,不要放到内容面板,直接放到fremesplitter = wx.SplitterWindow(self, -1)# 创建左侧面板leftPanel = wx.Panel(splitter)# 创建右侧面板rightPanel = wx.Panel(splitter)# 设置哪个是左面板,哪个是右面板,窗框的位置是100splitter.SplitVertically(leftPanel, rightPanel, 100)# 设置面板最小的值splitter.SetMinimumPaneSize(80)list1 = ['apple', 'orange', 'pear']# 创建列表控件,加入到左侧面板lb2 = wx.ListBox(parent=leftPanel, id=-1, choices=list1, style=wx.LB_SINGLE)# 为列表控件绑定事件self.Bind(event=wx.EVT_LISTBOX, handler=self.on_click, source=lb2)# 创建垂直布局管理器vbox1 = wx.BoxSizer(wx.VERTICAL)# 把lb2控件添加到垂直布局管理中vbox1.Add(lb2, proportion=1, flag=wx.ALL | wx.EXPAND, border=5)# 把vbox1设置到左侧面板leftPanel.SetSizer(vbox1)vbox2 = wx.BoxSizer(wx.VERTICAL)self.content = wx.StaticText(rightPanel, label="右侧面板")vbox2.Add(self.content, proportion=1, flag=wx.ALL | wx.EXPAND, border=5)rightPanel.SetSizer(vbox2)def on_click(self, event):s = f'选择{event.GetString()}'self.content.SetLabelText(s)class App(wx.App):def OnInit(self):# 创建窗口对象frame = MyFrame()frame.Show()return Truedef OnExit(self):print('应用程序退出')return 0if __name__ == '__main__':app = App()app.MainLoop() # 进入主事件循环
运行效果:
5.2 使用树
- AddRoot(text, image=-1, selImage=-1, data=None)。添加根节点,text 参数根节点显示的文本;image 参数是该节点未被选中时的图片索引,wx.TreeCtrl 中使用的图片被放到 wx.ImageList 图像列表中;selImage 参数是该节点被选中时的图片索引。data 参数是给节点传递的数据。方法返回节点,节点类型是 wx.TreeItemId。
- AppendItem(parent, text, image=-1, selImage=-1, data=None)。添加子节点,parent 参数是父节点,其他参数同 AddRoot()方法。方法返回值 wx.TreeItemId。
- SelectItem(item, select=True)。选中 item 节点,如图 19-24 所示 TreeRoot 节点被选
中。 - Expand(item)。展开 item 节点,如图 19-24 所示 Item 1 和 Item 4 节点处于展开状态。
- ExpandAll()。展开根节点下的所有子节点。
- ExpandAllChildren(item)。展开 item 节点下的所有子节点。
- AssignImageList(imageList)。保存 wx.ImageList 图像列表到树中,这样就可以在AddRoot()和 AppendItem()方法中使用图像列表索引了。
class MyFrame(wx.Frame):def __init__(self):super().__init__(parent=None, title="分隔窗口", size=(400, 300))self.Center()# 在父窗口创建左右布局分割窗口,不要放到内容面板,直接放到fremesplitter = wx.SplitterWindow(self, -1)# 创建左侧面板leftPanel = wx.Panel(splitter)# 创建右侧面板rightPanel = wx.Panel(splitter)# 设置哪个是左面板,哪个是右面板,窗框的位置是100splitter.SplitVertically(leftPanel, rightPanel, 200)# 设置面板最小的值splitter.SetMinimumPaneSize(80)# 创建树控件self.tree = self.CreateTreeCtrl(leftPanel)# 为树控件绑定事件self.Bind(event=wx.EVT_TREE_SEL_CHANGED, handler=self.on_click, source=self.tree)# 创建垂直布局管理器vbox1 = wx.BoxSizer(wx.VERTICAL)# 把self.tree数控件添加到垂直布局管理中vbox1.Add(self.tree, proportion=1, flag=wx.ALL | wx.EXPAND, border=5)# 把vbox1设置到左侧面板leftPanel.SetSizer(vbox1)vbox2 = wx.BoxSizer(wx.VERTICAL)self.content = wx.StaticText(rightPanel, label="右侧面板")vbox2.Add(self.content, proportion=1, flag=wx.ALL | wx.EXPAND, border=5)rightPanel.SetSizer(vbox2)def CreateTreeCtrl(self, parent):tree = wx.TreeCtrl(parent)imglist = wx.ImageList(width=16, height=16, mask=True, initialCount=2)imglist.Add(wx.ArtProvider.GetBitmap(wx.ART_FOLDER, size=wx.Size(16, 16)))imglist.Add(wx.ArtProvider.GetBitmap(wx.ART_NORMAL_FILE, size=wx.Size(16, 16)))tree.AssignImageList(imglist)items = []root = tree.AddRoot('TreeRoot', image=0)items.append(tree.AppendItem(root, 'Item 1', 0))items.append(tree.AppendItem(root, 'Item 2', 0))items.append(tree.AppendItem(root, 'Item 3', 0))items.append(tree.AppendItem(root, 'Item 4', 0))items.append(tree.AppendItem(root, 'Item 5', 0))for i in range(len(items)):id = items[i]tree.AppendItem(id, 'Subitem 1', 1)tree.AppendItem(id, 'Subitem 2', 1)tree.AppendItem(id, 'Subitem 3', 1)tree.AppendItem(id, 'Subitem 4', 1)tree.AppendItem(id, 'Subitem 5', 1)tree.Expand(root)tree.Expand(items[0])tree.Expand(items[3])tree.SelectItem(root)return treedef on_click(self, event):item = event.GetItem()self.content.SetLabel(self.tree.GetItemText(item))class App(wx.App):def OnInit(self):# 创建窗口对象frame = MyFrame()frame.Show()return Truedef OnExit(self):print('应用程序退出')return 0if __name__ == '__main__':app = App()app.MainLoop() # 进入主事件循环
运行效果:
六、使用菜单
6.1 使用菜单
菜单栏不添加到父窗口中,需要在顶级窗口中通过SetMenuBar(menuBar)方法将添加菜单栏。菜单栏(wx.MenuBar)通过 Append(menu,title)方法将菜单添加到菜单栏中,其中 menu 是菜单对象,title是菜单上文本。菜单对象(wx.Menu)通过 Append(menultem)方法将菜单项添加到菜单中。
kind是菜单项类型,菜单项类型主要有如下5种:
Wx.ITEM_SEPARATOR。分隔线菜单项:
Wx.ITEM_NORMAL。普通菜单项。
wx.ITEM_CHECK。复选框形式的菜单项,
WX.ITEM_RADIO。单选按钮形式的菜单项。
Wx.ITEM_DROPDOWN。下拉列表形式的菜单项。
class MyFrame(wx.Frame):def __init__(self):super().__init__(parent=None, title="使用菜单", size=(400, 300))self.Center()# 创建一个多行的文本控制器self.text = wx.TextCtrl(self, id=-1, style=wx.EXPAND | wx.TE_MULTILINE)# 创建一个垂直布局管理器vbox = wx.BoxSizer(wx.VERTICAL)# 将多行文本加到垂直布局管理vbox.Add(self.text, proportion= 1, flag=wx.EXPAND | wx.ALL, border=1)self.SetSizer(vbox)# 创建一个菜单栏memubar = wx.MenuBar()# 创建一个菜单对象file_menu = wx.Menu()# 将菜单对象file_menu添加到菜单栏中,显示文本为【文件】memubar.Append(file_menu,title='文件')# 通过MenuItem新建一个菜单项,显示为【新建】,加入到file_menu【文件】菜单对象中new_item = wx.MenuItem(parentMenu=file_menu, id=99, text='新建', kind=wx.ITEM_NORMAL)# 将菜单项new_item【新建】添加到file_menu【文件】菜单对象中file_menu.Append(new_item)# 菜单对象间的分割线file_menu.AppendSeparator()# 新建一个【编辑】菜单对象edit_menu = wx.Menu()# 通过MenuItem新建一个菜单项,显示为【复制】,使用父菜单edit_menu【编辑】菜单对象中copy_item = wx.MenuItem(parentMenu=edit_menu, id=100, text='复制', kind=wx.ITEM_NORMAL)# 将菜单项copy_item添加到edit_menu菜单对象中edit_menu.Append(copy_item)# 通过MenuItem新建一个菜单项,显示为【剪切】,使用父菜单edit_menu【编辑】菜单对象中cut_item = wx.MenuItem(parentMenu=edit_menu, id=101, text='剪切', kind=wx.ITEM_NORMAL)# 将菜单项cut_item添加到edit_menu菜单对象中edit_menu.Append(cut_item)# 通过MenuItem新建一个菜单项,显示为【粘贴】,使用父菜单edit_menu【编辑】菜单对象中paste_item = wx.MenuItem(parentMenu=edit_menu, id=102, text='粘贴', kind=wx.ITEM_NORMAL)# 将菜单项paste_item添加到edit_menu菜单对象中edit_menu.Append(paste_item)# 以上菜单项事件绑定到事件处理方法self.on_clickself.Bind(event=wx.EVT_MENU, handler=self.on_click, id=99, id2=102)# 将菜单项edit_item【编辑】添加到file_menu【文件】菜单对象中file_menu.Append(id=99, item='编辑', subMenu=edit_menu)# 将菜单栏添加到顶级窗口中self.SetMenuBar(memubar)def on_click(self, event):event_id = event.GetId()if event_id == 99:self.text.SetLabel('单击【新建】菜单')elif event_id == 100:self.text.SetLabel('单击【复制】菜单')elif event_id == 101:self.text.SetLabel('单击【剪切】菜单')else:self.text.SetLabel('单击【粘贴】菜单')class App(wx.App):def OnInit(self):# 创建窗口对象frame = MyFrame()frame.Show()return Truedef OnExit(self):print('应用程序退出')return 0if __name__ == '__main__':app = App()app.MainLoop() # 进入主事件循环
使用效果:
七、使用工具栏
wxPython 中工具栏类是wx.Toolbar。顶级窗口都有一个 ToolBar 属性可以设置它的工具栏,然后通过工具栏的 AddTool()方法添加按钮到工具栏,最后调用工具栏的 Realize()确定。
import wx
import wx.grid
# 自定义窗口类MyFrame
class MyFrame(wx.Frame):def __init__(self):super().__init__(parent=None, title='使用工具栏', size=(550, 500))self.Centre() # 设置窗口居中self.Show(True)self.text = wx.TextCtrl(self, -1, style=wx.EXPAND | wx.TE_MULTILINE)vbox = wx.BoxSizer(wx.VERTICAL)vbox.Add(self.text, proportion=1, flag=wx.EXPAND | wx.ALL, border=1)self.SetSizer(vbox)menubar = wx.MenuBar()file_menu = wx.Menu()new_item = wx.MenuItem(file_menu, wx.ID_NEW, text="新建", kind=wx.ITEM_NORMAL)file_menu.Append(new_item)file_menu.AppendSeparator()edit_menu = wx.Menu()copy_item = wx.MenuItem(edit_menu, 100, text="复制", kind=wx.ITEM_NORMAL)edit_menu.Append(copy_item)cut_item = wx.MenuItem(edit_menu, 101, text="剪切", kind=wx.ITEM_NORMAL)edit_menu.Append(cut_item)paste_item = wx.MenuItem(edit_menu, 102, text="粘贴", kind=wx.ITEM_NORMAL)edit_menu.Append(paste_item)file_menu.Append(wx.ID_ANY, "编辑", edit_menu)menubar.Append(file_menu, '文件')self.SetMenuBar(menubar)tb = wx.ToolBar(self, wx.ID_ANY)self.ToolBar = tbtsize = (24, 24)new_bmp = wx.ArtProvider.GetBitmap(wx.ART_NEW, wx.ART_TOOLBAR, tsize)open_bmp = wx.ArtProvider.GetBitmap(wx.ART_FILE_OPEN, wx.ART_TOOLBAR, tsize)copy_bmp = wx.ArtProvider.GetBitmap(wx.ART_COPY, wx.ART_TOOLBAR, tsize)paste_bmp = wx.ArtProvider.GetBitmap(wx.ART_PASTE, wx.ART_TOOLBAR, tsize)tb.AddTool(10, "New", new_bmp, kind=wx.ITEM_NORMAL, shortHelp="New")tb.AddTool(20, "Open", open_bmp, kind=wx.ITEM_NORMAL, shortHelp="Open")tb.AddSeparator()tb.AddTool(30, "Copy", copy_bmp, kind=wx.ITEM_NORMAL, shortHelp="Copy")tb.AddTool(40, "Paste", paste_bmp, kind=wx.ITEM_NORMAL, shortHelp="Paste")tb.AddSeparator()tb.AddTool(201, "back", wx.Bitmap("menu_icon/back.png"), kind=wx.ITEM_NORMAL, shortHelp="Back")tb.AddTool(202, "forward", wx.Bitmap("menu_icon/forward.png"), kind=wx.ITEM_NORMAL, shortHelp="Forward")self.Bind(wx.EVT_MENU, self.on_click, id=201, id2=202)tb.AddSeparator()tb.Realize()def on_click(self, event):event_id = event.GetId()if event_id == 201:self.text.SetLabel('单击【Back】按钮')else:self.text.SetLabel('单击【Forward】按钮')class App(wx.App):def OnInit(self):# 创建窗口对象frame = MyFrame()frame.Show()return Trueif __name__ == '__main__':app = App()app.MainLoop() # 进入主事件循环
使用效果: