欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

【Tkinter】Python标准库中的GUI框架入门之一——window和widget

程序员文章站 2022-05-30 15:12:20
...

本博客高度参考了这篇文章,可以认为是该文章的译文,将分期发表。
对原作者和网站提供如此高质量的教程表示感谢!

图形用户界面(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()

这时,操作系统就会弹出一个新的窗口,大概长这个样子:
【Tkinter】Python标准库中的GUI框架入门之一——window和widget
至此,画布就准备好了,因为现在什么都没做,所以画布是空白的。我们可以向这张画布上写一些字符:

greeting = tk.Label(text='Hello Tkinter!')
greeting.pack()

最终弹窗的结果是这样的:
【Tkinter】Python标准库中的GUI框架入门之一——window和widget
第一行代码的意思是,实例化一个Label,在实例化的过程中,通过text指定了该变量要显示的内容。

第二行代码的意思是,将该实例固定到画布上。

LabelTkinter中的一个类,单词翻译成中文的意思是“说明性短语”,在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()

很直观,我们改变了前景色和背景色,弹窗如下:
【Tkinter】Python标准库中的GUI框架入门之一——window和widget
关于颜色,可以查看颜色手册。当然也可以使用十六进制的RGB颜色值,这更加灵活。

Tkinter还提供了缩写,如果你觉得background这个单词太长了,可以用简写bg代替它,同理,可以用fg代替foreground

通过指定宽度和高度,来控制控件的大小:

greeting = tk.Label(
    text='Hello Tkinter!',
    fg="white",
    bg="black",
    width=10,
    height=10
	)
greeting.pack()

窗口如下:
【Tkinter】Python标准库中的GUI框架入门之一——window和widget
这里就存在一个问题了:宽度和高度都指定的是“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",
)

弹窗是这样的:
【Tkinter】Python标准库中的GUI框架入门之一——window和widget

2.3 使用Entry控件

当你需要收集用户输入的一些信息,比如用户名、密码等,这时候就可以使用Entry控件了。它显示为一个小的文本框,用户可以在其中输入一些文本。创建和设计Entry控件的工作原理与上面提到的LabelButton非常类似:

entry = tk.Entry(fg="yellow", bg="blue", width=50)

弹窗如下:
【Tkinter】Python标准库中的GUI框架入门之一——window和widget
上面已经说过,Entry是一个单行输入的文本框,那么一个重要的功能就是如何使用它从用户那里获取信息。对于一个Entry实例,最常用的方法有三个:

  • .get():获取Entry实例中的文本
  • .delete():删除Entry实例中的文本
  • .insert():向Entry实例中插入文本

下面通过具体的例子来演示这些方法的作用。

首先,创建一个GUI,其窗口显示的内容如下:
【Tkinter】Python标准库中的GUI框架入门之一——window和widget
生成这个窗口的脚本为:

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)

即删除了第一个字符,这时弹窗为:
【Tkinter】Python标准库中的GUI框架入门之一——window和widget
如果想一次性删除好几个字符,还需要传入第二个整数,用于告诉Python删除字符的终止位置:

entry.delete(0, 4)

这时,弹窗为:
【Tkinter】Python标准库中的GUI框架入门之一——window和widget
第二个数字为终止字符的位置,该位置的字符不会被删除,上面的代码实际上只删除了位置为0、1、2、3的字符。

如果想一次性删除全部,可以用如下的方法实现:

entry.delete(0, tk.END)

这时,弹窗里面的Entry控件又为空了:
【Tkinter】Python标准库中的GUI框架入门之一——window和widget
与之相反,你可以通过.insert()方法来向控件中添加字符:

entry.insert(0, "Python")

第一个整数告诉Python从哪里开始插入字符。如果Entry控件中没有任何字符,那么新字符总是插入到控件的开始位置上,与你传入的第一个参数无关。

这时的窗口又变成了这样:
【Tkinter】Python标准库中的GUI框架入门之一——window和widget
再继续往里面插值:

entry.insert(0, "Real ")

会发现,窗口变成了第一次输入后的样子:
【Tkinter】Python标准库中的GUI框架入门之一——window和widget

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应该具有如下样式:

【Tkinter】Python标准库中的GUI框架入门之一——window和widget

单击该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()

弹窗如下:
【Tkinter】Python标准库中的GUI框架入门之一——window和widget
代码的理解也是比较直观的:

  • 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()
...

弹窗如下:
【Tkinter】Python标准库中的GUI框架入门之一——window和widget
通过前后两次实验的对比,可以这样理解Frame控件的工作原理:其他控件在实例化时,通过master参数指定了其所属的Frame,然后将其pack到该Frame上。最后,将Frame实例pack到主window上,就显示出来各个控件了。