【Tkinter】Python标准库中的GUI框架入门之一——window和widget
本博客高度参考了这篇文章,可以认为是该文章的译文,将分期发表。
对原作者和网站提供如此高质量的教程表示感谢!
图形用户界面(Graphical User Interface,简称GUI),是指采用图形方式显示计算机的操作界面。相较于命令行,这种方式上手难度低,更加容易为普通用户所接受。
Python中有许多GUI框架,但Tkinter
是唯一一种被写入标准库中的。Tkinter
是跨平台(cross-platform)的,这意味着相同的代码可以在Windows、Linux和macOS上运行,而且GUI中的可视化元素(按钮、文本框等)可以根据其所在的操作系统进行自动渲染。
当然,Tkinter
也有其不足之处。其中一点就是,它显得“过时”了——许多可视化元素的样式看起来像上个世纪的个人电脑中的一样。如果你追求新潮、酷炫,那么Tkinter
多半没法满足你的要求;如果只是想实现某些功能而不怎么注重外观,那么轻量级的Tkinter
足以让你快速、便捷地构建自己的应用。
1. 窗口(window)
在一个GUI中,最基础最底层的元素是窗口(window)。如果一个把一个完整的GUI比作一幅画,那么window就是画布,所有的操作都在这张画布上进行。所以,在编写GUI之前,需要创建这样一张画布——用编程的语言来说,就是要先实例化这样一个window。
首先,打开Python的交互式窗口,导入tkinter
(在所有的交互代码中,均省略了’>>>’):
import tkinter as tk
然后,实例化一个window
:
window = tk.Tk()
这时,操作系统就会弹出一个新的窗口,大概长这个样子:
至此,画布就准备好了,因为现在什么都没做,所以画布是空白的。我们可以向这张画布上写一些字符:
greeting = tk.Label(text='Hello Tkinter!')
greeting.pack()
最终弹窗的结果是这样的:
第一行代码的意思是,实例化一个Label
,在实例化的过程中,通过text
指定了该变量要显示的内容。
第二行代码的意思是,将该实例固定到画布上。
Label
是Tkinter
中的一个类,单词翻译成中文的意思是“说明性短语”,在GUI领域,这个类被叫做“控件”(widget),通过向窗口中添加不同的控件,并对它们进行组合,就形成了各式各样的GUI了。
Windows操作系统之所以这样命名,就是因为它本质上就是许多window的组合,当然这些组合是非常复杂的。这些window为用户屏蔽了看上去很枯燥的命令操作,从而提升了用户体验,但代价也是很巨大的,就是速度变慢。这种变慢对于普通用户来说可能并没有什么感觉,但是对于服务器而言,却很可能是致命的,这也是为什么绝大部分服务器都采用没有GUI的Linux操作系统的原因之一。
2. 控件(widget)
如果说窗口是画布,那么控件就是不同的颜料了。它们也是组成一个GUI所必不可少的东西,是用户与程序进行交互的基础。在Tkinter
中,每一种控件都由一个类来定义,一些常见的控件及其说明如下:
控件 | 说明 |
---|---|
Label | 用于在屏幕上打印文本 |
Button | 按钮,可以包含文字,并在单击时进行相应的操作 |
Entry | 只允许单行文本输入的控件 |
Text | 允许多行文本输入的控件 |
Frame | 一个矩形区域,用于对控件分组或为其提供填充 |
当然,还有许多其他的控件,但上面这些确实是最基础的,熟练使用这些控件,就可以开发一些有趣的东西,并对Tkinter
的运用有相当程度的理解。
2.1 使用Label
控件
Label
控件可以用来展示文本或者图片。这些文本(图片)是不能够被用户编辑的,上面你已经看到了一个展示文本的例子,而实际上我们还可以对展示的文本进行更多的操作:
greeting = tk.Label(
text='Hello Tkinter!',
foreground="white",
background="black"
)
greeting.pack()
很直观,我们改变了前景色和背景色,弹窗如下:
关于颜色,可以查看颜色手册。当然也可以使用十六进制的RGB颜色值,这更加灵活。
Tkinter
还提供了缩写,如果你觉得background
这个单词太长了,可以用简写bg
代替它,同理,可以用fg
代替foreground
。
通过指定宽度和高度,来控制控件的大小:
greeting = tk.Label(
text='Hello Tkinter!',
fg="white",
bg="black",
width=10,
height=10
)
greeting.pack()
窗口如下:
这里就存在一个问题了:宽度和高度都指定的是“10”,怎么渲染出来的不是一个正方形呢?这是因为在Tkinter
中,宽度和高度都是以文本单元(text unit)来度量的。具体来说,1单位的宽度由系统中的字符“0”或者数字0的宽度来决定,1单位的高度由系统中的字符“0”或者数字0的高度来决定。
采用这种设置而不是用英寸、厘米、像素等度量,也是为了保证程序在跨平台运行时显示一致。以字符来度量意味着控件大小与用户机器上的默认字体是相关的,这样就保证了文本能够正确地嵌入到Label和Button中。
2.2 使用Button
控件
顾名思义,Button
控件就是用来展示一个按钮的,点击该按钮,可以实现一个操作。
事实上,我们可以将Button
理解为一个可以点击的Label
。用于创建Label
的关键字参数也适用于Button
控件:
button = tk.Button(
text="Click me!",
width=25,
height=5,
bg="blue",
fg="yellow",
)
弹窗是这样的:
2.3 使用Entry
控件
当你需要收集用户输入的一些信息,比如用户名、密码等,这时候就可以使用Entry
控件了。它显示为一个小的文本框,用户可以在其中输入一些文本。创建和设计Entry
控件的工作原理与上面提到的Label
和Button
非常类似:
entry = tk.Entry(fg="yellow", bg="blue", width=50)
弹窗如下:
上面已经说过,Entry
是一个单行输入的文本框,那么一个重要的功能就是如何使用它从用户那里获取信息。对于一个Entry
实例,最常用的方法有三个:
-
.get()
:获取Entry
实例中的文本 -
.delete()
:删除Entry
实例中的文本 -
.insert()
:向Entry
实例中插入文本
下面通过具体的例子来演示这些方法的作用。
首先,创建一个GUI,其窗口显示的内容如下:
生成这个窗口的脚本为:
import tkinter as tk
window = tk.Tk()
label = tk.Label(text='name')
entry = tk.Entry()
label.pack()
entry.pack()
需要注意的一点是,两个控件都是居中对齐的,这也是.pack()
方法的特性之一,后文会对这个特性进行说明。
单击一下输入框,就可以在里面输入内容了,假设我们输入“Real Python”。为了验证各个方法的作用,我们在交互式窗口接着输入:
entry.get()
则会打印出“Real Python”的字符。
.delete()
方法用于删除字符,其使用与对待Python中的字符串的使用方式很相似,需要传入一个参数告诉Python删除的字符的位置:
entry.delete(0)
即删除了第一个字符,这时弹窗为:
如果想一次性删除好几个字符,还需要传入第二个整数,用于告诉Python删除字符的终止位置:
entry.delete(0, 4)
这时,弹窗为:
第二个数字为终止字符的位置,该位置的字符不会被删除,上面的代码实际上只删除了位置为0、1、2、3的字符。
如果想一次性删除全部,可以用如下的方法实现:
entry.delete(0, tk.END)
这时,弹窗里面的Entry
控件又为空了:
与之相反,你可以通过.insert()
方法来向控件中添加字符:
entry.insert(0, "Python")
第一个整数告诉Python从哪里开始插入字符。如果Entry
控件中没有任何字符,那么新字符总是插入到控件的开始位置上,与你传入的第一个参数无关。
这时的窗口又变成了这样:
再继续往里面插值:
entry.insert(0, "Real ")
会发现,窗口变成了第一次输入后的样子:
2.4 使用Text
控件
可以将Text
控件理解为一个可以输入很多行文本的Entry
。事实上,它的作用就是如此。
与Entry
控件类似,Text
控件也有三种主要的方法:
-
.get()
:获取Text
实例中的文本 -
.delete()
:删除Text
实例中的文本 -
.insert()
:向Text
实例中插入文本
需要注意的是,尽管方法名称完全一致,但由于多行文本的原因,其使用方法略有不同。
首先,我们重新创建一个带Text
控件的GUI:
window = tk.Tk()
text_box = tk.Text()
text_box.pack()
GUI应该具有如下样式:
单击该Text
中的任意位置,然后就可以输入字符了,这里我输入了“Hello World”,占用了两行。
与Entry
控件相类似,可以采用.get()
方法来提取Text
控件中的内容。但需要注意的是,Text
控件的.get()
方法至少需要一个参数。如果只传入一个参数,那么你将得到该位置上的字符;如果想要得到数个字符,则需要传入开始位置参数和终止位置参数。由于Text
控件中的内容可能包含多行,因此,每个参数都必须包含两部分内容:字符的行号与字符在该行中的位置。
所以,位置参数的格式是<line>.<char>
。为了直观的理解,尝试一下下面的代码:
text_box.get("1.0")
# 'H'
text_box.get("1.0", "1.5")
# 'Hello'
text_box.get("1.0", tk.END)
# 'Hello\nWorld\n'
注意到,换行符也是一个字符。
在理解了.get()
方法之后,相应的.delete()
方法和.insert()
方法也是通过类似的方式进行调用的。具体不再赘述,只需要记住,\n
也是一个字符。
2.5 使用Frame
控件
一个好的GUI不但有很多控件,它们之间还得通过合适的方式组合起来才行,Frame
控件就可以满足这个要求。
可以把Frame
控件理解为其他控件的容器,在实例化其他控件时,通过master
参数来指定该控件属于哪个Frame
。具体的,看以下代码:
import tkinter as tk
window = tk.Tk()
frame_a = tk.Frame()
frame_b = tk.Frame()
label_a = tk.Label(master=frame_a, text="I'm in Frame A")
label_a.pack()
label_b = tk.Label(master=frame_b, text="I'm in Frame B")
label_b.pack()
frame_a.pack()
frame_b.pack()
弹窗如下:
代码的理解也是比较直观的:
- 5、6行实例化了两个
Frame
控件; - 8、9行实例化了一个
Label
控件,并通过master
参数,指定了该控件包含于frame_a
中; - 11、12行操作类似;
- 14、15行将两个
Frame
控件pack到window上; - 17行启动了window的主循环,以保证窗口处于打开状态。
接下来,如果我改变14和15行的顺序,那么弹窗是什么样的呢?
...
frame_b.pack()
frame_a.pack()
...
弹窗如下:
通过前后两次实验的对比,可以这样理解Frame
控件的工作原理:其他控件在实例化时,通过master
参数指定了其所属的Frame
,然后将其pack到该Frame
上。最后,将Frame
实例pack到主window上,就显示出来各个控件了。
上一篇: Ubuntu 64位移植MiniGui
下一篇: 【npm】npm换成cnpm