Bootstrap

【Python应用】tkinter简介

简介

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

Frame表示的是一个框架组件,在屏幕上显示为一个矩形区域,多用来作为容器。

它的创建也很简单(事实上所有的组件创建都很简单):

fram = tkinter.Frame(root, bg='red', height=30, width=130)

这里通过Frame类创建框架实例,参数说明如下:

  • root表示父组件,通常所有的组件都有一个父组件,而最常用的父组件就是tkinter.Tk()
  • 之后的参数,比如bgheightwidth,都是可选的,它们规定了框架的属性,比如颜色、高度、长度等,所有Frame支持的属性如下所示:
可选项描述
bg框架背景颜色。
bd框架的宽度,默认为2个像素。
cursor1、鼠标移动到框架时,光标的形状;
2、可以设置为arrowcirclecrossplus 等。
height框架的高度,默认值是 0。
highlightbackground框架没有获得焦点时,高亮边框的颜色,默认由系统指定。
highlightcolor框架获得焦点时,高亮边框的颜色。
highlightthickness指定高亮边框的宽度,默认值为0不带高亮边框。
relief1、边框样式;
2、可选的有:FLATSUNKENRAISEDGROOVERIDGE
3、默认为FLAT
width设置框架宽度,默认值是0。
takefocus指定该组件是否接受输入焦点(用户可以通过Tab键将焦点转移上来),默认为false

Frame创建之后并不会直接显示,而是需要通过pack()来布局和显示:

fram.pack(side=tkinter.TOP, anchor=tkinter.SE)

实际上除了使用pack()来进行布局,还有其它的方法,这里说明:

  • pack():按添加顺序排列组件。
  • grid():按行列形式排列组件。
  • place():能够实现自定义排列组件。

这里只说明pack()的实现。

pack()接受一系列的位置参数,说明如下:

可选项描述
anchor1、控制组件在pack()分配的空间中的位置;
2、"n""ne""e""se""s""sw""w""nw""center"来定位;
3、默认值是"center"
expand1、指定是否填充父组件的额外空间;
2、默认值是False
fill1、指定填充pack()分配的空间;
2、默认值是NONE,表示保持子组件的原始尺寸;
3、还可以使用的值有:"x"(水平填充),"y"(垂直填充)和 "both"(水平和垂直填充)。
in_1、将该组件放到该选项指定的组件中;
2、指定的组件必须是该组件的父组件。
ipadx指定水平方向上的内边距。
ipady指定垂直方向上的内边距。
padx指定水平方向上的外边距。
pady指定垂直方向上的外边距。
side1、指定组件的放置位置;
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

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也接收两个部分的参数:

  • 父组件;
  • 可选参数,其可选值如下:
可选项描述
anchor1、文本或图像在背景内容区的位置;
2、可选值为"n""ne""e""se""s""sw""w""nw""center"
3、默认为center
bg标签背景颜色。
bd标签的大小,默认为2个像素。
bitmap指定标签上的位图,如果指定了图片,则该选项忽略。
cursor鼠标移动到标签时,光标的形状,可以设置为arrowcirclecrossplus等。
font设置字体。
fg设置前景色。
height标签的高度,默认值是0。
image设置标签图像。
justify1、定义对齐方式;
2、可选值有:LEFTRIGHTCENTER
3、默认为CENTER
padxx轴间距,以像素计,默认1。
padyy轴间距,以像素计,默认1。
relief1、边框样式;
2、可选的有:FLATSUNKENRAISEDGROOVERIDGE
3、默认为FLAT
text设置文本,可以包含换行符(\n)。
textvariable1、标签显示Tkinter变量,StringVar类型;
2、如果变量被修改,标签文本将自动更新。
underline设置下划线,默认-1,如果设置1,则是从第二个字符开始画下划线。
width设置标签宽度,默认值是0,自动计算,单位以像素计。
wraplength设置标签文本为多少行显示,默认为0。

Entry

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光标的形状设定,如arrowcirclecrossplus等。
font文本字体。
exportselection默认情况下,你如果在输入框中选中文本,默认会复制到粘贴板,如果要忽略这个功能刻工艺设置exportselection=0
fg1、文字颜色;
2、值为颜色或颜色代码,如:'red''#ff0000'
highlightcolor文本框高亮边框颜色,当文本框获取焦点时显示。
justify显示多行文本的时候,设置不同行之间的对齐方式,可选项包括LEFTRIGHTCENTER
relief1、边框样式,设置组件3D效果;
2、可选的有:FLATSUNKENRAISEDGROOVERIDGE
3、默认为FLAT
selectbackground选中文字的背景颜色。
selectborderwid选中文字的背景边框宽度。
selectforeground选中文字的颜色。
show指定文本框内容显示为字符,值随意,满足字符即可。如密码可以将值设为show="*"
state默认为state=NORMAL, 文框状态,分为只读和可写,值为:normal/disabled
textvariable文本框的值,是一个StringVar对象。
width文本框宽度。
xscrollcommand设置水平方向滚动条,一般在用户输入的文本框内容宽度大于文本框显示的宽度时使用。

执行结果如下:

在这里插入图片描述

Entry与前面介绍的组件的差别在于它是可交互的,所以还有额外的事情可以做,比如说当输入内容之后按下Enter键来触发事件:

self.edit.bind("<Return>", (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

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显示多行文本的时候,设置不同行之间的对齐方式,可选项包括LEFTRIGHTCENTER
padx按钮在x轴方向上的内边距(padding),是指按钮的内容与按钮边缘的距离。
pady按钮在y轴方向上的内边距(padding)。
relief1、边框样式,设置组件3D效果;
2、可选的有:FLATSUNKENRAISEDGROOVERIDGE
3、默认为FLAT
state1、设置按钮组件状态;
2、可选的有NORMALACTIVEDISABLED
3、默认NORMAL
underline1、下划线;
2、默认按钮上的文本都不带下划线;
3、取值就是带下划线的字符串索引,为0时,第一个字符带下划线,为1时,前两个字符带下划线,以此类推。
width按钮的宽度,如未设置此项,其大小以适应按钮的内容(文本或图片的大小)。
wraplength限制按钮每行显示的字符的数量。
text按钮的文本内容。
anchor锚选项,控制文本的位置,默认为中心。

本例中的command指定了在Entry中的search_bar()函数,所以按键之后该函数也会被回调,结果跟Entry的一致。

执行结果如下:

在这里插入图片描述

Panedwindow

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

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除了接收父组件,还包含一系列的可选参数:

可选项描述
accelerator1、设置菜单项的快捷键,快捷键会显示在菜单项目的右边,比如accelerator = "Ctrl+O"表示打开;
2、注意,此选项并不会自动将快捷键与菜单项连接在一起,必须通过按键绑定来实现。
command选择菜单项时执行的callback函数。
label定义菜单项内的文字。
menu此属性与add_cascade()方法一起使用,用来新增菜单项的子菜单项。
selectcolor指定当菜单项显示为单选按钮或多选按钮时选择中标志的颜色。
state定义菜单项的状态,可以是normalactivedisabled
onvalue/offvalue1、默认情况下,variable选项设置为1表示选中状态,反之设置为0;
2、设置offvalue/onvalue的值可以自定义未选中状态的值。
tearoff1、如果此选项为True,在菜单项的上面就会显示一个可选择的分隔线;
2、注意:分隔线会将此菜单项分离出来成为一个新的窗口。
underline设置菜单项中哪一个字符要有下画线。
value1、设置按钮菜单项的值
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组件主要是提供多栏的显示功能。在设计时也可以在左边栏设计成树状结构或是称层次结构,用户可以显示或隐藏任何部分。它可以跟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)
select1、项目被选择的模式;
2、可选项有:‘browse’(单选),‘extended’(可多选),‘none’(不可选)。
show1、'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("<<TreeviewSelect>>", self.on_config_page_select_change)
self.left.bind("<Enter>",  lambda e: self.in_left.set(True))
self.left.bind("<Leave>",  lambda e: self.in_left.set(False))
self.left.bind("<MouseWheel>",  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

Canvas属于tkinter,就是画布,可以将图形,文本,小部件或框架放置在画布上。

创建画布的方式:

self.conf_canvas = tkinter.Canvas(frame_right, highlightthickness=0)

第一个参数是父组件,第二个参数是可选的,其可选值如下:

可选项描述
bd边框宽度,单位像素,默认为2像素。
bg背景色。
confine如果为 True(默认),画布不能滚动到可滑动的区域外。
cursor光标的形状设定,如arrowcirclecrossplus 等。
height高度。
highlightcolor要高亮的颜色。
relief边框样式,可选值为FLATSUNKENRAISEDGROOVERIDGE。 默认为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('<Enter>',  lambda e: self.in_right.set(True))
self.conf_canvas.bind('<Leave>',  lambda e: self.in_right.set(False))
self.conf_canvas.bind("<Configure>", self.on_canvas_configure)
self.conf_canvas.bind_all("<MouseWheel>", self.on_page_scroll)

filedialog

用于打开一个选择文件的窗口:

    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可以在代码中使用该文件。

;