tkinter是Python自带的GUI库。
tkinter的全称是Tk Interface。
其中Tk是开发桌面应用的GUI工具库,它是Tcl的标准GUI,而Tcl全称Tool Command Language,是一种动态编程语言,可用于桌面应用开发。关于Tk和Tcl,可以在https://www.tcl.tk/查看到更多的内容,这里不多做介绍。
Tk和Tcl并不是Python的一部分,Python只不过提供了与Tk和Tcl交互的接口,而Tk和Tcl是系统本身支持的,Window、Linux和MacOS都是支持的,所以通过Python tkinter创建的GUI程序可以在不同的操作系统使用。
通过如下命令可以将tkinter导入到Python:
import tkinter
之后就可以使用了,以下是一个最简单的例子:
import tkinter
if __name__ == '__main__':
root = tkinter.Tk()
root.mainloop()
通过Tk()
就创建了一个窗口组件,mainloop()
表示开始渲染这个窗口。执行该脚本得到的应用:
这其实只是一个什么也没有的框体。为了能够使其有用,还需要在这个窗口组件之上增加额外的组件,来完成需要的功能。这个将在后面详细介绍。
首先对上面的代码做如下的修改:
import tkinter
import tkinter.ttk as ttk
class application(tkinter.Frame):
pass
if __name__ == '__main__':
root = tkinter.Tk()
app = application(master=root)
root.title("Config Editor")
root.geometry("1200x800")
root.mainloop()
这里创建了一个类application
,它的参数是窗口组件,这样在类的初始化过程中就可以同时初始化窗口组件,增加各类组件,完成需要实现的功能。这里的类并不是必要的,之所以要创建一个类,而不是直接在上述代码中实现,是因为最终窗口是有特定的功能需要实现的,通过类的方式来包装能够更好地实现功能。
title()
函数为窗口组件增加名称,会在窗口头部显示(下图红色部分);而geometry()
函数设置了窗口的尺寸,也方便后续增加内容。其执行结果如下:
后续的内容就是在类application
中增加各类组件。
Frame表示的是一个框架组件,在屏幕上显示为一个矩形区域,多用来作为容器。
它的创建也很简单(事实上所有的组件创建都很简单):
fram = tkinter.Frame(root, bg='red', height=30, width=130)
这里通过Frame
类创建框架实例,参数说明如下:
root
表示父组件,通常所有的组件都有一个父组件,而最常用的父组件就是tkinter.Tk()
;bg
、height
、width
,都是可选的,它们规定了框架的属性,比如颜色、高度、长度等,所有Frame支持的属性如下所示:可选项 | 描述 |
---|---|
bg | 框架背景颜色。 |
bd | 框架的宽度,默认为2个像素。 |
cursor | 1、鼠标移动到框架时,光标的形状; 2、可以设置为 arrow ,circle ,cross ,plus 等。 |
height | 框架的高度,默认值是 0。 |
highlightbackground | 框架没有获得焦点时,高亮边框的颜色,默认由系统指定。 |
highlightcolor | 框架获得焦点时,高亮边框的颜色。 |
highlightthickness | 指定高亮边框的宽度,默认值为0不带高亮边框。 |
relief | 1、边框样式; 2、可选的有: FLAT ,SUNKEN ,RAISED ,GROOVE ,RIDGE ;3、默认为 FLAT 。 |
width | 设置框架宽度,默认值是0。 |
takefocus | 指定该组件是否接受输入焦点(用户可以通过Tab键将焦点转移上来),默认为false 。 |
Frame创建之后并不会直接显示,而是需要通过pack()
来布局和显示:
fram.pack(side=tkinter.TOP, anchor=tkinter.SE)
实际上除了使用pack()
来进行布局,还有其它的方法,这里说明:
pack()
:按添加顺序排列组件。grid()
:按行列形式排列组件。place()
:能够实现自定义排列组件。这里只说明pack()
的实现。
pack()
接受一系列的位置参数,说明如下:
可选项 | 描述 |
---|---|
anchor | 1、控制组件在pack() 分配的空间中的位置;2、 "n" ,"ne" ,"e" ,"se" ,"s" ,"sw" ,"w" ,"nw" ,"center" 来定位;3、默认值是 "center" 。 |
expand | 1、指定是否填充父组件的额外空间; 2、默认值是 False 。 |
fill | 1、指定填充pack() 分配的空间;2、默认值是 NONE ,表示保持子组件的原始尺寸;3、还可以使用的值有: "x" (水平填充),"y" (垂直填充)和 "both" (水平和垂直填充)。 |
in_ | 1、将该组件放到该选项指定的组件中; 2、指定的组件必须是该组件的父组件。 |
ipadx | 指定水平方向上的内边距。 |
ipady | 指定垂直方向上的内边距。 |
padx | 指定水平方向上的外边距。 |
pady | 指定垂直方向上的外边距。 |
side | 1、指定组件的放置位置; 2、默认值是 "top" ;3、还可以设置的值有: "left" ,"bottom" ,"right" 。 |
某些参数通过字符串来指定,但是并不会直接使用字符串,而是会用tkinter中预定义的值:
# -anchor and -sticky
N='n'
S='s'
W='w'
E='e'
NW='nw'
SW='sw'
NE='ne'
SE='se'
NS='ns'
EW='ew'
NSEW='nsew'
CENTER='center'
# -fill
NONE='none'
X='x'
Y='y'
BOTH='both'
# -side
LEFT='left'
TOP='top'
RIGHT='right'
BOTTOM='bottom'
所以在代码中会看到tkinter.TOP
这样的值。
最终得到的结果如下:
右侧红色部分就是渲染的结果,可以看到Frame就是一个矩形的图形,它本身意义不大,通常用来作为其它组件的容器。
Label通常是一个带字符串的标签,创建方式如下:
tkinter.Label(fram, text='Text to find:').pack(side=tkinter.LEFT)
通常也是创建和布局两部分,这里直接组合在一起了,显示结果如下:
下面是另外一个例子:
status = tkinter.Label(master, text="Check status here", bd=1, relief=tkinter.SUNKEN, anchor=tkinter.W)
status.pack(side=tkinter.BOTTOM, fill=tkinter.X)
显示结果如下:
Entry也接收两个部分的参数:
可选项 | 描述 |
---|---|
anchor | 1、文本或图像在背景内容区的位置; 2、可选值为 "n" ,"ne" ,"e" ,"se" ,"s" ,"sw" ,"w" ,"nw" ,"center" ;3、默认为 center 。 |
bg | 标签背景颜色。 |
bd | 标签的大小,默认为2个像素。 |
bitmap | 指定标签上的位图,如果指定了图片,则该选项忽略。 |
cursor | 鼠标移动到标签时,光标的形状,可以设置为arrow ,circle ,cross ,plus 等。 |
font | 设置字体。 |
fg | 设置前景色。 |
height | 标签的高度,默认值是0。 |
image | 设置标签图像。 |
justify | 1、定义对齐方式; 2、可选值有: LEFT ,RIGHT ,CENTER ;3、默认为 CENTER 。 |
padx | x轴间距,以像素计,默认1。 |
pady | y轴间距,以像素计,默认1。 |
relief | 1、边框样式; 2、可选的有: FLAT ,SUNKEN ,RAISED ,GROOVE ,RIDGE ;3、默认为 FLAT 。 |
text | 设置文本,可以包含换行符(\n )。 |
textvariable | 1、标签显示Tkinter 变量,StringVar 类型;2、如果变量被修改,标签文本将自动更新。 |
underline | 设置下划线,默认-1,如果设置1,则是从第二个字符开始画下划线。 |
width | 设置标签宽度,默认值是0,自动计算,单位以像素计。 |
wraplength | 设置标签文本为多少行显示,默认为0。 |
Entry表示的是文本框,可以在里面输入文本,并在后台处理。它的创建和布局:
self.edit = tkinter.Entry(fram, width=30)
self.edit.pack(side=tkinter.LEFT, fill=tkinter.BOTH, expand=1, pady=2, padx=(4, 4))
Entry也接收两个部分的参数:
可选项 | 描述 |
---|---|
bg | 输入框背景颜色。 |
bd | 边框的大小,默认为2个像素。 |
cursor | 光标的形状设定,如arrow ,circle ,cross ,plus 等。 |
font | 文本字体。 |
exportselection | 默认情况下,你如果在输入框中选中文本,默认会复制到粘贴板,如果要忽略这个功能刻工艺设置exportselection=0 。 |
fg | 1、文字颜色; 2、值为颜色或颜色代码,如: 'red' ,'#ff0000' 。 |
highlightcolor | 文本框高亮边框颜色,当文本框获取焦点时显示。 |
justify | 显示多行文本的时候,设置不同行之间的对齐方式,可选项包括LEFT ,RIGHT ,CENTER 。 |
relief | 1、边框样式,设置组件3D效果; 2、可选的有: FLAT ,SUNKEN ,RAISED ,GROOVE ,RIDGE ;3、默认为 FLAT 。 |
selectbackground | 选中文字的背景颜色。 |
selectborderwid | 选中文字的背景边框宽度。 |
selectforeground | 选中文字的颜色。 |
show | 指定文本框内容显示为字符,值随意,满足字符即可。如密码可以将值设为show="*" 。 |
state | 默认为state=NORMAL , 文框状态,分为只读和可写,值为:normal/disabled 。 |
textvariable | 文本框的值,是一个StringVar 对象。 |
width | 文本框宽度。 |
xscrollcommand | 设置水平方向滚动条,一般在用户输入的文本框内容宽度大于文本框显示的宽度时使用。 |
执行结果如下:
Entry与前面介绍的组件的差别在于它是可交互的,所以还有额外的事情可以做,比如说当输入内容之后按下Enter键来触发事件:
self.edit.bind("" , (lambda event: self.search_bar()))
这里通过bind()
函数绑定一个事件,该事件回调search_bar()
函数:
def search_bar(self):
print("Input: %s" % self.edit.get())
pass
以上是一个示例函数,它做的事情就是打印获取到的值,执行结果如下:
在Entry文本框中输入内容之后按下Enter键就会在命令行窗口打印文本框中的内容。
另外再介绍一个函数:
self.edit.focus_set()
它设置文本框为焦点,这样打开程序的时候鼠标就会直接在该文本框中,可以直接进行输入。
除了使用Enter键,另外的选择就是在旁边增加按钮,下面将介绍。
Button增加一个按键,创建和布局的方式如下:
butt = tkinter.Button(fram, text='Search', relief=tkinter.GROOVE, command=self.search_bar)
butt.pack(side=tkinter.RIGHT, padx=(4, 4))
Button类的参数如下:
可选项 | 描述 |
---|---|
activebackground | 当鼠标放上去时,按钮的背景色。 |
activeforeground | 当鼠标放上去时,按钮的前景色。 |
bd | 按钮边框的大小,默认为2个像素。 |
bg | 按钮的背景色。 |
command | 按钮关联的函数,当按钮被点击时,执行该函数。 |
fg | 按钮的前景色(按钮文本的颜色)。 |
font | 文本字体。 |
height | 按钮的高度。 |
highlightcolor | 要高亮的颜色。 |
image | 按钮上要显示的图片。 |
justify | 显示多行文本的时候,设置不同行之间的对齐方式,可选项包括LEFT ,RIGHT ,CENTER 。 |
padx | 按钮在x轴方向上的内边距(padding),是指按钮的内容与按钮边缘的距离。 |
pady | 按钮在y轴方向上的内边距(padding)。 |
relief | 1、边框样式,设置组件3D效果; 2、可选的有: FLAT ,SUNKEN ,RAISED ,GROOVE ,RIDGE ;3、默认为 FLAT 。 |
state | 1、设置按钮组件状态; 2、可选的有 NORMAL ,ACTIVE ,DISABLED ;3、默认 NORMAL 。 |
underline | 1、下划线; 2、默认按钮上的文本都不带下划线; 3、取值就是带下划线的字符串索引,为0时,第一个字符带下划线,为1时,前两个字符带下划线,以此类推。 |
width | 按钮的宽度,如未设置此项,其大小以适应按钮的内容(文本或图片的大小)。 |
wraplength | 限制按钮每行显示的字符的数量。 |
text | 按钮的文本内容。 |
anchor | 锚选项,控制文本的位置,默认为中心。 |
本例中的command
指定了在Entry中的search_bar()
函数,所以按键之后该函数也会被回调,结果跟Entry的一致。
执行结果如下:
PanedWindow组件是一个空间管理组件。跟Frame组件类似,都是为组件提供一个框架,不过PanedWindow允许让用户调整应用程序的空间划分。
PanedWindow组件会为每一个子组件生成一个独立的窗格,用户可以自由调整窗格的大小。下面是一个例子:
paned = ttk.Panedwindow(root, orient=tkinter.HORIZONTAL)
paned.pack(fill=tkinter.BOTH, expand=True, padx=(4, 4))
frame_left = ttk.Frame(paned, height=800, relief="groove")
frame_right = ttk.Frame(paned, relief="groove")
paned.add(frame_left, weight=2)
paned.add(frame_right, weight=10)
显示的结果:
红框中通过两个Frame分割了Panedwindow。
Menu用来创建菜单栏。
tkinter提供了三种类型的菜单,分别是:toplevel(主目录菜单),pull-down(下拉式菜单),pop-up(弹出式菜单,或称快捷式菜单)。
创建Menu的方式:
menubar = tkinter.Menu(root)
file_menu = tkinter.Menu(menubar, tearoff=0)
generate_menu = tkinter.Menu(menubar, tearoff=0)
Menu除了接收父组件,还包含一系列的可选参数:
可选项 | 描述 |
---|---|
accelerator | 1、设置菜单项的快捷键,快捷键会显示在菜单项目的右边,比如accelerator = "Ctrl+O" 表示打开;2、注意,此选项并不会自动将快捷键与菜单项连接在一起,必须通过按键绑定来实现。 |
command | 选择菜单项时执行的callback函数。 |
label | 定义菜单项内的文字。 |
menu | 此属性与add_cascade() 方法一起使用,用来新增菜单项的子菜单项。 |
selectcolor | 指定当菜单项显示为单选按钮或多选按钮时选择中标志的颜色。 |
state | 定义菜单项的状态,可以是normal 、active 或disabled 。 |
onvalue/offvalue | 1、默认情况下,variable 选项设置为1表示选中状态,反之设置为0;2、设置 offvalue /onvalue 的值可以自定义未选中状态的值。 |
tearoff | 1、如果此选项为True ,在菜单项的上面就会显示一个可选择的分隔线;2、注意:分隔线会将此菜单项分离出来成为一个新的窗口。 |
underline | 设置菜单项中哪一个字符要有下画线。 |
value | 1、设置按钮菜单项的值 2、在同一组中的所有按钮应该拥有各不相同的值; 3、通过将该值与 variable 选项的值对比,即可判断用户选中了哪个按钮。 |
variable | 当菜单项是单选按钮或多选按钮时,与之关联的变量。 |
本例创建了两个Menu,第一个是父Menu,后面的是子Menu。前者的父Menu组成一组横向的菜单,而后者的子Menu组成下拉的菜单。首先是横向的菜单:
menubar.add_cascade(label="File", menu=file_menu)
menubar.add_cascade(label="Generate", menu=generate_menu)
然后通过root的config()
函数配置root的属性以显示菜单:
root.config(menu=menubar)
得到的结果如下:
不过前面创建的只是空的菜单,下面是添加后续内容:
# Create parent menu.
menubar = tkinter.Menu(root)
# Create child menu.
file_menu = tkinter.Menu(menubar, tearoff=0)
file_menu.add_command(label="Open JSON file...",
command=self.load_from_json)
file_menu.add_command(label="Open binary file...",
command=self.load_from_bin)
generate_menu = tkinter.Menu(menubar, tearoff=0)
generate_menu.add_command(label="Generate binary...",
command=self.generate_bin)
# Add child menu to parent menu.
menubar.add_cascade(label="File", menu=file_menu)
menubar.add_cascade(label="Generate", menu=generate_menu)
# Draw menu.
root.config(menu=menubar)
最终得到的结果:
这里的load_from_json()
等回调函数的实现不在本文的涉及范围,所以这里不作说明。
Treeview组件主要是提供多栏的显示功能。在设计时也可以在左边栏设计成树状结构或是称层次结构,用户可以显示或隐藏任何部分。它可以跟Scrollbar一起使用,用来滚动显示Treeview组件的可见范围。
下面是创建Treeview的示例:
frame_left = ttk.Frame(paned, height=800, relief="groove")
self.left = ttk.Treeview(frame_left, show="tree")
Treeview的可见参数:
可选项 | 描述 |
---|---|
class_ | 部件分类名称,建立后不能改变。 |
columns | 序列的识别码字符串,该序列不包含图标列的识别码,第一列图标列的识别码永远为"#0" 。 |
cursor | 鼠标悬停在按钮上时显示的鼠标,内定为空字符串,继承父部件的选项。 |
displaycolumns | 指定各列显示与否及其顺序,"#all" 代表全部,或整数的列表,识别码列表,各列的内容必须按此顺序提供。 |
height | 部件的行数。 |
padding | 部件内部子部件的外部间隔,可以是单一尺寸,或最多4-tuple,顺序为(left, top, right, bottom) ,省略部份由其它代替,如a=(a, a, a, a) ,(a, b) = (a, b, a, b) 。 |
select | 1、项目被选择的模式; 2、可选项有:‘ browse ’(单选),‘extended ’(可多选),‘none ’(不可选)。 |
show | 1、'tree' 不显示表头显示图标栏;2、 'headings' 不显示图标栏显示表头;3、 'tree headings' 显示表头及图标栏(预设);4、 '' 表头及图标栏都不显示。 |
style | 生成部件的样式。 |
takefocus | 指定该组件是否接受输入焦点(用户可以通过tab键将焦点转移上来),默认为 false 。 |
下面是创建Scrollbar的示例:
self.tree_scroll = ttk.Scrollbar(frame_left, orient="vertical", command=self.left.yview)
Scrollbar的可见参数:
可选项 | 描述 |
---|---|
bg | 设置背景颜色。 |
bd | 指定边框宽度,通常是2像素。 |
cursor | 指定当鼠标在上方飘过的时候的鼠标样式。 |
orient | 指定绘制"horizontal" (垂直滚动条)还是"vertical" (水平滚动条)。 |
highlightbackground | 指定当滚动条没有获得焦点的时候高亮边框的颜色。 |
highlightcolor | 指定当滚动条获得焦点的时候高亮边框的颜色。 |
jump | 指定当用户拖拽滚动条时的行为。 |
relief | 指定边框样式, 默认值是"sunken" 。 |
command | 当滚动条更新时回调的函数 通常的是指定对应组件的xview() 或yview() 方法。 |
takefocus | 指定该组件是否接受输入焦点(用户可以通过 tab 键将焦点转移上来), 默认值是 True 。 |
width | 设置滚动条的宽度, 默认值是16像素。 |
Treeview还包含一些有用的函数:
self.left.configure(yscrollcommand=self.tree_scroll.set)
self.left.bind("<>" , self.on_config_page_select_change)
self.left.bind("" , lambda e: self.in_left.set(True))
self.left.bind("" , lambda e: self.in_left.set(False))
self.left.bind("" , self.on_tree_scroll)
第一个函数configure()
用于设置样式,与前面创建的Scrollbar匹配。
后面的bind()
是绑定时间的,后面的比较好理解,第一个<<>>
需要解释下:
<< TreeviewSelect>>
,代表选择变化是发生;<< TreeviewOpen>>
,item的open=True时发生;<< TreeviewClose>>
,item的open=False时发生。上面的item可以通过Treeview.focus()
和Treeview.selection()
可获取。
最终结果:
注意这里没有对绑定的回调函数进行说明,这不在本文的讨论范围内。不过还需要介绍如何往Tree中增加内容,其使用的函数是:
self.left.insert('', 'end', text=key)
insert()
函数接收如下的参数:
parent
:对于表格类型的Treeview,parent一般为空。对于树形类型的Treeview,parent为父节点;index
:指明在何处插入新的项;可以是'end'
,也可以是数字;比如,如果要让新插入的项成为第一个子节点或者在第一行,index就设为0;如果是第二个子节点或者第二行,就是设置index为1;如果在最末端插入项,就设置index='end'
;iid
:如果没有赋值,就使用系统自动生成的id;如果输入了id,必须保证与现有的id不重复;否则系统会自动生成id;**kw
:设置插入项的属性,支持的属性有:可选项 | 描述 |
---|---|
image | 显示图像。 |
open | 针对树形结构,确认插入的项是打开还是折叠状态。True 打开,False 为折叠。 |
tags | 为新插入的项设置tag标签。 |
text | 显示文字。 |
values | 在表格结构中,要显示的数值;这些数值是按照逻辑结构赋值的,也就是按照columns设定的列次序来赋值;如果输入的个数少于columns指定列数,则插入空白字符串。 |
得到的示例结果如下:
Canvas属于tkinter,就是画布,可以将图形,文本,小部件或框架放置在画布上。
创建画布的方式:
self.conf_canvas = tkinter.Canvas(frame_right, highlightthickness=0)
第一个参数是父组件,第二个参数是可选的,其可选值如下:
可选项 | 描述 |
---|---|
bd | 边框宽度,单位像素,默认为2像素。 |
bg | 背景色。 |
confine | 如果为 True (默认),画布不能滚动到可滑动的区域外。 |
cursor | 光标的形状设定,如arrow ,circle ,cross ,plus 等。 |
height | 高度。 |
highlightcolor | 要高亮的颜色。 |
relief | 边框样式,可选值为FLAT ,SUNKEN ,RAISED ,GROOVE ,RIDGE 。 默认为FLAT 。 |
scrollregion | 一个元组(w, n, e, s) ,定义了画布可滚动的最大区域,w 为左边,n 为头部,e 为右边,s 为底部。 |
width | 画布在x坐标轴上的大小。 |
xscrollincrement | 用于滚动请求水平滚动的数量值。 |
xscrollcommand | 水平滚动条,如果画布是可滚动的,则该属性是水平滚动条的set() 方法。 |
yscrollincrement | 类似xscrollincrement,但是垂直方向。 |
yscrollcommand | 垂直滚动条,如果画布是可滚动的,则该属性是垂直滚动条的set() 方法。 |
Canvas也可以跟Scrollbar绑定:
self.page_scroll = ttk.Scrollbar(frame_right, orient="vertical", command=self.conf_canvas.yview)
self.conf_canvas.configure(yscrollcommand=self.page_scroll.set)
Canvas最重要的是一系列的create_xx()
函数,比如下面的代码创建了一个组件:
self.conf_canvas.create_window(0, 0, window=self.right_grid, anchor='nw')
Canvas也支持bind()
函数:
self.conf_canvas.bind('' , lambda e: self.in_right.set(True))
self.conf_canvas.bind('' , lambda e: self.in_right.set(False))
self.conf_canvas.bind("" , self.on_canvas_configure)
self.conf_canvas.bind_all("" , self.on_page_scroll)
用于打开一个选择文件的窗口:
self.last_dir = '.'
path = filedialog.askopenfilename(
initialdir=self.last_dir,
title="Load file",
filetypes=(("%s files" % file_type, "*.%s" % file_ext), ("all files", "*.*"))
)
执行之后,选择“File->Open JSON file…”,结果如下:
选择之后就得到path可以在代码中使用该文件。