故事的起因在于和一个朋友的聊天,他拜托我帮他看看一个系统的操作界面代码,然后我就想啊,能不能干脆我自己来写一个简单的操作界面呢?
工欲善其事,必先利其器。因此,这里我们先尝试基于莫烦python中的可视化界面写作教程来熟悉一下python中的可视化界面写作库tkinter。
所有的代码实现我们会放置到我们的GitHub仓库当中,仓库链接如下:
BTW:
又及:
首先,我们来看一下使用python中的tkinter库进行可视化界面编程的一般流程。
他有点像是使用matplotlib进行绘图的方式,首先创建一个基础画布(基础的窗口),然后定义不同的绘图曲线(窗口插件),然后将定义的曲线(窗口插件)放置到画布(窗口)上进行显示。
因此,这里,我们先使用一个简单的显示窗口来走一遍整个流程,介绍一下窗口创建的流程以及插件安放的方法。
这里,我们给出基于tkinter写作可视化界面的最基础代码如下。
我们的目标是创建一个窗口,然后在上面放上一个显示面板,显示面板中打印出hello world
文本。
这个功能算是一个最为基础的可视化界面写作了。
我们给出代码实现如下:
import tkinter
class Window:
def __init__(self):
self.window = self.build_window()
self.add_panel()
def build_window(self):
window = tkinter.Tk() # 创建窗口实例
window.title("user interface") # 设置窗口名称
window.geometry("400x300") # 设置窗口大小
return window
def add_panel(self):
self.panel = tkinter.Label(self.window, text="hello world!", bg="red", font=("Arial", 12), width=20, height=4) # 创建显示面板
self.panel.pack() # 将显示面板放置到窗口当中
def run(self):
self.window.mainloop()
return
if __name__ == "__main__":
window = Window()
window.run()
可以看到:
tkinter.Tk()
进行实例化,而后我们只需要在其中加入组件就行了;title
函数进行窗口命名;geometry
方法进行窗口大小设置。窗口类包含的主要方法包括:
mainloop()
:启动窗口,并循环更新状态;quit()
:关闭窗口;Tk
类事实上还包含有大量其他的方法,但是文档中没有很好的说明,而且似乎也并不常用,有兴趣的读者可以直接阅读源码进行学习,这里就请容我直接跳过了。
如前,我们事实上已经初步看到了界面中组件的使用方法,它基本包含两个步骤:
而要将组件加载到界面当中,我们只需要使用使用pack()
方法即可。
pack()
方法的一些常用参数定义如下:
anchor
: 组件的定位核心锚点定义;side
: 组件的安放位置;更多相关的定义可以直接查看源码,源码路径如下:
最后,我们来看一下组件位置的调整方法。
除了在定义过程中进行位置指定之外,我们也可以使用place()
和grid()
两个方法进行组件位置的设置。
我们来分别考察一下这两个函数:
place(x, y, anchor="nw")
nw
)grid(row, column, padx, pady)
这里,**比较建议使用place()
**方法。
grid()
方法的坑还是蛮多的,包括但不限于:
(0,0)
坐标上,因为起始点就是当前坐标;(0,0)
到(2,2)
,grid不会自动去计算间隔,而是非常粗暴地将其视作(1,1)
进行处理,即按照坐标往后平移一个位置,也就是说,坐标点相互之间只有相对关系,而不代表绝对关系;另外,需要特别注意的是:
place
以及grid
方法必须在组件已经配置到窗口界面之后才会起效,即其必须写在pack
函数之后!下面,在介绍完了整体的可视化界面的使用方法之后,我们就来考察一下可以使用在界面中的具体组件。
结合上面介绍的组件使用方法,我们就可以编写一些基本的gui界面了。
tkinter中的所有变量事实上都是tk.Variable
类的一个子类,和C语言中的参数定义没啥区别,就是实现申明一个变量,然后进行赋值和获取。
所有的变量都包含下述两个方法:
set()
:设置变量的值;get()
:获取变量的值;下面,我们给出tkinter的变量类型如下:
default=""
default=0
default=0.0
default=False
Label组件有点类似于labview里面的indicator组件,作用是对某个定义好的参数或者常量进行显示,他不提供交互功能,仅仅提供显示功能。
上面,事实上我们已经使用过了,tkinter中的显示窗口定义方式就是实例化一个Label类。
我们给出其定义如下:
label = tk.Label(window, text="hello world", bg="red", font=("Arial", 12), width=15, height=3)
label.pack()
其中,text用于表示显示的文本,如果是一个常量,那么直接使用text进行赋值即可,如果是一个变量,则需要通过一个StringVar
类进行间接赋值。
代码语言如下:
var = tk.StringVar(value="hello world")
component = tk.Label(window, textvariable=var, **kwargs)
它的好处在于可以通过var.set()
方法对需要显示的内容进行调整。
另外,font是字体设置,而width和height是组件的长宽设置参数,他们的单位和像素的对应关系目前还没有一个比较确定的映射关系,不过经过尝试总结,有像素对应关系如下:
W = w i d t h × 10 + δ 1 H = h e i g h t × 20 + δ 2 \begin{aligned} W &= width \times 10 + \delta_1\\ H &= height \times 20 + \delta_2 \end{aligned} WH=width×10+δ1=height×20+δ2
Entry组件为一个字符输入控制插件,他的作用是可以让用户在给定的输入框当中输入文本,然后通过get
内置函数就能够获取用户的输入。
Entry组件使用的典型代码如下:
entry = tk.Entry(window)
entry.pack()
而后,用户就可以在界面上的窗口中进行输入,我们通过entry.pack()
方法就可以获取用户的输入内容了。
需要注意的是,Entry
组件在定义是可以通过设置show
参数将所有的输入显示全部转换为指定的特殊字符,例如:
entry = tk.Entry(window, show="*")
entry.pack()
此时所有的输入字符在界面上的显示都会被替换为*
字符,特别适合于密码框的设置。
Text组件算是结合了Label组件与Entry组件,它既可以允许用户直接对其中的字符串变量进行编辑,也可以对结果进行显示。
同样给出Text组件的典型使用方法如下:
text = tk.Text(window, width=20, height=2)
text.pack()
Text类的典型方法如下:
get(self, index1, index2=None)
s[index1:index2]
。line.column
格式,具体而言,0.0
表示第0行第0列;insert
:当前光标所处的位置;end
:文本末端;insert(self, index, chars)
index
位置插入字符串chars
。Message组件和Label组件基本是完全一样的,不过Label组件的显示框的长宽是一开始就定义好的,而Message组件的长宽则会根据输入文本的长度进行自适应的调整。
给出Message组件的典型使用代码如下:
message = tk.Label(window, text="hello world", bg="red", font=("Arial", 12), width=200)
message.pack()
需要注意的是,Message组件的的width定义单位为像素点,而且它指定的是最大的宽度,当文本宽度超过最大宽度时,文本会自动换行。
tkinter中的Button组件事实上等价于LabVIEW当中的bool按键,它的基本用法如下:
button = tk.Button(window, text="Ok", width=10, height=2, command=fn)
button.pack()
其中,前面一些参数都好理解,唯一的差一点在于最后一个command参数,他的输入为一个行为函数fn
,他的含义为触发函数,当点击行为发生时,就会触发行为函数fn
,需要注意的是,他的行为发生方式为下沿触发,即当点击被释放时,触发行为函数。
tkinter中的Radiobutton组件同样是一个bool按键的组件,直接给出他的常用代码方法如下:
var = tk.StringVar()
radio_button = tk.Radiobutton(window, text='Option A', variable=var, value='A', command=fn)
radio_button.pack()
这个插件比较坑爹,他的功能事实上就是每一次点击这个bool按键,就将variable赋值为value,如果variable与value相同,那么显示就是勾选,反之就是没有勾选。
因此,如果只有一个选项,我们就无法完成撤销操作,然后就比较呵呵了。
这个插件的一个典型用法就是作为单选题的实现。
同样,我们给出checkbutton的组件使用基础代码如下:
var = tk.BooleanVar(value=False)
button = tk.Radiobutton(window, text='on', variable=var, onvalue=True, offvalue=False, command=fn)
button.pack()
这一插件的典型用法为bool按键,它可以控制bool按键的状态为开还是关,每一次点击都会进行一次状态转换(开→关/关→开),然后触发一次行为函数fn
。
另一些常用的输入组件是enum类型的输入组件,他们的输入是受到限制的,往往都是enum类型,这里,我们就来看一下这一类的插件的使用方法。
同样,我们给出Listbox组件的使用典型代码样例如下:
var = tk.StringVar()
var.set(["A", "B", "C", "D"])
listbox = tk.Listbox(window, listvariable=self.var, **kwargs)
listbox.pack()
我们可以通过listbox.curselection()
方法获取当前光标所处的选项index,然后通过listbox.get(index)
方法我们即可获得用户的选项内容。
需要注意的是,当用户没有选择时,listbox.curselection()
返回为一个空tuple。
Optionmenu组件和Listbox组件在功能上事实上差不多,但是前者是平铺式的将所有可选项全部显示出来,而Optionmenu则是通过一个折叠窗口将选项进行了隐藏。
同样的,我们给出Optionmenu的典型使用方法如下:
var = tk.StringVar()
optionList = ["A", "B", "C", "D"]
om = tk.OptionMenu(window, var, *optionList)
用户的每一次选择操作都会执行一次赋值操作,即将选择的选项赋值给var参量。
而我们可以通过get()
方法来获取OptionMenu
的参数值。
tkinter库同样支持图片的显示,和matplotlib等库绘制图片的方法大同小异,tkinter进行图片显示的方法同样是先定义一个画布,而后向画布当中填充元素。
Canvas组件就是tkinter库中的画布组件,我们要进行图片的绘制,首先就要创建一个画布,即实例化一个Canvas类,然后将所有的图像元素添加到画布上之后在进行pack展示。
给出典型的代码样例如下:
canvas = tk.Canvas(window, height=200, width=200)
line = canvas.create_line(50, 50, 80, 80)
canvas.pack()
canvas.place(x=200, y=0, anchor="n")
下面,我们给出Canvas组件中的一些常用几何元素的添加命令如下:
create_line()
:绘制直线(折线);create_oval()
:绘制椭圆;create_arc()
:绘制扇形;create_rectangle()
:绘制矩形;create_polygon()
:绘制曲线;create_text()
:绘制文本框;create_image()
:绘制图片,图片输入为下述PhotoImage组件实例;create_bitmap()
:绘制图片,图片输入为下述BitmapImage组件实例;PhotoImage组件与下述的BitmapImage组件为tkinter中的两种图片载入实例。他们都可以通过传入文件路径的方式读取图片,然后通过canvas组件中的相应方法载入到画布上。
PhotoImage组件与BitmapImage组件的唯一区别就在于支持的图片格式的差别,两种组件所支持的图片格式分别如下表所示:
图片插件 | 图片格式 |
---|---|
PhotoImage | .gif, .pgm, .ppm |
BitmapImage | .xbm |
tkinter同样提供了一些容器类的组件,他们的作用是在原先的窗口中另外开辟出一个独立的空间来部署其他插件,其功能和labview当中的container模块作用一模一样。
这里,我们给出Frame组件的使用代码样例如下:
frame = tk.Frame(window)
frame.pack()
label = tk.Label(frame, text="hello world")
label.pack()
本质来说和普通的用法没啥大的区别。labview当中container往往用于代码的模块化管理,这里原则上应该也是相似的用法,不过我是没看出再不能够折叠组件的情况下有啥作用。。。
现在,我们来看一下如何在可视化交互界面当中加入菜单栏。
tkinter当中用于实现这一功能的组件为Menu组件,其基础的使用代码如下:
menu = tk.Menu(window)
filemenu = tk.Menu(menu)
menu.add_cascade(label="File", menu=filemenu)
filemenu.add_command(label="quit", command=window.quit)
window.config(menu=menu)
可以看到:
add_cascade
命令在上层menu当中定义新的menu的方式构建多层级的菜单栏;add_command
方法在菜单栏中加入功能选项,其中的command
参数即为点击该菜单项时会执行的行为函数;config
命令作为window的一个属性进行传入。如果想要通过一个额外弹出框来进行消息提醒的话,tkinter提供的组件支持为tk.messagebox,他是一系列窗口函数的集合。
我们直接罗列出其中的具体弹出窗口函数如下:
showinfo(title, message, options)
showwarning(title, message, options)
showerror(title, message, options)
askyesno(title, message, options)
askretrycancel(title, message, options)
askquestion(title, message, options)
askokcancel(title, message, options)
上述便是tkinter库的一些基本的使用方法说明。
可以看到,除了一些坑之外,他们的用法事实上都是非常简单的,无奈官网的文档实在比较坑爹,各类教程又不太提及这个库,基本也就是莫烦那一套东西被搬来搬去,也不知道是谁抄谁的,有用法样例,但是细节内容也没有太过展开,想要了解基础用法的话是足够了,但是要想深入了解各部分功能的话还是有一点问题的。
这里,我们基于他们的教程对每个部分的插件使用方法进行了更加详细的讨论,某种意义上算是对官方文档的一点补充吧,也希望可以对想要使用tkinter库的朋友有所帮助。
不过总体而言事实上感觉是有点累赘的,毕竟tkinter这个库真心就只适合做做简单的窗口写作,不适合用于真正大规模的复杂窗口界面写作当中(大约也就是因为这个原因,官网的文档才会如此坑爹吧)。
Whatever,这里还是作为一个相对比较详细的文档介绍放在这里,希望对想要用tkinter写一些简单界面的朋友有所帮助吧。
上述所有的代码近期都会上传到我的GitHub仓库当中,仓库地址如下:
与君共勉!