也许您刚刚开始学习 Python,并且已经构建了一两个简单的应用程序。恭喜!但是,现在呢?那么,如何构建一个图形用户界面(“GUI”)来与您闪亮的新 Python 应用程序交互?
使用 Python 构建 GUI 应用程序有多种选择,包括PyQt和wxPython 。然而,本指南将向您介绍Tkinter 。
Tkinter代表“Tk 接口”,是 Python 构建 GUI 的标准,包含在Python 标准库中。它是Tk GUI 工具包的绑定,这是一个免费的开源 GUI小部件库,可用于以各种编程语言构建图形界面。
本教程介绍了一个基本 GUI 应用程序的设计,该应用程序在用户选择的时区中显示本地时间。这些步骤逐步构建应用程序并描述了使用Tkinter时的一些关键概念,包括 GUI 元素的布局、捕获用户输入以及将 GUI 元素绑定到回调方法。
要完成本指南,您需要具备:
pip
。pytz
时区库。该库可以使用pip
安装。
pip install pytz
本指南将交替使用术语根窗口和主窗口。
创建一个名为timezone.py
的新 Python 应用程序并添加以下import
语句以导入Tkinter
、 datetime
和pytz
模块。
import tkinter as tk import datetime import pytz
本指南包含一组美国时区,它们只是pytz
支持的时区的一小部分。可以通过添加额外的pytz
时区名称来扩展应用程序的功能。可以通过运行以下命令输出pytz
可用时区的完整列表:
print(pytz.all_timezones)
通过pytz
可用的美国时区集被指定为一个列表。
import tkinter as tk import datetime import pytz timezones = ["US/Alaska", "US/Aleutian", "US/Arizona", "US/Central", "US/East-Indiana", "US/Eastern", "US/Hawaii", "US/Indiana-Starke", "US/Michigan", "US/Mountain", "US/Pacific", "US/Samoa"]
实例化Tk
类会创建一个根窗口,它将作为时区应用程序 GUI 的主窗口。
import tkinter as tk import datetime import pytz timezones = ["US/Alaska", "US/Aleutian", "US/Arizona", "US/Central", "US/East-Indiana", "US/Eastern", "US/Hawaii", "US/Indiana-Starke", "US/Michigan", "US/Mountain", "US/Pacific", "US/Samoa"] root = tk.Tk()
此步骤配置根窗口,特别是它的标题、几何形状和可调整大小。
窗口标题是使用title
方法设置的。
import tkinter as tk import datetime import pytz timezones = ["US/Alaska", "US/Aleutian", "US/Arizona", "US/Central", "US/East-Indiana", "US/Eastern", "US/Hawaii", "US/Indiana-Starke", "US/Michigan", "US/Mountain", "US/Pacific", "US/Samoa"] root = tk.Tk() root.title("Simple Timezone Application")
以像素表示的窗口宽度和高度值可以分配给变量,以使代码更易于理解。
import tkinter as tk import datetime import pytz timezones = ["US/Alaska", "US/Aleutian", "US/Arizona", "US/Central", "US/East-Indiana", "US/Eastern", "US/Hawaii", "US/Indiana-Starke", "US/Michigan", "US/Mountain", "US/Pacific", "US/Samoa"] root = tk.Tk() root.title("Simple Timezone Application") window_width = 450 window_height = 175
主窗口可以放置在屏幕上的任何位置,例如中心、左上角等。居中的窗口提供了一个漂亮的“外观”,并且可以使用根窗口的winfo_screenwidth()
确定主窗口的中心位置winfo_screenwidth()
和winfo_screenheight()
方法以及一些简单的数学运算。这两种方法返回屏幕宽度和屏幕高度,用于计算适当的 (x,y) 坐标以使主窗口居中。与窗口宽度和高度一样,中心位置值也可以分配给变量以使代码更具可读性。
import tkinter as tk import datetime import pytz timezones = ["US/Alaska", "US/Aleutian", "US/Arizona", "US/Central", "US/East-Indiana", "US/Eastern", "US/Hawaii", "US/Indiana-Starke", "US/Michigan", "US/Mountain", "US/Pacific", "US/Samoa"] root = tk.Tk() root.title("Simple Timezone Application") window_width = 450 window_height = 175 screen_width = root.winfo_screenwidth() screen_height = root.winfo_screenheight() center_x = int((screen_width - window_width)/2) center_y = int((screen_height - window_height)/2)
请注意, center_x
和center_y
值也以像素表示,并且int
用于确保两个计算值都是整数。
root
窗口几何,它指定主窗口的大小和位置,是使用根窗口的geometry
方法设置的。为简单起见,您可以使用格式化的字符串文字或f-string ,它将在运行时插入几何变量表达式。根据以下代码块,将f 字符串文字作为参数传递给geometry
方法。
import tkinter as tk import datetime import pytz timezones = ["US/Alaska", "US/Aleutian", "US/Arizona", "US/Central", "US/East-Indiana", "US/Eastern", "US/Hawaii", "US/Indiana-Starke", "US/Michigan", "US/Mountain", "US/Pacific", "US/Samoa"] root = tk.Tk() root.title("Simple Timezone Application") window_width = 450 window_height = 175 screen_width = root.winfo_screenwidth() screen_height = root.winfo_screenheight() center_x = int((screen_width - window_width)/2) center_y = int((screen_height - window_height)/2) root.geometry(f"{window_width}x{window_height}+{center_x}+{center_y}")
根窗口沿其 x 和 y 轴的可调整大小可以通过根窗口的resizable
方法设置。 resizable
方法按顺序接受height
和width
参数。任一参数的True
或非零值指定主窗口可沿相关轴调整大小。任一参数的False
或0
值指定主窗口不能沿给定轴调整大小。计时器应用程序将对高度和宽度使用False
值,以防止调整主窗口的大小。
import tkinter as tk import datetime import pytz timezones = ["US/Alaska", "US/Aleutian", "US/Arizona", "US/Central", "US/East-Indiana", "US/Eastern", "US/Hawaii", "US/Indiana-Starke", "US/Michigan", "US/Mountain", "US/Pacific", "US/Samoa"] root = tk.Tk() root.title("Simple Timezone Application") window_width = 450 window_height = 175 screen_width = root.winfo_screenwidth() screen_height = root.winfo_screenheight() center_x = int((screen_width - window_width)/2) center_y = int((screen_height - window_height)/2) root.geometry(f"{window_width}x{window_height}+{center_x}+{center_y}") root.resizable(False, False)
可以使用几何管理器以不同的方式将小部件排列在主窗口的顶部。通常,小部件可以按 3 种方式排列:
使用pack()
方法打包。 pack()
几何管理器在将小部件添加到根窗口或父小部件之前将它们组织成块。考虑包装的一种不完美但可能有用的方法是考虑将杂货物品添加到杂货袋中。这些物品不一定要添加到包中的预定义位置。相反,它们是一个接一个地包装,用尽可用空间,直到所有物品都放入包中。 pack()
几何管理器以类似的方式工作。
使用place()
方法放置。 place()
几何管理器将小部件放置到根窗口或父小部件中特定的预定义位置。在构建精确的 GUI 布局时,这个几何管理器显然很有用。
使用grid()
方法排列为网格。网格是二维行/列表。通过指定应放置小部件的特定行和列,将小部件添加到网格中。使用根窗口的columnconfigure
和rowconfigure
方法设置网格。这些方法中的每一个都有一个index
和一个weight
属性。 index
属性使用起始基0
指定列或行的位置。 weight
属性指定特定列或行相对于其他列的大小。例如,如果第0 列的权重为1
,第 1列的权重为3
,则第 1 列将是第0 列的 3 倍。
将元素彼此相邻放置时,网格很方便,而且实现起来相对简单。时区应用程序将使用划分为 4 个相等列的主窗口网格。
import tkinter as tk import datetime import pytz timezones = ["US/Alaska", "US/Aleutian", "US/Arizona", "US/Central", "US/East-Indiana", "US/Eastern", "US/Hawaii", "US/Indiana-Starke", "US/Michigan", "US/Mountain", "US/Pacific", "US/Samoa"] root = tk.Tk() root.title("Simple Timezone Application") window_width = 450 window_height = 175 screen_width = root.winfo_screenwidth() screen_height = root.winfo_screenheight() center_x = int((screen_width - window_width)/2) center_y = int((screen_height - window_height)/2) root.geometry(f"{window_width}x{window_height}+{center_x}+{center_y}") root.resizable(False, False) root.columnconfigure(0, weight=1) root.columnconfigure(1, weight=1) root.columnconfigure(2, weight=1) root.columnconfigure(3, weight=1)
在这一步中,小部件被添加到步骤 4 中定义的主窗口网格中。时区应用程序将使用 3 个TKinter小部件:
Label
Listbox
Button
Tkinter 的Label
和Button
小部件与它们的名称完全相同。 Label
小部件允许程序员显示文本(即标签),而Button
小部件用于显示按钮。 Listbox
小部件用于从选项列表中选择一个(或多个)值。
当然,除了上面列出的 3 个小部件之外,还有其他小部件,例如SpinBox
、 Entry
和Message
小部件。正式的Tk 命令文档是一个有用的资源,可以帮助您了解更多关于 Tk 工具包中可用的小部件的信息。
为与 GUI 设计一起使用的给定小部件创建每个小部件类的实例。时区应用程序使用 4 个小部件实例:2 个Label
小部件、1 个Listbox
小部件和 1 个Button
小部件。
import tkinter as tk import datetime import pytz timezones = ["US/Alaska", "US/Aleutian", "US/Arizona", "US/Central", "US/East-Indiana", "US/Eastern", "US/Hawaii", "US/Indiana-Starke", "US/Michigan", "US/Mountain", "US/Pacific", "US/Samoa"] root = tk.Tk() root.title("Simple Timezone Application") window_width = 450 window_height = 175 screen_width = root.winfo_screenwidth() screen_height = root.winfo_screenheight() center_x = int((screen_width - window_width)/2) center_y = int((screen_height - window_height)/2) root.geometry(f"{window_width}x{window_height}+{center_x}+{center_y}") root.resizable(False, False) root.columnconfigure(0, weight=1) root.columnconfigure(1, weight=1) root.columnconfigure(2, weight=1) root.columnconfigure(3, weight=1) # Instance of Label widget class for selection prompt. select_timezone_label = tk.Label(root, text="Please select a timezone.") # Instance of Listbox class for selection of timezone from list. list_var = tk.Variable(value=timezones) select_timezone_listbox = tk.Listbox(root, listvariable=list_var, height=1) # Instance of Button class to get the local time in the selected timezone. select_timezone_button = tk.Button(root, text="Get Time") # Second instance of the Label class to display the local time in the selected timezone. time_label = tk.Label(root, text="")
如上面的代码块所示,每个小部件实例都“附加”到root
窗口。 Listbox
小部件还需要一个特殊的 Tkinter Variable()
类的实例化,该类用于为小部件提供时区选项列表,用户可以通过Listbox
listvariable
属性从中选择。每个Label
小部件显示的文本可以使用text
属性进行配置。 time_label
的文本值留空,因为每次用户“获取”所选时区的时间时都会动态设置它。
步骤 5a 中创建的小部件实例可以使用grid()
方法放置在步骤 4 中定义的网格上。时区应用程序将使用以下grid()
方法属性来定义布局:
column
:指定将使用0
基础放置小部件的特定列。
row
:指定将使用0
放置小部件的特定行。
columnspan
:指定小部件应跨越指定数量的列。例如,具有columnspan=3
值的小部件将跨越 3 列,即使小部件本身小于 3 列。
sticky
:Tkinter 的默认行为是将一个小部件放置在它所在的单元格的水平和垂直中心(即特定的行/列位置)。可以使用sticky
属性覆盖此默认行为,该属性使用类似指南针的值,包括NW
、 N
、 NE
、 W
、 E
、 SW
、 S
和SE
,以将小部件对齐到小部件单元格内的特定位置。例如, sticky=tK.W
指定小部件应与其网格单元的西角对齐。类似地, sticky=tK.E
指定小部件应与其网格单元的东角对齐。
padx
, pady
: padx
和pady
属性分别用于添加 x 轴和 y 轴填充,其值以像素为单位。自然地,填充可以提供更专业的 GUI,确保小部件不会直接“碰撞”到根窗口或其他小部件的边缘。
import tkinter as tk import datetime import pytz timezones = ["US/Alaska", "US/Aleutian", "US/Arizona", "US/Central", "US/East-Indiana", "US/Eastern", "US/Hawaii", "US/Indiana-Starke", "US/Michigan", "US/Mountain", "US/Pacific", "US/Samoa"] root = tk.Tk() root.title("Simple Timezone Application") window_width = 450 window_height = 175 screen_width = root.winfo_screenwidth() screen_height = root.winfo_screenheight() center_x = int((screen_width - window_width)/2) center_y = int((screen_height - window_height)/2) root.geometry(f"{window_width}x{window_height}+{center_x}+{center_y}") root.resizable(False, False) root.columnconfigure(0, weight=1) root.columnconfigure(1, weight=1) root.columnconfigure(2, weight=1) root.columnconfigure(3, weight=1) # Instance of Label widget class for selection prompt. select_timezone_label = tk.Label(root, text="Please select a timezone.") # Instance of Listbox class for selection of timezone from list. list_var = tk.Variable(value=timezones) select_timezone_listbox = tk.Listbox(root, listvariable=list_var, height=1) # Instance of Button class to get the local time in the selected timezone. select_timezone_button = tk.Button(root, text="Get Time") # Second instance of the Label class to display the local time in the selected timezone. time_label = tk.Label(root, text="") # Place widgets on grid. select_timezone_label.grid(column=0, row=0, columnspan=4, sticky=tk.W, padx=10, pady=10) select_timezone_listbox.grid(column=0, row=1, columnspan=3, sticky=tk.EW, padx=10, pady=10) select_timezone_button.grid(column=4, row=1, sticky=tk.E, padx=10, pady=10) time_label.grid(column=0, row=4, columnspan=4, sticky=tk.W, padx=10, pady=10)
当用户单击第 5 步中创建的select_timezone_button
按钮时,需要定义一个回调方法来处理事件。
在定义回调逻辑之前,首先将按钮绑定到最终将包含回调代码的方法名称会很有帮助。 bind()
方法可以与lambda
函数结合使用,将select_timezone_button
绑定到指定的回调方法。另外,请注意lambda
函数用于将引用传递给select_timezone_listbox
和time_label
小部件作为回调参数。这些回调参数实际上是不必要的,因为select_timezone_listbox
和time_label
在全局范围内。但是,演示如何将参数传递给回调函数可以说是有用的。
import tkinter as tk import datetime import pytz timezones = ["US/Alaska", "US/Aleutian", "US/Arizona", "US/Central", "US/East-Indiana", "US/Eastern", "US/Hawaii", "US/Indiana-Starke", "US/Michigan", "US/Mountain", "US/Pacific", "US/Samoa"] root = tk.Tk() root.title("Simple Timezone Application") window_width = 450 window_height = 175 screen_width = root.winfo_screenwidth() screen_height = root.winfo_screenheight() center_x = int((screen_width - window_width)/2) center_y = int((screen_height - window_height)/2) root.geometry(f"{window_width}x{window_height}+{center_x}+{center_y}") root.resizable(False, False) root.columnconfigure(0, weight=1) root.columnconfigure(1, weight=1) root.columnconfigure(2, weight=1) root.columnconfigure(3, weight=1) # Instance of Label widget class for selection prompt. select_timezone_label = tk.Label(root, text="Please select a timezone.") # Instance of Listbox class for selection of timezone from list. list_var = tk.Variable(value=timezones) select_timezone_listbox = tk.Listbox(root, listvariable=list_var, height=1) # Instance of Button class to get the local time in the selected timezone. select_timezone_button = tk.Button(root, text="Get Time") # Second instance of the Label class to display the local time in the selected timezone. time_label = tk.Label(root, text="") # Place widgets on grid. select_timezone_label.grid(column=0, row=0, columnspan=4, sticky=tk.W, padx=10, pady=10) select_timezone_listbox.grid(column=0, row=1, columnspan=3, sticky=tk.EW, padx=10, pady=10) select_timezone_button.grid(column=4, row=1, sticky=tk.E, padx=10, pady=10) time_label.grid(column=0, row=4, columnspan=4, sticky=tk.W, padx=10, pady=10) select_timezone_button.bind("<Button>", lambda e, args=[select_timezone_listbox, time_label]: get_timezone_time(e, args))
处理按钮点击事件的回调逻辑定义如下。
def get_timezone_time(e, args): select_timezone_listbox = args[0] time_label = args[1] selection_index = select_timezone_listbox.curselection() selected_timezone = select_timezone_listbox.get(selection_index) now_time = datetime.datetime.now() tz_time = now_time.astimezone(pytz.timezone(selected_timezone)) tz_formatted = tz_time.strftime("%H:%M:%S") time_label.configure({"text": f"The time in {selected_timezone} is {tz_formatted}."}) time_label.update()
curselection()
和get()
方法用于从对select_timezone_listbox
小部件的引用中检索用户选择的时区。然后将用户的当前时间转换为选定的时区时间。最后,使用configure
方法将time_label
引用的text
属性更改为所选时区的本地时间。请注意, update()
方法用于“强制” time_label
小部件使用新的文本值更新自身。
根窗口的mainloop()
方法作为最后一行代码应用。 mainloop()
方法将无限循环运行 GUI,直到用户退出。
完成的代码如下:
import tkinter as tk import datetime import pytz timezones = ["US/Alaska", "US/Aleutian", "US/Arizona", "US/Central", "US/East-Indiana", "US/Eastern", "US/Hawaii", "US/Indiana-Starke", "US/Michigan", "US/Mountain", "US/Pacific", "US/Samoa"] def get_timezone_time(e, args): select_timezone_listbox = args[0] time_label = args[1] selection_index = select_timezone_listbox.curselection() selected_timezone = select_timezone_listbox.get(selection_index) now_time = datetime.datetime.now() tz_time = now_time.astimezone(pytz.timezone(selected_timezone)) tz_formatted = tz_time.strftime("%H:%M:%S") time_label.configure({"text": f"The time in {selected_timezone} is {tz_formatted}."}) time_label.update() root = tk.Tk() root.title("Simple Timezone Application") window_width = 450 window_height = 175 screen_width = root.winfo_screenwidth() screen_height = root.winfo_screenheight() center_x = int((screen_width - window_width)/2) center_y = int((screen_height - window_height)/2) root.geometry(f"{window_width}x{window_height}+{center_x}+{center_y}") root.resizable(False, False) root.columnconfigure(0, weight=1) root.columnconfigure(1, weight=1) root.columnconfigure(2, weight=1) root.columnconfigure(3, weight=1) # Instance of Label widget class for selection prompt. select_timezone_label = tk.Label(root, text="Please select a timezone.") # Instance of Listbox class for selection of timezone from list. list_var = tk.Variable(value=timezones) select_timezone_listbox = tk.Listbox(root, listvariable=list_var, height=1) # Instance of Button class to get the local time in the selected timezone. select_timezone_button = tk.Button(root, text="Get Time") # Second instance of the Label class to display the local time in the selected timezone. time_label = tk.Label(root, text="") # Place widgets on grid. select_timezone_label.grid(column=0, row=0, columnspan=4, sticky=tk.W, padx=10, pady=10) select_timezone_listbox.grid(column=0, row=1, columnspan=3, sticky=tk.EW, padx=10, pady=10) select_timezone_button.grid(column=4, row=1, sticky=tk.E, padx=10, pady=10) time_label.grid(column=0, row=4, columnspan=4, sticky=tk.W, padx=10, pady=10) # Bind button to callback. select_timezone_button.bind("<Button>", lambda e, args=[select_timezone_listbox, time_label]: get_timezone_time(e, args)) root.mainloop()
启动应用程序后,GUI 应如下所示:
要使用该应用程序,请单击列表框并使用键盘的上/下光标键滚动浏览时区选项。单击获取时间按钮以显示所选时区的当前时间。
恭喜您使用Tkinter构建了您的第一个 Python GUI 应用程序!如简介中所述,本指南旨在向您介绍一些基本概念。前面提到的Python 标准库文档和Tk 命令文档参考是两个很好的资源,可帮助您了解更高级的 Tkinter 特性和功能。
Tkinter GUI 应用程序有时被批评为具有非本地外观。这可能是真的。但是工具包小部件是高度可配置的,并且可以相对快速地构建 Tkinter GUI 应用程序,而无需安装任何外部 Python 包。
除了正式的文档之外,还有无数的教程可以通过 Internet 来了解如何使用 Tkinter 构建更复杂的 GUI 应用程序。