Skip to content

Python GUI 编程

Python 提供了多种用于创建图形用户界面(GUI, Graphical User Interface)的库。其中一些流行的选项包括:

  • tkinter: (在 Python 2 中曾称为 Tkinter)是 Python 标准内置的 Tcl/Tk GUI 工具包接口。它是跨平台的,并且相对容易上手。本章节将重点介绍 tkinter。
  • PyQt / PySide: 是强大的 Qt 框架的绑定。提供了广泛的现代窗口部件和功能。常用于复杂的桌面应用程序。
  • Kivy: 一个用于开发多点触控应用的开源库。适用于现代 UI、游戏和移动开发。
  • wxPython: 是 wxWidgets C++ 库的绑定。另一个成熟的跨平台工具包。
  • Web 框架(带包装器):使用 Flask/Django 等框架配合 PyWebView 或类似 Electron 的包装器来使用 Web 技术创建桌面应用。

tkinter 提供了一种直接使用 Python 构建 GUI 应用程序的简单方法。它封装了 Tcl/Tk 工具包,将其组件作为 Python 类提供。

创建 tkinter 应用的基本步骤:

  • 导入 tkinter 模块(通常导入为 tk)。
  • 创建主应用程序窗口(tk.Tk 的实例)。
  • 向主窗口或其他容器窗口部件(如 Frame)添加窗口部件(按钮、标签、输入框等)。
  • 使用几何管理器(pack、grid、place)配置窗口部件属性和布局。
  • 定义事件处理程序(用户点击按钮等时调用的函数)。
  • 启动 tkinter 主事件循环(mainloop()),使窗口具有交互性并响应用户操作。
#!/usr/bin/env python3
import tkinter as tk
# 1. Create the main window
# 1. 创建主窗口
root = tk.Tk()
root.title("Simple Tkinter App") # Set window title
# 设置窗口标题
root.geometry("300x150") # Set initial window size (width x height)
# 设置初始窗口大小(宽度 x 高度)
# 2. Add widgets (example: a label)
# 2. 添加窗口部件(示例:一个标签)
my_label = tk.Label(root, text="Hello, Tkinter!")
my_label.pack(pady=20) # Use pack geometry manager with padding
# 使用 pack 几何管理器,并添加垂直内边距
# 3. Add a button with an action
# 3. 添加一个带动作的按钮
def on_button_click():
print("Button clicked!")
my_label.config(text="Button was clicked!")
my_button = tk.Button(root, text="Click Me", command=on_button_click)
my_button.pack()
# 4. Start the main event loop
# 4. 启动主事件循环
root.mainloop()
# Code after mainloop() executes only after the window is closed.
# mainloop() 之后的代码只有在窗口关闭后才会执行。

运行此脚本将创建一个标题为 “Simple Tkinter App” 的小窗口,其中包含文本 “Hello, Tkinter!” 和一个标有 “Click Me” 的按钮。点击按钮会在控制台打印一条消息,并更改标签的文本。

窗口部件是 tkinter GUI 的构建块(按钮、文本框、标签等)。每个窗口部件都由一个类表示。

常用窗口部件:

窗口部件类 / 模块描述
Tk应用程序的主(根)窗口。
Toplevel创建额外的独立窗口。
Frame一个容器窗口部件,用于分组和组织其他窗口部件。
Label显示静态文本或图像。
Button一个可点击的按钮,可以触发函数调用(command)。
Entry单行文本输入字段。
Text多行文本输入和显示区域。
Checkbutton复选框,允许选择/取消选择。
Radiobutton允许从一组选项中选择一个(通过共享变量链接)。
Listbox显示项目列表,用户可以从中选择。
Scrollbar为 Listbox、Text、Canvas 等窗口部件添加滚动功能。
Menu创建菜单栏和上下文菜单。
Scale一个滑块窗口部件,用于选择范围内的值。
Canvas一个多功能窗口部件,用于绘制形状、线条、文本、图像以及嵌入其他窗口部件。
Spinbox一个带上下箭头的 Entry 窗口部件,用于从值范围中选择。
LabelFrame带可见边框和文本标签的 Frame。
PanedWindow管理可由用户调整大小的窗格的容器。
Message类似于 Label,但设计用于多行文本消息。
messagebox (模块)tkinter.messagebox 提供用于标准对话框(信息、警告、错误、询问是/否等)的函数。

窗口部件有许多选项(属性)控制其外观和行为。这些选项可以在创建时设置,也可以稍后使用 config() 或 configure() 方法(或类似字典的访问方式 widget['option'] = value)修改。

常用选项包括:

  • text: 窗口部件上显示的文本(Label、Button 等)。
  • textvariable: 一个链接到窗口部件文本/值的 tkinter 变量(StringVar、IntVar 等)。变量的更改会更新窗口部件,反之亦然(对于 Entry、Checkbutton、Radiobutton、Scale)。
  • command: 窗口部件激活时(例如,Button 点击)调用的函数。
  • width、height: 窗口部件的大小(单位取决于窗口部件,通常是文本单位或像素)。
  • bg(或 background)、fg(或 foreground):背景色和文本/前景色。
  • font: 指定字体(例如,('Arial', 12, 'bold'))。
  • relief: 边框样式(flat 平坦、raised 凸起、sunken 凹陷、groove 凹槽、ridge 脊)。
  • borderwidth(或 bd):边框宽度。
  • state: 窗口部件状态(normal 正常、disabled 禁用、readonly 只读)。
  • anchor: 内容在窗口部件空间内的位置(例如,tk.N 北、tk.S 南、tk.E 东、tk.W 西、tk.CENTER 居中)。
  • padx、pady: 内部填充(内容与边框之间的空间)。
  • image: 显示图像(需要 PhotoImage 或 BitmapImage 对象)。

使用选项的示例:

import tkinter as tk
root = tk.Tk()
root.title("Widget Options")
# Label with specific font and color
# 带有特定字体和颜色的标签
label = tk.Label(
root,
text="Styled Label",
font=("Helvetica", 16, "italic"),
fg="blue",
bg="lightgrey",
padx=10,
pady=10,
relief=tk.RAISED,
borderwidth=2
)
label.pack(pady=10)
# Button initially disabled
# 最初禁用的按钮
button = tk.Button(root, text="Disabled Button", state=tk.DISABLED)
button.pack(pady=5)
# Function to enable the button
# 用于启用按钮的函数
def enable_button():
button.config(state=tk.NORMAL)
button['text'] = "Enabled!"
enabler_button = tk.Button(root, text="Enable Other Button", command=enable_button)
enabler_button.pack(pady=5)
root.mainloop()

几何管理器控制窗口部件在其父容器内的位置和大小。tkinter 提供了三种主要的管理器:

  • pack(): 按块组织窗口部件。窗口部件沿容器剩余空间的边缘(top 上、bottom 下、left 左、right 右)依次打包。对于基本布局很简单,但对于复杂设计可能会变得复杂。
  • grid(): 将窗口部件排列成表格状的行和列网格。对位置和对齐提供更精确的控制。对于复杂布局非常灵活。
  • place(): 允许使用绝对坐标 (x, y) 或容器内的相对位置/大小定位窗口部件。对于一般布局不太常用,因为它不太适应窗口大小调整或内容变化,但对于特定的定位需求很有用。

重要提示: 在同一个父容器内,不应混合使用 pack() 和 grid()。对于直接位于特定 Frame 或 Window 内的窗口部件,应始终使用其中一种。但是,可以在一个 Frame 内使用 pack,而在同一窗口内的另一个 Frame 内使用 grid。

label1 = tk.Label(root, text="Top", bg="red")
label1.pack(side=tk.TOP, fill=tk.X) # Fill horizontally
# 水平填充
label2 = tk.Label(root, text="Left", bg="blue")
label2.pack(side=tk.LEFT, fill=tk.Y) # Fill vertically
# 垂直填充
label3 = tk.Label(root, text="Center", bg="green")
label3.pack(expand=True, fill=tk.BOTH) # Expand to fill remaining space
# 扩展填充剩余空间
entry1 = tk.Entry(root)
entry1.grid(row=0, column=1, padx=5, pady=5)
label1 = tk.Label(root, text="Label 1:")
label1.grid(row=0, column=0, sticky=tk.E) # Align right
# 右对齐
entry2 = tk.Entry(root)
entry2.grid(row=1, column=1, padx=5, pady=5)
label2 = tk.Label(root, text="Longer Label 2:")
label2.grid(row=1, column=0, sticky=tk.E)
button = tk.Button(root, text="Submit")
# Span across 2 columns, add padding, stick to bottom-right corner within cell
# 跨越 2 列,添加内边距,在单元格内粘滞到底部右侧
button.grid(row=2, column=0, columnspan=2, pady=10, sticky=tk.SE)
button1 = tk.Button(root, text="Absolute Position")
button1.place(x=50, y=50)
button2 = tk.Button(root, text="Relative Position")
# Place at 70% width, 70% height relative to parent size
# 相对于父容器大小,放置在 70% 宽度,70% 高度处
button2.place(relx=0.7, rely=0.7, anchor=tk.CENTER) # anchor center
# 锚定中心

GUI 响应用户交互(事件),例如按钮点击、按键、鼠标移动。tkinter 使用回调和事件绑定。

  • command 选项:许多窗口部件(如 Button、Checkbutton、Radiobutton)有一个 command 选项,用于指定窗口部件被激活时要调用的函数。
  • bind() 方法:提供了一种更通用的方式,将 Python 函数(事件处理程序)绑定到任何窗口部件上的特定事件(例如 <Button-1> 用于左键单击,<KeyPress> 用于按键,<Enter> 用于鼠标进入窗口部件区域)。

使用 bind() 的示例:

import tkinter as tk
def handle_click(event):
# 'event' object contains details like coordinates (event.x, event.y)
# 'event' 对象包含坐标等详细信息 (event.x, event.y)
print(f"Left mouse button clicked at ({event.x}, {event.y})")
def handle_keypress(event):
print(f"Key pressed: '{event.char}' (Key code: {event.keycode})")
root = tk.Tk()
root.geometry("300x200")
frame = tk.Frame(root, width=200, height=100, bg="lightblue")
# Bind left mouse click event (<Button-1>) on the frame
# 将左键单击事件 (<Button-1>) 绑定到 frame 上
frame.bind("<Button-1>", handle_click)
frame.pack(pady=20)
entry = tk.Entry(root)
# Bind any key press event (<KeyPress>) on the entry widget
# 将任意按键事件 (<KeyPress>) 绑定到 entry 窗口部件上
entry.bind("<KeyPress>", handle_keypress)
entry.pack(pady=10)
entry.focus_set() # Give focus to entry so keypresses are captured
# 将焦点设置到 entry,以便捕获按键
root.mainloop()