什么是 GUI
既然要制作 GUI,那么就要先明确什么是 GUI。wiki上是这么说的:
图形用户界面(Graphical User Interface,简称 GUI)是指采用图形方式显示的计算机操作用户接口。
曾经有个同学这么跟我说,“MATLAB 有个 GUI 的功能,貌似很强大啊”,听完我就凌乱了,图形用户界面什么时候成了 MATLAB 的专属功能了?我平常用的 Windows 或者 Linux、Mac 都是在图形用户界面下进行操作的,如果想看一看命令行界面,Windows 可以在“运行”中输入 cmd 然后回车,Linux下可以直接 alt+F1(切换回来用 alt+F7)。简单来说,GUI 提供给用户一种更加直白的图形化的人机交互界面。
Python 中的 GUI 工具包
如果想用 Python 语言制作出一个程序的 GUI,不仅仅可以用 Tkinter 一个包,最常见的还有 wxPython 和 PyQt,还有很多很多(我不熟悉啊 (╯▽╰))。
-
wxPython:wxPython 创建的是 wxWidgets。它的好处是,可以通过简单的命令就创建出比较漂亮的界面,并且在中国的使用人数挺多,遇到问题在网上找起答案来比较轻松。缺点是并不是 Python 的标准工具包,还需要另外安装。官网在这里,官网提供的英文教程在这里
-
PyQt:和名字一样,创建的是 Qt 的 GUI。Qt 的 GUI 当然是漂亮的不能再漂亮了。优点是,创建出的 GUI 很漂亮,且有图形化的 GUI 设计软件 Qt Designer 来使整个设计过程简单化。缺点:同样不是 Python 的标准工具包,还需要另外安装,如果手工输入 GUI 代码习惯了,有时候图形化的 GUI 设计软件反而不习惯。官网在这里,这里有一个比较不错的中文教程,主要讲的是 Qt Designer 的入门使用,很不错。
-
Tkinter: 从名字可以看出,创建的是 Tk 的GUI。优点很明显,是一个已经半官方化的工具包,可以直接使用,无需安装,并且教程众多,几乎所有介绍 Python 入门的书,只要提及 GUI 设计都会讲 Tkinter。缺点一样明显,设计出的 GUI 很难看,要问多难看,你自己看看 Python 的那个 IDLE 就好了,土的掉渣啊,而且元件一样很少,如果只是编写一些小程序的话还可以,如果编写大程序要用到比较复杂的元件的话,就需要从提供的元件里找一些替代品了,手工输入代码,有时候很累啊。但是我认为用 Tkinter 来入门 GUI 设计是很好的,因为思路清晰,代码简单,这也就是为什么我在这里只介绍 Tkinter 的原因。一个英文教程在这里(之前的网站已经不在了,作者将其移到了这里,貌似开始将从前未写完的补充完整了{: style=“color: red”}),由于我不想记住所有元件的方法,所以用到的话就来这里查询,很方便。
GUI 的设计基本思路
但你开始学习 Python 的时候,所有都会考诉你,Python 是 OOP(面向对象程序设计)的程序语言,但是我认为“类”的概念对于初学者是非常难以理解的,而且也没有人规定在 Python 中必须使用“类”,所以我下面的例子,都不是以定义“类”的形式出现,而是以简单的定义“函数”的形式出现,使代码简单、易懂。
对于 GUI 的设计,我认为是“不要着急,不要着急”。先确保实现主要功能的函数正常运行再来考虑 GUI 也不迟。GUI 的设计主要包括下面三个步骤:
- 创建主窗体
- 创建元件
- 显示元件
- 进入窗体的主循环
下面是一个简单的例子:
import Tkinter
root=Tkinter.Tk() %创建主窗体
MainLabel=Tkinter.Label(root,text="I am so ugly. -- Tkinter",font="Times 16 bold") %创建元件
MainLabel.pack() %显示元件
root.mainloop() %进入窗体的主循环
这个例子中,只包含一个元件是 Label,最后效果就是
根据需要,可以在主窗口中创建多个元件,每一个元件所拥有的参数不同,但是有一点是一样的,就是第一个参数一定是其所属的窗口的变量名。上面的例子中,元件 Label就是隶属于 root,所以在调用 pack 方法之后,这个元件就会在 root 窗口中显示了,只与显示的位置和显示的样子,需要调整 pack方法的参数,我这里使用默认值,所以什么参数也没有,具体 pack 的用法可以参照这里。
这三步其实就是 Tkinter GUI 设计的全部了,剩下的就是每个元件的位置、样子、显示的文字、显示的图像、调用的函数等等元件参数上的设定了,这就涉及到每个元件包含哪些参数、每个参数是什么意思的问题了,这个可以通过查询我提到的那个网站来得到。
在 GUI 设计中有以下几点需要注意(持续更新中):
-
对于元件函数的调用,有两种方式:直接绑定和间接绑定。直接绑定就是在元件 Button 中的参数 command 中设定回调的函数名称,如下:
ButtonInput=Tkinter.Button(root,text="Input",command=InputString)
间接绑定是用 bind 方法,对于所有元件都有这个方法,形式如下:
ButtonInput.bind('<Return>',InputString)
bind 需要指定一种触发方式,这里的触发方式就不再仅仅局限于 Button 的左键单击了,键盘输入、鼠标点击都是可以的,我这里的触发方式是 <Return>,也就是回车。但是要注意的是,bind 是绑定在一个元件上的,也就是说,当焦点在这个元件上的时候触发才有用,而且回调的函数必须要要有一个 event 作为输入参数,如果没有就直接 None 好了,如下:
def InputString(event=None): … … …
这两种方法使用的时候有一个共同需要注意的地方就是,回调函数不要出现参数,下面这种就是错误的:
ButtonInput=Tkinter.Button(root,text="Input",command=InputString())
如果这样,程序就会直接调用函数 InputString(),而不是等到用户点击按钮的时候再调用了,如果一定要向回调的函数中加入参数的话,就用 lambda吧,如下:
ButtonInput=Tkinter.Button(root,text="Input",command=lambda x: InputString(x))
-
显示元件的方法不仅仅有 pack(),还有 grid(),如果元件排列需要很整齐,可以用 grid(),通过输入行数和列数来控制元件的显示位置,两者的比较和使用,可以参见这里。
- 创建主窗口用 Tkinter.Tk(),但是有的时候需要有一些子窗口,可以用 Tkinter.Toplevel() 来创建,保证子窗口一创建出来就在所有窗口的最前面。当运行完某个回调函数需要找回焦点的时候可以用所有元件都有的方法 focus(),来得到某个元件的焦点。
-
窗口退出的时候,可以用方法 quit(),也可以用 destroy(),比如:
root.quit()
-
如果某个元件需要更新,比如图片刷新,可以先调用 destroy() 这个方法摧毁掉这个元件,再将这个元件重建。如果只是 Label 元件的文字上的变动(比如状态栏的文字变化),可以用 StringVar(),需要输入什么直接调用 set() 方法就可以了,如下:
v = Tkinter.StringVar() Tkinter.Label(master, textvariable=v).pack() v.set("New Text!")
-
菜单栏的创建比较麻烦,如下:
from Tkinter import * root = Tk() def hello(): print('hello') def about(): print('我是开发者') menubar = Menu(root) #创建下拉菜单File,然后将其加入到顶级的菜单栏中 filemenu = Menu(menubar,tearoff=0) filemenu.add_command(label="Open", command=hello) filemenu.add_command(label="Save", command=hello) filemenu.add_separator() filemenu.add_command(label="Exit", command=root.quit) menubar.add_cascade(label="File", menu=filemenu) #创建另一个下拉菜单Edit editmenu = Menu(menubar, tearoff=0) editmenu.add_command(label="Cut", command=hello) editmenu.add_command(label="Copy", command=hello) editmenu.add_command(label="Paste", command=hello) menubar.add_cascade(label="Edit",menu=editmenu) #创建下拉菜单Help helpmenu = Menu(menubar, tearoff=0) helpmenu.add_command(label="About", command=about) menubar.add_cascade(label="Help", menu=helpmenu) #显示菜单 root.config(menu=menubar) mainloop()
其中 tearoff=0 就是下拉菜单和窗口是一体的,不能独立形成一个窗口,效果如下:
如果 tearoff=1,则效果如下,点击那个虚线会形成一个新的窗口。
- 简单的提示窗口(比如报错之类的),可以直接调用 tkMessageBox 库,具体用法可以参见这里。简单的数字输入、文件选取、颜色选取,可以直接调用 tkSimpleDialog 库,具体用法可以参见这里
-
Tkinter 自带的下拉框元件 Listbox 很不好用,可以使用 ttk 库中的 Combobox 元件,使用如下:
boudrate=Tkinter.StringVar() boudrate.set("9600") manul_boudrate=ttk.Combobox(root,text=boudrate,values=["2400","9600","12900"])
需要得到 Combobox 中选中的字符串的时候,直接 boudrate.get() 就可以了。