1. TK框架GUI编程的基本思路
首先我们来明确几个单词以明确Gui编程语境下的基本上下文环境.
窗口(window)
就是在屏幕上打开一个窗口,GUI的最上层单元,实际上GUI程序就是一个循环,平时一直空转等待
事件
,当发生事件后就由回调函数
执行对应操作. 窗口打开程序执行,窗口关闭程序退出.框架(Frame)
就是屏幕上的一块矩形区域,多是用来作为容器(container)来布局窗口
控件(widget)
就是具体的可操作可交互块,比如按钮,文本框,包括窗口,帧等都是.
事件(event)
指的是一个用于改变控件/帧/窗口等对象状态的触发信号
回调函数(callback)
指的是在外部定义好的控件/帧/窗口等在接收到事件后处理事件的方法.
网格(grid)
指的是堆放控件的一种常用方式,控件可摆放的位置按从上到下,从左到右的顺序获得一个二维的编号,组件按照编号放置在网格对应的位置上.
像素(pixel)
显示设备上用于显示图像的最小单位,每个像素就是一个发光点,GUI的图形本质上就是这些点的组合.
1.1. GUI编程的基本架构模式
通常gui编程就是将模块的接口使用gui组合包装起来,在不同的情况下调用不同的接口从而达到方便用户使用的目的.因此GUI编程基本可以使用mvc
结构进行分解,即:
- Model 模型,也就是业务模型,实际业务的内容
- View 视图,也就是GUI中的图形组合
- Controller 控制器,也就是连接视图与模型的组件,通常使用事件模型传递控制信息.
这3层相互独立,同常通过接口进行交互,但通常使用Tk的时候Controller作为application的住体,model和view则是作为主体的嵌入.model部分本文不会多做描述,而gui编程更加关心的是视图和控制器部分.
对MVC结构更多的理解可以看一些其他的书籍,推荐阮一峰先生的这篇博客,讲的浅显易懂,每次看都可以有新的收获.
1.2. 视图部分
视图部分主要是管用户实际看到的内容,因此除了堆砌组件外,设计也是一门技术活.视图也就基本可以分为组件和布局两个部分.
像C#
,QT
这种库一个最方便的地方是可以通过工具所见即所得的组织组件,python自带的Tkinter并没有提供这个工具.但其实是有一个类似工具的http://page.sourceforge.net/#Download,
它的安装需要先安装了TCL
环境(需翻墙)activetcl,不过使用工具生成的代码会比较难以维护,还是更加推荐手动编写,毕竟很简单.
1.2.1. 视图的组件
在python标准库中有3个库提供了tk的控件,分别是
- tkinter
tk的基本库,提供的控件包括:
控件名类型 | 意义 |
---|---|
Toplevel | 顶层帧 |
Frame | 框架 |
LabelFrame | 标签框架 |
Button | 按钮 |
Canvas | 画板 |
Checkbutton | 复选框 |
Entry | 输入框 |
Label | 标签 |
Listbox | 列表框 |
Menu | 菜单栏 |
Menubutton | 菜单按钮 |
Message | 信息栏 |
OptionMenu | 选项菜单 |
PanedWindow | 中分栏窗口 |
Radiobutton | 单选框 |
Scale | 滑块 |
Scrollbar | 滚动条 |
Spinbox | 指定输入范围值的输入框 |
Text | 文本框 |
- tkinter.ttk
一个tk的扩展库,提供了更多的样式和控件以及一些控件的美化版本. 优化控件包括:
控件名 | 说明 |
---|---|
Button | 按钮 |
Checkbutton | 复选框 |
Entry | 输入框 |
Frame | 框架 |
Label | 标签 |
LabelFrame | 标签框架 |
Menubutton | 菜单按钮 |
PanedWindow | 中分栏窗口 |
Radiobutton | 单选框 |
Scale | 滑块 |
Scrollbar | 滚动条 |
新增的控件包括:
控件名 | 说明 |
---|---|
Combobox | 组合框,包含文本字段和一个包含可选值的下拉列表 |
Notebook | 标签页,形式参见chrome中的标签页 |
Progressbar | 进度条 |
Separator | 分离器,显示一个水平或垂直分隔条 |
Sizegrip | 控制TopLevel的窗口大小 |
Treeview | TreeView控件显示一个项目的树状分层集合 |
- tkinter.scrolledtext
单独的带滚动条的文本框,集成了文本框和滚动条,调用更加方便些
另外还有两个第三方库可以使用,不过由于tk模块年深日久,第三方扩展也历史久远,现在只能直接下载源码使用python setup.py install
安装.
- Python megawidgets提供了更多组件的第三方库
- pandastable提供了对表格的支持.
各个控件的接口可以查看这份文档
控件的基本设置方式
在tkinter中每个控件都有一个configure()
方法用于设置,设置configure
的参数可能各不相同,但其设置方式都是一致的,就是
- 使用关键字在初始化控件时设置
- 调用控件对象的
configure()
方法设置
ttk的控件使用方式和tkinter一致,但设置方式使用ttk.Style()
进行全局设置,而非直接在单独的控件中设置.
比如同样是设置label,tkinter
中设置方式如下:
l1 = tkinter.Label(text="Test", fg="black", bg="white") l2 = tkinter.Label(text="Test", fg="black", bg="white")
而ttk中如下:
style = ttk.Style() style.configure("BW.TLabel", foreground="black", background="white") l1 = ttk.Label(text="Test", style="BW.TLabel") l2 = ttk.Label(text="Test", style="BW.TLabel")
控件的组合方式
用过图像编辑软件的读者一定知道图层
这一概念,图片是一个一个的图层堆叠组合而成.我们的GUI也是一层一层堆叠而成,最底层必须是TopLevel
这个有点类似于画板,通常在其上我们会放上一个帧,这有点像刷个底色图层,之后就是在这个帧上放置其他组件了.最终的gui就是由所有这些组件构成的一颗组件树.
每个组件初始化的第一参数都是它的父级组件,我们可以使用
第一个tkinter程序
%%writefile src/first.py import tkinter tkinter._test()
Overwriting src/first.py
%exec_py src/first.py
1.2.2. 布局思路
有3种方式可以为控件布局:
- pack()
- grid()
- place()
pack()
pack()默认会一个一个从上往下堆叠,但同样也可以接受几个参数
- side (left,top,right,bottom)
- fill ( X,Y,BOTH 和 NONE)水平方向填充,竖直方向填充,水平和竖直方向填充和不填充。
- expand 参数可以是 YES 和 NO
- anchor (n, ne, e, se, s, sw, w, nw, or center)NESW 表示上右下左以及他们的组合或者是 CENTER(表示中 间)。
- ipadx 表示的是内边距的 x 方向
- ipady 表示 的是内边距的 y 方向
- padx 表示的是外边距的 x 方向
- pady 表示的是外边距的 y 方向
%%writefile src/pack.py from tkinter import Frame,Label,Button class Application(Frame): def __init__(self, master=None): Frame.__init__(self, master) self.pack() self.createWidgets() def createWidgets(self): self.LabelL = Label(self, text='左边') self.LabelL.pack(side="left") self.LabelR = Label(self, text='右边') self.LabelR.pack(side="right") self.LabelT = Label(self, text='顶') self.LabelT.pack(side="top") self.LabelB = Label(self, text='底') self.LabelB.pack(side="bottom") self.LabelN = Label(self, text='N') self.LabelN.pack(anchor="n") self.LabelE = Label(self, text='E') self.LabelE.pack(anchor="e") self.LabelS = Label(self, text='S') self.LabelS.pack(anchor="s") self.LabelW= Label(self, text='W') self.LabelW.pack(anchor="w") self.LabelCENTER= Label(self, text='CENTER') self.LabelCENTER.pack(anchor="center") self.quitButton = Button(self, text='Quit',background="red", command=self.quit) self.quitButton.pack(side="bottom") if __name__ =="__main__": app = Application() # 设置窗口标题: app.master.title('pack布局测试') #窗口大小位置 #app.master.geometry("600x400+100+400")#长x宽+x+y # 主消息循环: app.mainloop()
Overwriting src/pack.py
%exec_py src/pack.py
grid() 最常用布局
grid()里的参数:
- row 表示行(从0开始)
- column 表示列(从0开始)
- sticky (N,E,S,W) 表 示上右下左,它决定了这个组件是从哪个方向开始的
- ipadx,ipady,padx,pady,它们 的意思和 pack 函数是一样的,默认边距是 0。
- rowspan 表示跨越的行数 columnspan 表示跨越的列数。
%%writefile src/grid.py from tkinter import Frame,Label,Button class Application(Frame): def __init__(self, master=None): Frame.__init__(self, master) self.pack() self.createWidgets() def createWidgets(self): self.Label00 = Label(self, text='00') self.Label00.grid(row=0,column=0) self.Label10 = Label(self, text='10') self.Label10.grid(row=1,column=0) self.Label11 = Label(self, text='11') self.Label11.grid(row=1,column=1) self.Label30 = Label(self, text='30') self.Label30.grid(row=3,column=0) self.quitButton = Button(self, text='Quit',background="red", command=self.quit) self.quitButton.grid(row=2,column=2) if __name__ =="__main__": app = Application() # 设置窗口标题: app.master.title('grid布局测试') #窗口大小位置 #app.master.geometry("600x400+100+400")#长x宽+x+y # 主消息循环: app.mainloop()
Overwriting src/grid.py
style = ttk.Style() style.configure("BW.TLabel", foreground="black", background="white") l1 = ttk.Label(text="Test", style="BW.TLabel") l2 = ttk.Label(text="Test", style="BW.TLabel")
0
1.3. 控制部分
Tkinter的控制部分是依靠为控件绑定事件和回调函数来实现的
1.3.1. 事件
事件绑定和解绑 bind() unbind()
绑定bind()
bind 函数的调用规则:
style = ttk.Style() style.configure("BW.TLabel", foreground="black", background="white") l1 = ttk.Label(text="Test", style="BW.TLabel") l2 = ttk.Label(text="Test", style="BW.TLabel")
1
事件类型:
事件类型是一组字符串,它采用的描述方式是这样的:
style = ttk.Style() style.configure("BW.TLabel", foreground="black", background="white") l1 = ttk.Label(text="Test", style="BW.TLabel") l2 = ttk.Label(text="Test", style="BW.TLabel")
2
- MODIFIER 即修饰符,它的全部取值如下:
Control | Mod1, M1, Command |
---|---|
Alt | Mod2, M2, Option |
Shift | Mod3, M3 |
Lock | Mod4, M4 |
Extended | Mod5, M5 |
Button1, B1 | Meta, M |
Button2, B2 | Double |
Button3, B3 | Triple |
Button4, B4 | Quadruple |
Button5, B5 | --- |
- TYPE 表示类型,它的全部取值如下:
Activate | Destroy | Map |
---|---|---|
ButtonPress, Button | Enter | MapRequest |
ButtonRelease | Expose | Motion |
Circulate | FocusIn | MouseWheel |
CirculateRequest | FocusOut | Property |
Colormap | Gravity | Reparent |
Configure | KeyPress, Key | ResizeRequest |
ConfigureRequest | KeyRelease | Unmap |
Create | Leave | Visibility |
Deactivate | --- | --- |
- DETAIL 表示细节,其实也就是对第二个参数的一些辅助说明。
常用事件类型:
<Button-1>
表示鼠标左键单击,其中的 1 换成 3 表示右 键被单击,为 2 的时候表示鼠标中键<KeyPress-A>
表示 A 键被按下,其中的 A 可以换成其他的键位。<Control-V>
表示按下的是 Ctrl 和 V 键,V 可以换成其他 键位。<F1>
表示按下的是 F1 键,对于 Fn 系列的,都可以随便 换。
更加具体的可以看http://www.tcl.tk/man/tcl8.5/TkCmd/bind.htm#M23
bind()可以绑定所有继承自Misc类的组件,也就是说即便是标签也可以绑定动作.
bind()有两个扩展
窗体对象.bind_all(事件类型,回调函数)全程序级别绑定事件
窗体对象.bind_class(类名,事件类型,回调函数)类级别绑定事件,比如所有标签这样
解绑 unbind()
窗体对象.unbind(事件类型)
我们看一个相对全面一些的例子:记事本
style = ttk.Style() style.configure("BW.TLabel", foreground="black", background="white") l1 = ttk.Label(text="Test", style="BW.TLabel") l2 = ttk.Label(text="Test", style="BW.TLabel")
3
style = ttk.Style() style.configure("BW.TLabel", foreground="black", background="white") l1 = ttk.Label(text="Test", style="BW.TLabel") l2 = ttk.Label(text="Test", style="BW.TLabel")
4
style = ttk.Style() style.configure("BW.TLabel", foreground="black", background="white") l1 = ttk.Label(text="Test", style="BW.TLabel") l2 = ttk.Label(text="Test", style="BW.TLabel")
5
1.3.2. 使用xxxcommand设置绑定默认事件的回调
另一种方式是使用xxxcommand
绑定默认事件,这个可以看作是官方给的语法糖,多数时候不同的控件会用到的事件也就一两种,官方为其提供了通过简单设置config
就可以绑定好回调的方法.比如按钮
就可以直接设置Button(self, text='Quit',fg="red", command=self.quit)
绑定退出方法.
1.4. 事件循环
从tk的执行方式上就可以很容易的看出,TK使用的是事件循环来执行GUI行为.在前文中我们介绍过事件循环的原理,TK太老了,到现在依然还没有支持异步协程的写法,倒是有个第三方演示项目asyncio-tkinter.但无论怎么样,事件循环不可以被阻塞,因此通常gui会结合多线程/多进程来执行model部分的逻辑.
在前文中我们也已经介绍过python的多进程/多线程工具,本人还是更加推荐使用concurrent.futures
中的执行器,通过池来执行,由于submit
后返回的是Future
对象,可以使用这个对象的各种接口获取其状态,在执行出结果后有两种方式刷新gui
- 使用
Future
对象的add_done_callback(fn)
接口在执行完后直接进行更新 - 通过
root.after(time,callback)
接口每隔一段时间检测下Future
的状态.
还没有评论,来说两句吧...