目录
Python中的图形用户界面开发库
安装wxPython
第一个wxPython程序
自定义窗口类
在窗口中添加控件
事件处理
布局管理
盒子布局管理器
重构事件处理示例
盒子布局管理器嵌套示例
控件
文本输入控件
复选框和单选按钮
列表
静态图片控件
我们之前的程序运行结果都被输出到命令提示符(终端)窗口,界面比较简陋。本章讲解如何将其输出到图形界面。
Python中的图形用户界面开发库有很多,较为突出的有:Tkinter、PyQt和wxPython。推荐使用wxPython开发图形用户界面。
注意:Qt是一个跨平台的C++应用程序开发框架,被广泛用于开发GUI 程序,也可用于开发非GUI程序。
(1)Tkinter
Tkinter是Python官方提供的图形用户界面开发库,用于封装TkGUI工具包,跨平台。但是,Tkinter工具包所包含的控件较少,帮助文档不健全,不便于我们开发复杂的图形用户界面。
(2)PyQt
PyQt是非Python官方提供的图形用户界面开发库,用于封装Qt工具包,跨平台。若想使用PyQt工具包,则需要额外安装软件包。
(3)wxPython
wxPython是非Python官方提供的图形用户界面开发库,也跨平台。 它提供了丰富的控件,可用于开发复杂的图形用户界面。它的工具包帮助文档很完善,案例也很丰富。
可以使用Python的pip指令进行安装,pip是Python提供的包(库)管理工具,可以对第三方库进行安装、卸载等操作。
在命令提示符(终端)窗口输入pip指令: 在Windows平台上通过pip指令安装wxPython,在命令提示符窗口输入如下指令。
pip install wxPython
如果安装成功,则可以出现如下窗口。
图形用户界面主要是由窗口及窗口中的控件构成的,编写wxPython程序其实主要是创建窗口和添加控件的过程。
若要构建一个最简单的wxPython程序,则至少需要一个应用(wx. App)对象和一个窗口(wx.Frame)对象。
示例代码如下:
import wx
#创建应用程序对象
app=wx.App()
#创建窗口对象
frm=wx.Frame(None,title="第一个wxPython程序!",size=(400,300),pos=(100,100)) # 所在父窗口,None表示没有父窗口,窗口的大小,窗口的位置
#显示窗口
frm.Show() # 窗口默认隐藏,需要调用Show()方法才能显示
#进入主事件循环
app.MainLoop()
注意:事件循环是一种事件或信息分发处理机制,大部分图形用户界面在界面中的显示及响应用户事件的处理都是通过主事件循环实现的。
输出结果(弹出以下窗口)
上面的示例过于简单,我们可以自定义窗口(wx.Frame)类,以便于扩展功能。示例代码如下:
import wx
#自定义窗口类MyFrame
class MyFrame(wx.Frame):
def __init__(self):
super().__init__(None,title="第一个wxPython程序!",size=(400,300),pos=(100,100))
#创建应用程序对象
app=wx.App()
#创建窗口对象
frm=MyFrame()
#显示窗口
frm.Show() # 窗口默认隐藏,需要调用Show()方法才能显示
#进入主事件循环
app.MainLoop()
输出结果(弹出以下窗口)
我们在窗口中添加两个控件:一个面板(Panel)和一个静态文本 (StaticText)。面板是一个没有标题栏的容器(可以容纳其他控件的控件)。示例代码如下:
import wx
#自定义窗口类MyFrame
class MyFrame(wx.Frame):
def __init__(self):
super().__init__(None,title="第一个wxPython程序!",size=(400,300),pos=(100,100))
panel=wx.Panel(parent=self) # 创建面板对象,参数parent传递的是self,即设置面板所在的父容器为当前窗口对象
statictext=wx.StaticText(parent=panel,label='Hello World!',pos=(10,10)) # 创建静态文本对象,将静态文本对象放到panel面板中,所以parent参数传递的是panel,参数label是在静态文本对象上显示的文字,参数pos用于设置静态文本对象的位置
#创建应用程序对象
app=wx.App()
#创建窗口对象
frm=MyFrame()
#显示窗口
frm.Show() # 窗口默认隐藏,需要调用Show()方法才能显示
#进入主事件循环
app.MainLoop()
输出结果(弹出以下窗口)
在以上示例中,面板被放到窗口中,而静态文本对象被放到面板中。
注意:控件可以直接放到窗口中,但直接把控件放到窗口中在布局时会有很多问题,推荐使用本例中的做法,先将所有控件都放到面板中,再将面板放到窗口中。
图形界面的控件要响应用户的操作,就必须添加事件处理机制。事件处理的过程如下图所示。
其中涉及的主要内容如下。
(1)事件源:事件发生的场所,就是各个控件,例如按钮事件的事件源是按钮。
(2)事件:wxPython中的事件被封装为事件类wx.Event及其子类,例如按钮事件类是wx.CommandEvent,鼠标事件类是wx.MoveEvent。
(3)事件处理程序:一个响应用户事件的方法。
下面通过一个示例介绍事件处理流程。在以下窗口中有一个按钮和一个静态文本,在单击OK按钮时会改变静态文本显示的内容。
示例代码如下:
import wx
#自定义窗口类MyFrame
class MyFrame(wx.Frame):
def __init__(self):
super().__init__(None,title="事件处理",size=(300,180))
panel=wx.Panel(parent=self)
self.statictext=wx.StaticText(parent=panel,label="请单击OK按钮",pos=(110,20))
b=wx.Button(parent=panel,label='OK',pos=(100,50)) #创建按钮对象
self.Bind(wx.EVT_BUTTON,self.on_click,b) # 绑定事件,wx.EVT_BUTTON是事件类型,即按钮单击事件;self.on_click是事件处理程序;b是事件源,即按钮对象
def on_click(self,event): # 事件处理程序
self.statictext.SetLabelText('Hello,World.')
#创建应用程序对象
app=wx.App()
#创建窗口对象
frm=MyFrame()
#显示窗口
frm.Show() # 窗口默认隐藏,需要调用Show()方法才能显示
#进入主事件循环
app.MainLoop()
控件的位置和大小都使用了绝对数值,这就是绝对布局。绝对布局有很多问题,在进行界面布局时尽量不要采用绝对布局。
wxPython提供了布局管理器类帮助实现界面布局,主要分为两大类 :盒子布局管理器和网格布局管理器。盒子布局类似于CSS中的弹性布局,非常灵活,我们重点介绍盒子布局。
盒子布局管理器类是wx.BoxSizer,Box布局管理器是最常用的布局管理器,它可以让其中的子窗口(或控件)沿垂直或水平方向布局。
(1)创建盒子布局管理器对象
我们使用wx.BoxSizer类创建盒子布局管理器对象,主要的构造方法如下:
设置为水平方向布局
wx.BoxSizer(wx.HORIZONTAL)
设置为垂直方向布局
wx.BoxSizer(wx.VERTICAL)
wx.HORIZONTAL是默认值,可以省略。
(2)添加子窗口(或控件)到父窗口
我们使用wx.BoxSizer对象的Add()方法添加子窗口(或控件)到父窗口,对Add()方法的语法说明如下:
Add(window,proportion=0,flag=0,border=0)
添加到父窗口
Add(sizer,proportion=0,flag=0,border=0) # 添加到另外一个布局对象,用于布局嵌套
proportion参数用于设置当前子窗口(或控件)在父窗口中所占的空间比例;flag参数是布局标志,用来控制对齐方式、边框和调整尺寸 ;border参数用于设置边框的宽度。
下面重点介绍flag标志,flag标志可以分为对齐、边框和调整尺寸。 flag对齐标志如下表所示。
flag边框标志如下表所示。
flag调整尺寸标志如下表所示。
前面的事件处理示例采用了绝对布局,这里采用盒子布局重构该示例。
示例代码如下:
import wx
#自定义窗口类MyFrame
class MyFrame(wx.Frame):
def __init__(self):
super().__init__(None,title="事件处理",size=(300,180))
panel=wx.Panel(parent=self)
self.statictext=wx.StaticText(parent=panel,label="请单击OK按钮")
b=wx.Button(parent=panel,label='OK') #创建按钮对象
self.Bind(wx.EVT_BUTTON,self.on_click,b) # 绑定事件,wx.EVT_BUTTON是事件类型,即按钮单击事件;self.on_click是事件处理程序;b是事件源,即按钮对象
# 布局相关代码
# 创建垂直方向的盒子布局管理对象vbox
vbox=wx.BoxSizer(wx.VERTICAL)
# 添加静态文本到vbox布局管理器
vbox.Add(self.statictext,proportion=1,# 两个控件proportion都为1,所以两个控件各占二分之一
flag=wx.ALIGN_CENTER_HORIZONTAL|wx.FIXED_MINSIZE|wx.TOP,border=30) # 控件水平居中|刚好包裹控件|设置顶部有边框,设置顶部边框宽度为30
# 添加按钮b到vbox布局管理器
vbox.Add(b,proportion=1,flag=wx.EXPAND|wx.BOTTOM,border=10) # 完全填满有效空间
# 设置面板(panel)采用vbox布局管理器
panel.SetSizer(vbox) # 两个控件都被放到面板中,所以需要设置布局为盒子布局
def on_click(self,event): # 事件处理程序
self.statictext.SetLabelText('Hello,World.')
#创建应用程序对象
app=wx.App()
#创建窗口对象
frm=MyFrame()
#显示窗口
frm.Show() # 窗口默认隐藏,需要调用Show()方法才能显示
#进入主事件循环
app.MainLoop()
对两个控件布局说明如下:
布局管理器还可以进行嵌套,我们通过一个示例介绍盒子布局管理器的嵌套。在该示例窗口中包括两个按钮和一个静态文本。
示例代码如下:
import wx
#自定义窗口类MyFrame
class MyFrame(wx.Frame):
def __init__(self):
super().__init__(None,title="布局管理器嵌套",size=(300,120))
panel=wx.Panel(parent=self)
self.statictext=wx.StaticText(parent=panel,label="请单击按钮")
b1=wx.Button(parent=panel,id=10,label='Button1')
b2=wx.Button(parent=panel,id=11,label='Button2')
# 创建水平方向的盒子布局管理hbox对象
hbox=wx.BoxSizer(wx.HORIZONTAL)
# 添加b1到hbox布局管理
hbox.Add(b1,proportion=1,flag=wx.EXPAND|wx.ALL,border=10)
# 添加b2到hbox布局管理
hbox.Add(b2,proportion=1,flag=wx.EXPAND|wx.ALL,border=10)
# 创建垂直方向的盒子布局管理对象vbox
vbox=wx.BoxSizer(wx.VERTICAL)
# 添加静态文本到vbox布局管理器
vbox.Add(self.statictext,proportion=1,# 两个控件proportion都为1,所以两个控件各占二分之一
flag=wx.CENTER|wx.FIXED_MINSIZE|wx.TOP,border=10) # 控件水平居中|刚好包裹控件|设置顶部有边框,设置顶部边框宽度为30
# 将水平hbox布局管理器对象到垂直vbox布局管理器对象
vbox.Add(hbox,proportion=1,flag=wx.CENTER)
# 设置面板(panel)采用vbox布局管理器
panel.SetSizer(vbox) # 两个控件都被放到面板中,所以需要设置布局为盒子布局
#将两按钮的单击事件绑定到self.on_click方法
self.Bind(wx.EVT_BUTTON,self.on_click,id=10,id2=20) # 绑定id为参数id~id2的按钮
def on_click(self,event): # 事件处理程序
event_id=event.GetId() # 获取绑定按钮的id
print(event_id)
if event_id==10: # 根据id判断单击了哪一个按钮
self.statictext.SetLabelText('Button1单击')
else:
self.statictext.SetLabelText('Button2单击')
#创建应用程序对象
app=wx.App()
#创建窗口对象
frm=MyFrame()
#显示窗口
frm.Show() # 窗口默认隐藏,需要调用Show()方法才能显示
#进入主事件循环
app.MainLoop()
在本例中采用了嵌套布局,首先将两个按钮(b1和b2)放到一个水平方向的盒子布局管理器对象(hbox)中,然后将一个静态文本(static text)和hbox放到一个垂直方向的盒子布局管理器对象(vbox)中。
wxPython的所有控件都继承自wx.Control类。之前的示例已经使用了静态文本和按钮,本节重点介绍文本输入控件、单选按钮、复选框、列表和静态图片控件。
文本输入控件(wx.TextCtrl)是可以输入文本的控件。
示例:在界面中实现三个文本输入控件和三个静态文本。
示例代码如下:
import wx
#自定义窗口类MyFrame
class MyFrame(wx.Frame):
def __init__(self):
super().__init__(None,title="文本输入控件",size=(300,260))
panel=wx.Panel(parent=self)
tc1=wx.TextCtrl(panel) # 创建普通文本输入控件
tc2=wx.TextCtrl(panel,style=wx.TE_PASSWORD) # 创建密码输入控件,设置style=wx.TE_PASSWORD
tc3=wx.TextCtrl(panel,style=wx.TC_MULTILINE) # 创建多行文本输入控件,设置style=wx.TC_MULTILINE
userid=wx.StaticText(panel,label="用户ID:")
pwd=wx.StaticText(panel,label="密码:")
content=wx.StaticText(panel,label="多行文本:")
# 创建垂直方向的盒子布局管理对象vbox
vbox=wx.BoxSizer(wx.VERTICAL)
vbox.Add(userid,flag=wx.EXPAND|wx.LEFT,border=10)
vbox.Add(tc1,flag=wx.EXPAND|wx.ALL,border=10)
vbox.Add(pwd,flag=wx.EXPAND|wx.LEFT,border=10)
vbox.Add(tc2,flag=wx.EXPAND|wx.ALL,border=10)
vbox.Add(content,flag=wx.EXPAND|wx.LEFT,border=10)
vbox.Add(tc3,flag=wx.EXPAND|wx.ALL,border=10)
#设置面板(panel)采用vbox布局管理器
panel.SetSizer(vbox)
#设置tc1初始值
tc1.SetValue('tony') # 设置文本输入控件的内容
#获取tc1值
print('读取用户ID:{0}'.format(tc1.GetValue())) # 获取文本输入控件的内容
#创建应用程序对象
app=wx.App()
#创建窗口对象
frm=MyFrame()
#显示窗口
frm.Show() # 窗口默认隐藏,需要调用Show()方法才能显示
#进入主事件循环
app.MainLoop()
多选控件是复选框(wx.CheckBox),复选框(wx.CheckBox)有时也能单独使用,能提供两种状态的开和关。
单选控件是单选按钮(wx.RadioButton),同一组的多个单选按钮应该具有互斥性,就是当一个按钮按下时,其他按钮一定释放。
示例:在界面中实现一组复选框和一组单选按钮。
示例代码如下:
import wx
#自定义窗口类MyFrame
class MyFrame(wx.Frame):
def __init__(self):
super().__init__(None,title="复选框和单选按钮",size=(330,120))
panel=wx.Panel(parent=self)
st1=wx.StaticText(panel,label='选择你喜欢的编程语言:')
cb1=wx.CheckBox(panel,id=1,label='Python') # 创建单选按钮
cb2=wx.CheckBox(panel,id=2,label='Java')
cb2.SetValue(True) # 设置cb2初始状态为选中
cb3=wx.CheckBox(panel,id=3,label='C++')
self.Bind(wx.EVT_CHECKBOX,self.on_checkbox_click,id=1,id2=3) # 绑定id为1~3的所有控件的事件处理到on_check_clickck()方法
st2=wx.StaticText(panel,label='选择性别:')
# 设置style=wx.RB_GROUP的单选按钮,说明一个组的开始,直到遇到另外设置style=wx.RB_GROUP的wx.RadioButton单选按钮为止都是同一个组,所以radio1和radio2是同一组,即这两个单选按钮是互斥的
radio1=wx.RadioButton(panel,id=4,label='男',style=wx.RB_GROUP) # 创建单选按钮
radio2=wx.RadioButton(panel,id=5,label='女')
self.Bind(wx.EVT_RADIOBUTTON,self.on_radio1_click,id=4,id2=5) # 绑定id从4~5的控件到on_radio1_click()方法
hbox1=wx.BoxSizer()
hbox1.Add(st1,flag=wx.LEFT|wx.RIGHT,border=5)
hbox1.Add(cb1)
hbox1.Add(cb2)
hbox1.Add(cb3)
hbox2=wx.BoxSizer()
hbox2.Add(st2,flag=wx.LEFT|wx.RIGHT,border=5)
hbox2.Add(radio1)
hbox2.Add(radio2)
vbox=wx.BoxSizer(wx.VERTICAL)
vbox.Add(hbox1,flag=wx.ALL,border=10)
vbox.Add(hbox2,flag=wx.ALL,border=10)
#设置面板(panel)采用vbox布局管理器
panel.SetSizer(vbox)
def on_checkbox_click(self,event):
cb=event.GetEventObject()
print('选择{0},状态{1}'.format(cb.GetLabel(),event.IsChecked())) # 从事件对象中取出事件源对象(复选框)
def on_radio1_click(self,event):
rb=event.GetEventObject() # 事件对象取出事件源对象
print('第一组{0}被选中'.format(rb.GetLabel()))
#创建应用程序对象
app=wx.App()
#创建窗口对象
frm=MyFrame()
#显示窗口
frm.Show() # 窗口默认隐藏,需要调用Show()方法才能显示
#进入主事件循环
app.MainLoop()
对列表控件可以进行单选或多选,列表控件类是wx.ListBox。
示例:在界面中实现以下两个列表控件。
示例代码如下:
import wx
#自定义窗口类MyFrame
class MyFrame(wx.Frame):
def __init__(self):
super().__init__(None,title="列表",size=(350,175))
panel=wx.Panel(parent=self)
st1=wx.StaticText(panel,label='选择你喜欢的编程语言:')
list1=['Python','C++','Java']
lb1=wx.ListBox(panel,choices=list1,style=wx.LB_SINGLE) # 创建列表控件,参数choices用于设置列表选项;参数style用于设置列表风格样式,wx.LB_SINGLE指单选列表控件
self.Bind(wx.EVT_LISTBOX,self.on_listbox1,lb1) # 绑定列表选择事件wx.EVT_LISTBOX到self.on_listbox1()方法
st2=wx.StaticText(panel,label='选择你喜欢吃的水果:')
list2=['苹果','橘子','香蕉']
lb2=wx.ListBox(panel,choices=list2,style=wx.LB_EXTENDED) #style=wx.LB_EXTENDED表示创建多选列表控件
self.Bind(wx.EVT_LISTBOX,self.on_listbox2,lb2)
hbox1=wx.BoxSizer()
hbox1.Add(st1,proportion=1,flag=wx.LEFT|wx.RIGHT,border=5)
hbox1.Add(lb1,proportion=1)
hbox2=wx.BoxSizer()
hbox2.Add(st2,proportion=1,flag=wx.LEFT|wx.RIGHT,border=5)
hbox2.Add(lb2,proportion=1)
vbox=wx.BoxSizer(wx.VERTICAL)
vbox.Add(hbox1,flag=wx.ALL|wx.EXPAND,border=5)
vbox.Add(hbox2,flag=wx.ALL|wx.EXPAND,border=5)
#设置面板(panel)采用vbox布局管理器
panel.SetSizer(vbox)
def on_listbox1(self,event):
listbox=event.GetEventObject()
print('选择{0}'.format(listbox.GetSelection())) # 返回单个选中项目的索引序号
def on_listbox2(self,event):
listbox=event.GetEventObject()
print('选择{0}'.format(listbox.GetSelection())) # 返回多个选中项目的索引序号列表
#创建应用程序对象
app=wx.App()
#创建窗口对象
frm=MyFrame()
#显示窗口
frm.Show() # 窗口默认隐藏,需要调用Show()方法才能显示
#进入主事件循环
app.MainLoop()
创建列表控件时需要用到的参数style的常见取值有以下四种。
wx.LB_SINGLE:单选。
wx.LB_MULTIPLE:多选。
wx.LB_EXTENDED:多选,但是需要在按住Ctrl或Shift键时选择项目。
wx.LB_SORT:对列表选择项进行排序。
静态图片控件用于显示一张图片,图片可以是wx.Python所支持的任意图片格式,静态图片控件类是wx.StaticBitmap。
示例:在界面中实现两个按钮和一个静态图片控件,在单击按钮时显示不同的图片。
示例代码如下:
import wx
#自定义窗口类MyFrame
class MyFrame(wx.Frame):
def __init__(self):
super().__init__(None,title="静态图片控件",size=(300,300))
self.panel=wx.Panel(parent=self) # 创建一个面板,它是该类的实例变量
self.bmps=[wx.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)]
b1=wx.Button(self.panel,id=1,label='Button1')
b2=wx.Button(self.panel,id=2,label='Button2')
self.Bind(wx.EVT_BUTTON,self.on_click,id=1,id2=2)
self.image=wx.StaticBitmap(self.panel,bitmap=self.bmps[0]) # 静态图片控件对象,self.nmps[0]是静态图片控件要显示的图片对象
# 创建垂直方向的布局管理器对象vbox
vbox=wx.BoxSizer(wx.VERTICAL)
# 添加标控件到布局管理器对象vbox
vbox.Add(b1,proportion=1,flag=wx.EXPAND)
vbox.Add(b2,proportion=1,flag=wx.EXPAND)
vbox.Add(self.image,proportion=3,flag=wx.EXPAND)
self.panel.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])
self.panel.Layout() # 重置设置panel面板布局
#创建应用程序对象
app=wx.App()
#创建窗口对象
frm=MyFrame()
#显示窗口
frm.Show() # 窗口默认隐藏,需要调用Show()方法才能显示
#进入主事件循环
app.MainLoop()
注意:使用self.panel.Layout()语句重新设置panel面板布局,在图片替换后,需要重写绘制窗口,否则布局会发生混乱。