KBEngine 内置 Python 的 Tk 图形界面编程指南:tkinter 体系、核心 API 与源码实现
KBEngine 内置 Python 的 Tk 图形界面编程指南:tkinter 体系、核心 API 与源码实现
导读
KBEngine 在 kbe/src/lib/python 中随附了一套完整的 Python 标准库源码,其中包含 tkinter(Tk 接口)全套文档与实现。本指南以该文档区入口 tk.rst 为核心骨架,系统讲解 Tk/Tcl 与 Python 的对接方式、tkinter 包的分层架构、Hello World 起步、控件布局与选项设置、事件绑定、ttk 主题控件、tix 扩展控件及配套模块。读完本文,你可以利用这套随引擎分发的 Python 环境,快速编写跨平台(Windows / Unix / macOS)的本地 GUI 工具,并能从 Lib/tkinter/ 与 Modules/_tkinter.c 的源码层面理解其工作原理。
一、Tk 图形界面文档区概览:tkinter 是什么
在 KBEngine 内置 Python 的库文档中,Graphical User Interfaces with Tk 是整个 GUI 章节的入口页。它开篇即给出定位:
Tk/Tcl 长久以来都是 Python 的组成部分。它提供了健壮且跨平台的窗口工具包,Python 程序员可以通过
tkinter包及其扩展模块tkinter.tix、tkinter.ttk来使用它。
几个关键事实(均可在 tk.rst 中找到原文依据):
tkinter是 Tcl/Tk 之上的一层薄型面向对象封装。使用tkinter不必编写 Tcl 代码,但需要查阅 Tk 文档(偶尔需要 Tcl 文档);tkinter是一组把 Tk 控件实现为 Python 类的包装器。- 底层
_tkinter模块提供线程安全机制,允许 Python 与 Tcl 相互交互;它属于内部模块,应用开发者不应直接使用。 - 优点:
tkinter速度快,而且通常随 Python 一并分发;其官方标准文档较弱,但外部参考资料充足;它一度因外观老旧而闻名,Tk 8.5 起外观已大幅改善。 - 该章节的文档骨架(toctree) 由六份子文档组成:
tkinter.rst、tkinter.ttk.rst、tkinter.tix.rst、tkinter.scrolledtext.rst、idle.rst、othergui.rst。
在仓库中,这些文档与实现一一对应:
| 文档(仓库相对路径) | 对应源码 | 主题 |
|---|---|---|
| tkinter.rst | Lib/tkinter/init.py、Modules/_tkinter.c | Tk 核心接口 |
| tkinter.ttk.rst | Lib/tkinter/ttk.py | 主题控件集 |
| tkinter.tix.rst | Lib/tkinter/tix.py | Tix 扩展控件(已弃用) |
| tkinter.scrolledtext.rst | Lib/tkinter/scrolledtext.py | 带滚动条文本控件 |
| idle.rst | Lib/idlelib/ | IDLE 集成开发环境 |
| othergui.rst | — | 其他 GUI 工具包概览 |
二、检查 tkinter 是否可用:python -m tkinter
tkinter 的标准用法文档(tkinter.rst)给出了一个最直接的安装自检命令:
python -m tkinter
从命令行运行该命令后,应当会打开一个演示简单 Tk 界面的窗口,这可以:
- 确认
tkinter已正确安装; - 显示当前安装的 Tcl/Tk 版本号,以便你查阅与该版本匹配的 Tcl/Tk 文档。
在 KBEngine 仓库中,这一能力的实现与文档是成套提供的:tkinter 包的 Python 源码位于 Lib/tkinter/,其中既有 __init__.py(tkinter 主包)也有 __main__.py(支持 python -m tkinter 的入口);C 扩展 _tkinter 的源码位于 Modules/_tkinter.c。也就是说,凡是运行在随 KBEngine 分发的这套 Python 上的脚本,都可以直接导入 tkinter 编写本地 GUI 工具。
Tkinter 的模块家族
多数时候只需要导入 tkinter 本身,但围绕它还有一批配套 Python 模块(tkinter.rst 中列出,源码均位于 Lib/tkinter/):
| 模块 | 作用 |
|---|---|
tkinter.constants |
常量定义;导入 tkinter 会自动导入它 |
tkinter.scrolledtext |
内置垂直滚动条的文本控件 |
tkinter.colorchooser |
让用户选择颜色的对话框 |
tkinter.commondialog |
其余对话框模块的基类 |
tkinter.filedialog |
打开/保存文件的通用对话框 |
tkinter.font |
字体相关的工具函数 |
tkinter.messagebox |
访问标准 Tk 对话框 |
tkinter.simpledialog |
基础对话框与便捷函数 |
tkinter.dnd |
拖放支持(实验性,未来可能被 Tk DND 取代) |
turtle |
在 Tk 窗口中绘制海龟图形 |
Tkinter.constants 是其中最重要的一个:导入 tkinter 会自动导入 tkinter.constants,因此通常只需要一条简单的导入语句即可:
import tkinter
或者更常见的写法:
from tkinter import *
三、核心类:Tk 与 Tcl
tkinter 文档定义了两个顶层工厂:
class Tk(screenName=None, baseName=None, className='Tk', useTk=1)
Tk类通常不带参数实例化,它创建 Tk 的一个顶层控件,通常是应用的主窗口;每个实例都关联一个独立的 Tcl 解释器。- 一个应用里
Tk类一般只实例化一次;即使不显式实例化,系统在实例化其他任何控件类时也会自动创建一个。
class Tcl(screenName=None, baseName=None, className='Tk', useTk=0)
Tcl工厂创建的对象与Tk类似,但不初始化 Tk 子系统。这在需要驱动 Tcl 解释器、又不想创建多余顶层窗口的环境中(例如没有 X 服务器的 Unix/Linux 系统)最有用。- 之后可以通过调用其
loadtk()方法来创建 Toplevel 窗口并初始化 Tk 子系统。
四、Tk 与 Tkinter 的分层架构
tkinter.rst 用一张自顶向下的调用链清晰地说明了 Tk 与 Tkinter 的关系:
你的应用(Python)
│ 调用 tkinter
tkinter(Python 包)
│ 解析命令与参数,转换为“像来自 Tk 脚本”的形式
_tkinter(C 扩展模块,注意下划线前缀)
│ 把命令和参数传给 C 函数
Tk Widgets(C 与 Tcl)
│ Tk 库的 C 函数;Tcl 部分负责绑定默认行为(在导入 tkinter 时执行一次)
Tk(C)
│ 最终映射到
Xlib(C)
在屏幕上绘制图形
在仓库源码中可以看到这条链的落点:_tkinter 扩展在 Modules/_tkinter.c 中通过 Tcl_CreateInterp() 创建解释器(源码第 622 行附近),并将 Tk 命令封装成 Python 可调用的对象。
Tk 命令速读:classCommand newPathname options
要理解 Tkinter,需要能读懂 Tk 脚本。Tk 脚本就是 Tcl 程序——本质上是用空格分隔的 token 列表。创建控件时命令形式固定为:
classCommand newPathname options
- classCommand:表示要创建哪种控件(按钮、标签、菜单……)。
- newPathname:控件的唯一名称。Tk 中所有名称必须唯一,控件用类似文件系统的 pathnames 命名:顶层根控件叫
.(句点),子控件用更多句点分隔,例如.myApp.controlPanel.okButton。 - options:以
-开头的标志加值的列表,配置控件外观与部分行为;多词值需加引号。
示例:
button .fred -fg red -text "hi there"
创建之后,控件的 pathname 就成为一个新的命令(widget command)。在 Tk 中对控件执行动作的写法是:
.fred someAction someOptions
而动作的合法取值取决于控件类别,例如 .fred disable 对按钮有效(按钮变灰),对标签无效(Tk 不支持禁用标签)。
把基本 Tk 映射到 Tkinter(tkinter-basic-mapping)
文档给出了一组等价映射表,这是把 Tk 经验迁移到 Tkinter 的关键:
| Tk | Tkinter |
|---|---|
button .fred |
fred = Button() |
button .panel.fred |
fred = Button(panel)(master 显式指定) |
button .fred -fg red |
fred = Button(panel, fg="red") |
.fred configure -fg red |
fred["fg"] = red 或 fred.config(fg="red") |
.fred invoke |
fred.invoke() |
pack .fred -side left |
fred.pack(side="left") |
要点:Tk 中的 class command 对应 Tkinter 的类构造器;Tk 隐式的 master 关系在 Tkinter 中通过构造参数显式指定;Tk 的 -opt val 列表在 Tkinter 中变成构造器/config 的关键字参数或字典式索引;所有 Tkinter 控件都继承自 Packer,因此都拥有 pack 系列方法。
五、Hello World:最小可运行程序
tkinter.rst 给出的经典示例,可直接在 KBEngine 内置 Python 环境中运行:
import tkinter as tk
class Application(tk.Frame):
def __init__(self, master=None):
super().__init__(master)
self.master = master
self.pack()
self.create_widgets()
def create_widgets(self):
self.hi_there = tk.Button(self)
self.hi_there["text"] = "Hello World\n(click me)"
self.hi_there["command"] = self.say_hi
self.hi_there.pack(side="top")
self.quit = tk.Button(self, text="QUIT", fg="red",
command=self.master.destroy)
self.quit.pack(side="bottom")
def say_hi(self):
print("hi there, everyone!")
root = tk.Tk()
app = Application(master=root)
app.mainloop()
这段代码演示了四个基本动作:子类化 tk.Frame 组织应用结构、用 pack() 进行布局、用字典式索引 widget["text"] 设置选项、用 command 回调把按钮点击连接到 Python 方法。
类层次速览
文档特别提醒,Tkinter 的类层次看起来很复杂,但实际使用时程序员几乎总是引用层次最底部的类。三条组织性规则:
- 某些类只是把相关函数归入同一命名空间,不应当被独立实例化;
Tk类在一个应用中只实例化一次,且通常无需显式创建;Widget类不该被实例化,它只用于被子类化产生"真正的控件"(相当于 C++ 的抽象类)。
六、选项设置的三种方式与 config() 返回值
控件选项控制颜色、边框宽度等外观与行为。设置选项有三种方式(tkinter.rst):
方式一:创建对象时用关键字参数
fred = Button(self, fg="red", bg="blue")
方式二:创建后按字典索引赋值
fred["fg"] = "red"
fred["bg"] = "blue"
方式三:用 config() 方法批量更新
fred.config(fg="red", bg="blue")
读懂 config() 的返回值:5 元组
不带参数调用 config()(或调用 keys())会返回一个字典,键是选项名(如 'relief'),值通常是 5 元组:
| 索引 | 含义 | 示例 |
|---|---|---|
| 0 | 选项名 | 'relief' |
| 1 | 数据库查找用的选项名 | 'relief' |
| 2 | 数据库查找用的选项类 | 'Relief' |
| 3 | 默认值 | 'raised' |
| 4 | 当前值 | 'groove' |
>>> print(fred.config())
{'relief': ('relief', 'relief', 'Relief', 'raised', 'groove')}
注意:某些选项(如 bg)是长名称的同义词(bg 是 background 的简写),对同义词调用 config() 返回的是 2 元组 ('bg', 'background'),而非 5 元组。Tk 手册对每个控件都区分 "STANDARD OPTIONS"(通用选项,见 options(3) 手册页)与 "WIDGET SPECIFIC OPTIONS"(该控件特有选项),例如按钮有 command 选项而标签没有。
七、布局:Packer 几何管理器
Packer 是 Tk 的几何管理机制之一,负责指定控件在其容器(master)内的相对位置。与不常用的 placer 相比,packer 接受的是定性关系描述("上方"、"左边"、"填充"等),并自动计算出精确坐标。
关键规则:
- master 的大小由其内部 slave 控件的大小决定;
- 可以把控件 pack 进 Frame,再把 Frame 层层嵌套,实现想要的布局;
- 布局会随配置的增量变化动态调整;
- 控件必须经过几何管理器(如
pack())才会显示——忘记调用几何管理器是新手最常见的错误:控件创建了,但界面上什么都没出现。
pack() 可用关键字选项控制控件在容器内的位置与窗口缩放时的行为:
fred.pack() # 默认 side = "top"
fred.pack(side="left")
fred.pack(expand=1)
Packer 选项速查
| 选项 | 合法值/含义 |
|---|---|
anchor |
锚点类型,指定每个 slave 在其 parcel 中的放置位置 |
expand |
布尔值,0 或 1 |
fill |
'x'、'y'、'both'、'none' |
ipadx / ipady |
距离,slave 控件内部每侧的内边距 |
padx / pady |
距离,slave 控件外部每侧的外边距 |
side |
'left'、'right'、'top'、'bottom' |
八、变量耦合:StringVar / IntVar / DoubleVar / BooleanVar
某些控件的当前值(如文本输入框)可以通过特殊选项直接连接到应用变量:variable、textvariable、onvalue、offvalue、value。这种连接是双向的:变量一改变,绑定它的控件立即更新。
注意实现限制:tkinter 的当前实现不允许把任意 Python 变量交给控件,只有 tkinter 中定义的 Variable 的子类可以。官方预定义了四个实用子类:StringVar、IntVar、DoubleVar、BooleanVar。读取用 get(),修改用 set(),遵循这一协议控件就会始终跟随变量。
文档示例(输入框与变量联动,回车时打印内容):
class App(Frame):
def __init__(self, master=None):
super().__init__(master)
self.pack()
self.entrythingy = Entry()
self.entrythingy.pack()
# 应用变量
self.contents = StringVar()
self.contents.set("this is a variable")
# 让 entry 控件监视该变量
self.entrythingy["textvariable"] = self.contents
# 用户按回车时打印变量值
self.entrythingy.bind('<Key-Return>',
self.print_contents)
def print_contents(self, event):
print("hi. contents of entry is now ---->",
self.contents.get())
九、窗口管理器 Wm 类
Tk 提供 wm 命令与窗口管理器交互,控制标题、位置、图标位图等。在 tkinter 中这些命令被实现为 Wm 类的方法,Toplevel 控件继承自 Wm,可直接调用这些方法。
要获取任意控件所在的顶层窗口,可以调用 _root() 方法(下划线开头表明它是实现细节,而非 Tk 功能接口;如果控件直接 pack 在 master 里,通常直接引用 master 即可,但 pack 在 Frame 内时 master 并不代表顶层窗口)。
典型用法:
import tkinter as tk
class App(tk.Frame):
def __init__(self, master=None):
super().__init__(master)
self.pack()
myapp = App()
myapp.master.title("My Do-Nothing Application")
myapp.master.maxsize(1000, 400)
myapp.mainloop()
十、Tk 选项数据类型速查
Tkinter 的选项值有固定的一套数据类型(tkinter.rst 中 "Tk Option Data Types" 一节),常用类型如下:
| 类型 | 说明与合法值 |
|---|---|
anchor |
罗盘点:"n"、"ne"、"e"、"se"、"s"、"sw"、"w"、"nw" 及 "center" |
bitmap |
8 个内建命名位图:'error'、'gray25'、'gray50'、'hourglass'、'info'、'questhead'、'question'、'warning';X bitmap 文件需完整路径并以 @ 开头,如 "@/usr/contrib/bitmap/gumby.bit" |
boolean |
整数 0/1 或字符串 "yes" / "no" |
callback |
不带参数的 Python 函数,如 fred["command"] = print_it |
color |
X 颜色名,或 "#RGB"、"#RRGGBB"、"#RRRGGGBBB"、"#RRRRGGGGBBBB" 形式的 RGB 十六进制串 |
cursor |
cursorfont.h 中的 X 光标名去掉 XC_ 前缀,如 "hand2";也可自定位图与掩码文件 |
distance |
像素数字,或带单位后缀的字符串:c(厘米)、i(英寸)、m(毫米)、p(印刷点),如 "3.5i" |
font |
Tk 列表式字体名,如 {courier 10 bold};正数尺寸单位为点,负数单位为像素 |
geometry |
widthxheight 字符串,如 fred["geometry"] = "200x100" |
justify |
"left"、"center"、"right"、"fill" |
region |
四个空格分隔的距离,如 "2 3 4 5"、"3i 2i 4.5i 2i" |
relief |
"raised"、"sunken"、"flat"、"groove"、"ridge" |
scrollcommand |
几乎总是某个滚动条的 set 方法,也可以是任意接收单参数的控件方法 |
wrap |
"none"、"char" 或 "word" |
十一、事件绑定与 bind() 方法
控件命令中的 bind 方法用于监听特定事件并在事件发生时触发回调,其签名如下:
def bind(self, sequence, func, add=''):
三个参数:
- sequence:字符串,表示目标事件类型(如
"<Enter>"); - func:接收一个参数的 Python 函数,事件发生时被调用,参数是一个
Event实例(这类函数通常叫 callbacks); - add:可选,
''或'+'。传空串表示本次绑定替换该事件已有的绑定;传'+'表示把函数追加到该事件类型的绑定列表。
示例:
def turn_red(self, event):
event.widget["activeforeground"] = "red"
self.button.bind("<Enter>", self.turn_red)
回调中通过 event.widget 访问捕获到 X 事件的控件。事件字段与 Tk 记号 %x 的对应关系(查阅 Tk 手册时非常有用):
| Tk | Tkinter 事件字段 | Tk | Tkinter 事件字段 |
|---|---|---|---|
%f |
focus |
%A |
char |
%h |
height |
%E |
send_event |
%k |
keycode |
%K |
keysym |
%s |
state |
%N |
keysym_num |
%t |
time |
%T |
type |
%w |
width |
%W |
widget |
%x |
x |
%X |
x_root |
%y |
y |
%Y |
y_root |
十二、index 参数:Entry / Text / Menu 中的位置引用
许多控件需要 "index" 参数来指向特定位置——Text 控件中的位置、Entry 控件中的字符、Menu 控件中的菜单项:
Entry 控件索引:Entry 有指向文本中字符位置的选项,可用 tkinter 函数访问这些特殊点。
Text 控件索引:索引记号非常丰富,最详细的说明见 Tk 手册页。
Menu 索引(menu.invoke()、menu.entryconfig() 等)可传入:
- 整数:条目在菜单中的数字位置,从顶部开始、从 0 计数;
- 字符串
"active":当前位于光标下的菜单位置; - 字符串
"last":最后一个菜单项; @前缀整数,如@6:菜单坐标系中的 y 像素坐标;- 字符串
"none":表示没有任何菜单项,常用于menu.activate()停用全部条目; - 文本字符串:从菜单顶部向下与条目 label 做模式匹配;该类型最后才被考虑,因此 label 恰好为
last、active、none的条目可能被解释为上文中的字面量。
十三、图像:BitmapImage 与 PhotoImage
不同格式的图像通过 tkinter.Image 的对应子类创建:
BitmapImage:XBM 格式;PhotoImage:PGM、PPM、GIF 和 PNG 格式(PNG 自 Tk 8.6 起支持)。
两种图像都通过 file 或 data 选项创建。图像对象可被用在任何支持 image 选项的控件上(标签、按钮、菜单等)。注意引用管理:Tk 不会保留对图像的引用——当最后一个 Python 引用被删除时,图像数据随之删除,界面上原本使用该图像的地方会显示空框。因此图像对象需要在 Python 侧保持引用(例如保存为实例属性)。
十四、文件句柄回调:createfilehandler
Tk 允许注册/注销一个回调函数,在文件描述符可进行 I/O 时由 Tk mainloop 调用;每个文件描述符只能注册一个处理器。示例(tkinter.rst 中 tkinter-file-handlers 一节):
import tkinter
widget = tkinter.Tk()
mask = tkinter.READABLE | tkinter.WRITABLE
widget.tk.createfilehandler(file, mask, callback)
# ...
widget.tk.deletefilehandler(file)
方法语义:
Widget.tk.createfilehandler(file, mask, func):注册回调。file可以是带fileno()方法的对象(文件或 socket 对象),也可以是整数文件描述符;mask是下面三个常量的 OR 组合;回调以callback(file, mask)形式被调用。Widget.tk.deletefilehandler(file):注销文件句柄回调。
常量 READABLE、WRITABLE、EXCEPTION 用于构成 mask。该特性在 Windows 上不可用。
一个实践警告:由于无法预知可读字节数,不要使用 io.BufferedIOBase / io.TextIOBase 的 read() 或 readline()(它们坚持读取预定数量的字节);对 socket 用 recv()/recvfrom() 没问题,对其他文件应使用原始读取或 os.read(file.fileno(), maxbytecount)。
在仓库源码中,该方法对应 Modules/_tkinter.c 中的 _tkinter_tkapp_createfilehandler_impl(源码第 2624 行附近),印证了文档所述的注册语义。
十五、配套模块详解:ScrolledText 与更多
tkinter.scrolledtext
tkinter.scrolledtext.rst 介绍 ScrolledText 类:一个"自动配置正确滚动行为"的基础文本控件——内置垂直滚动条,比手动组合 Text + Scrollbar 简单得多。构造函数与 tkinter.Text 相同;文本控件与滚动条被打包在一个 Frame 中,因此 Grid/Pack 几何管理器的方法直接从该 Frame 对象获得。
需要更精细控制时,可访问两个公开属性:
| 属性 | 说明 |
|---|---|
ScrolledText.frame |
包裹文本与滚动条控件的 Frame |
ScrolledText.vbar |
滚动条控件 |
源码位于 Lib/tkinter/scrolledtext.py。
tix:Tk 扩展控件(已弃用)
tkinter.tix.rst 说明 tkinter.tix(Tk Interface Extension)提供了标准 Tk 所缺的常用控件:HList、ComboBox、Control(即 SpinBox)以及一批可滚动控件,总量超过 40 个,另有 NoteBook、FileEntry、PanedWindow 等通用控件。
重要提示:文档明确标注该扩展自 Python 3.6 起已弃用——"This Tk extension is unmaintained and should not be used in new code. Use tkinter.ttk instead."(该 Tk 扩展已无人维护,新代码不应使用,请改用 tkinter.ttk)。
若确需使用,导入方式为:
from tkinter import tix
from tkinter.constants import *
root = tix.Tk()
tix.Tk() 是代表应用主窗口的 Toplevel 控件,带关联 Tcl 解释器;tix 模块子类化 tkinter 中的类并导入后者,因此只需导入 tix 一个模块。使用前提是 Tix 控件已随 Tk 安装,可用下面代码验证:
from tkinter import tix
root = tix.Tk()
root.tk.eval('package require Tix')
十六、ttk:Tk 8.5 主题控件集
tkinter.ttk 提供 Tk 8.5 引入的主题控件集(tkinter.ttk.rst)。若 Python 未针对 Tk 8.5 编译,只要安装了 Tile 也能访问该模块;而 Tk 8.5 原生支持还额外带来 X11 下的抗锯齿字体渲染和窗口透明(后者需要 X11 合成窗口管理器)。
核心思想:尽可能把实现控件行为的代码与实现外观的代码分离。源码见 Lib/tkinter/ttk.py。
使用与导入
from tkinter import ttk
若要覆盖基础 Tk 控件,导入顺序应为:
from tkinter import *
from tkinter.ttk import *
这会让多个 ttk 控件(Button、Checkbutton、Entry、Frame、Label、LabelFrame、Menubutton、PanedWindow、Radiobutton、Scale、Scrollbar)自动替换同名 Tk 控件,获得跨平台更一致的外观。注意兼容性差异:fg、bg 等样式相关选项在 Ttk 控件中不再存在,应改用 ttk.Style 类实现样式效果。
18 个 ttk 控件
| 类别 | 控件 |
|---|---|
| 12 个原 tkinter 已有 | Button、Checkbutton、Entry、Frame、Label、LabelFrame、Menubutton、PanedWindow、Radiobutton、Scale、Scrollbar、Spinbox |
| 6 个新增 | Combobox、Notebook、Progressbar、Separator、Sizegrip、Treeview |
全部都是 Widget 的子类。
样式编码对比
Tk 写法:
l1 = tkinter.Label(text="Test", fg="black", bg="white")
l2 = tkinter.Label(text="Test", fg="black", bg="white")
Ttk 写法(用命名样式 "BW.TLabel" 复用外观):
style = ttk.Style()
style.configure("BW.TLabel", foreground="black", background="white")
l1 = ttk.Label(text="Test", style="BW.TLabel")
l2 = ttk.Label(text="Test", style="BW.TLabel")
标准选项与滚动选项
所有 ttk 控件接受以下标准选项:
| 选项 | 说明 |
|---|---|
class |
指定窗口类,用于查询选项数据库、决定默认 bindtags、选择默认布局与样式;只读,仅创建时可用 |
cursor |
鼠标光标;默认空串表示继承父控件光标 |
takefocus |
键盘遍历时是否接受焦点:0 跳过、1 接收、空串由遍历脚本决定 |
style |
指定自定义控件样式 |
受滚动条控制的控件还支持 xscrollcommand / yscrollcommand:视图变化时控件会基于该选项生成 Tcl 命令,通常把它设为某个 Scrollbar 的 set 方法,滚动条即自动同步。
控件状态标志
ttk 控件的状态是独立状态位的位图,常用标志:
| 标志 | 含义 |
|---|---|
active |
鼠标悬停且按下会触发动作 |
disabled |
程序控制下禁用 |
focus |
拥有键盘焦点 |
pressed |
正在被按下 |
selected |
Checkbutton/Radiobutton 的"开/真/当前"状态 |
background |
窗口在后台(Windows/Mac 概念) |
readonly |
不允许用户修改 |
alternate |
控件专属的备选显示格式 |
invalid |
控件值无效 |
状态规格(statespec)是状态名序列,可用 ! 前缀表示该位关闭。
ttk.Widget 的核心方法:
identify(x, y):返回 (x, y) 处元素名,不在任何元素内则返回空串;instate(statespec, callback=None, *args, **kw):无回调时按 statespec 匹配返回True/False,有回调时匹配则调用;state(statespec=None):传参则按规格设置状态并返回变化的标志,不传则返回当前启用的标志。
十七、ttk 主要控件速查
Combobox(下拉选择框)
ttk.Combobox 把文本框与弹出列表组合,是 Entry 的子类。专用选项:
| 选项 | 说明 |
|---|---|
exportselection |
布尔值,选择是否关联 Window Manager 选择 |
justify |
"left" / "center" / "right" |
height |
弹出列表的行数 |
postcommand |
显示列表前立即调用的脚本,可指定要显示的取值 |
state |
"normal"(可编辑)/ "readonly"(只能从下拉选)/ "disabled"(不可交互) |
textvariable |
与控件值双向链接的变量名(见 tkinter.StringVar) |
values |
下拉列表显示的值列表 |
width |
输入窗口期望宽度(按控件字体的平均字符数) |
用户从列表选择时会生成 <<ComboboxSelected>> 虚拟事件。方法:current(newindex=None)(设置/查询当前项索引,不在列表中返回 -1)、get()、set(value)。
Spinbox(微调框)
ttk.Spinbox 是带增减箭头的 Entry,可用于数字或字符串值列表。专用选项:
| 选项 | 说明 |
|---|---|
from |
最小值(作参数时必须写 from_,因为 from 是 Python 关键字) |
to |
最大值 |
increment |
增减步长,默认 1.0 |
values |
若指定,箭头在序列中循环而不是增减数字 |
wrap |
布尔值,True 时在 to/from 之间循环 |
format |
数字格式,形如 "%W.Pf"(W 为宽度、P 为精度) |
command |
按箭头时被调用的无参 Python 可调用对象 |
按 <Up>/<Down> 分别生成 <<Increment>> / <<Decrement>> 虚拟事件。方法:get()、set(value)。
Notebook(选项卡)
ttk.Notebook 管理一组窗口并一次只显示一个,每个子窗口关联一个可点击切换的标签页。
- 选项:
height/width(pane 区域尺寸)、padding(四周留白,最多 4 个长度值,省略时 bottom 取 top、right 取 left、top 取 left)。 - Tab 选项:
state("normal"/"disabled"/"hidden")、sticky("n"/"s"/"e"/"w"组合)、padding、text、image、compound、underline(下划线字符索引,配合enable_traversal()做助记键激活)。 tab_id的合法形式:0 到页数间的整数、子窗口名、"@x,y"位置规格、字面量"current"、字面量"end"(仅index()返回页数)。- 切换标签后生成
<<NotebookTabChanged>>虚拟事件。 - 常用方法:
add(child, **kw)、forget(tab_id)、hide(tab_id)、identify(x, y)、index(tab_id)、insert(pos, child, **kw)、select(tab_id=None)、tab(tab_id, option=None, **kw)、tabs()、enable_traversal()。 enable_traversal()启用键盘遍历:Control-Tab切到下一页、Shift-Control-Tab切到上一页、Alt-K(K 为下划线助记字符)直达对应页。
Progressbar(进度条)
ttk.Progressbar 显示长耗时操作的状态,两种模式:
- determinate(确定):显示已完成量占总工作量的比例;
- indeterminate(不确定):动画显示"正在推进"。
选项:orient("horizontal"/"vertical")、length、mode、maximum(默认 100)、value、variable(链接的变量名)、phase(只读,供主题做动画)。方法:start(interval=None)(默认每 50 毫秒 step 一次)、step(amount=None)(默认 +1.0)、stop()。
Separator 与 Sizegrip
Separator:水平/垂直分隔条,只有orient一个专用选项,无专用方法。Sizegrip(grow box):拖拽调整所在 toplevel 窗口大小,无专用选项/方法。注意事项:macOS 顶层窗口默认自带内建 grip;若窗口位置相对屏幕右/下边缘指定,Sizegrip 无法缩放窗口;只支持"东南向"缩放。
Treeview(树形表格)
ttk.Treeview 显示层级化条目集合:每条目有文本标签、可选图像、可选数据值列表;数据值依次显示在树标签之后的列中。可用 displaycolumns 控制数据列显示顺序与列标题。条目以唯一名称标识(不指定则自动生成);存在一个特殊根条目 {},自身不显示,其子条目出现在层级顶层。每条目还可带 tags,用于绑定事件和控制外观。
主要选项:columns、displaycolumns(或 "#all")、height(可见行数)、padding、selectmode("extended" 默认可多选 / "browse" 单选 / "none" 不改选择)、show("tree"/"headings",默认全部;列 #0 恒为树列)。
条目选项:text、image、values(与列数对应,多则忽略、少则补空)、open、tags。标签选项:foreground、background、font、image。
列标识符三种形式:columns 列表中的符号名、整数 n(第 n 个数据列)、#n 字符串(第 n 个显示列)。
虚拟事件:<<TreeviewSelect>>(选择变化)、<<TreeviewOpen>>(焦点条目展开前)、<<TreeviewClose>>(收起后)。
常用方法(节选):
bbox(item, column=None)、get_children(item=None)、set_children(item, *newchildren);column(column, option=None, **kw):列选项含id(只读)、anchor、minwidth、stretch、width,树列用column("#0", ...);heading(column, option=None, **kw):含text、image、anchor、command;identify(component, x, y)、identify_row(y)、identify_column(x)、identify_region(x, y)(返回heading/separator/tree/cell,Tk 8.6 起)、identify_element(x, y)(Tk 8.6 起);insert(parent, index, iid=None, **kw)、item(item, option=None, **kw)、move(item, parent, index)、reattach(item, parent, index)(move别名)、next/prev/parent/see;selection()系列:selection_set、selection_add、selection_remove、selection_toggle(注意:旧式selection()修改选择已在 3.6 弃用、3.8 移除);set(item, column=None, value=None)、tag_bind、tag_configure、tag_has(Tk 8.6 起)、xview/yview。
十八、Ttk 样式体系:Style 类与 Layouts
每个 ttk 控件都分配一个样式(style),它定义组成控件的一组元素、元素排列方式以及元素选项的动态/默认设置。默认样式名与控件类名相同,可用 style 选项覆盖;不确定类名时用 Misc.winfo_class()(即 somewidget.winfo_class())查询。
Style 类操作样式数据库,核心方法:
configure(style, query_opt=None, **kw):查询或设置样式的默认选项值。例如把默认按钮改为扁平、带内边距、灰底:
from tkinter import ttk
import tkinter
root = tkinter.Tk()
ttk.Style().configure("TButton", padding=6, relief="flat",
background="#ccc")
btn = ttk.Button(text="Sample")
btn.pack()
root.mainloop()
map(style, query_opt=None, **kw):查询或设置样式的动态值。每个键是选项,值一般是包含 statespec 的列表/元组。statespec 的顺序有讲究:
import tkinter
from tkinter import ttk
root = tkinter.Tk()
style = ttk.Style()
style.map("C.TButton",
foreground=[('pressed', 'red'), ('active', 'blue')],
background=[('pressed', '!disabled', 'black'), ('active', 'white')]
)
colored_btn = ttk.Button(text="Test", style="C.TButton").pack()
root.mainloop()
如果把 foreground 的顺序换成 [('active', 'blue'), ('pressed', 'red')],结果会变成 active/pressed 状态下前景色均为蓝色——因此 (states, value) 序列的顺序决定匹配优先级。
lookup(style, option, state=None, default=None):查询样式中的选项值。例如:
from tkinter import ttk
print(ttk.Style().lookup("TButton", "font"))
layout(style, layoutspec=None):定义/查询样式布局。布局规格是序列(非字符串),每项为 (layoutname, layout) 元组。文档示例(重建 TMenubutton 布局):
from tkinter import ttk
import tkinter
root = tkinter.Tk()
style = ttk.Style()
style.layout("TMenubutton", [
("Menubutton.background", None),
("Menubutton.button", {"children":
[("Menubutton.focus", {"children":
[("Menubutton.padding", {"children":
[("Menubutton.label", {"side": "left", "expand": 1})]
})]
})]
}),
])
mbtn = ttk.Menubutton(text='Text')
mbtn.pack()
root.mainloop()
element_create(elementname, etype, *args, **kw):在当前主题创建元素,etype 为 "image"、"from" 或 "vsapi"(后者仅 Tk 8.6a 的 Windows XP/Vista 可用)。"image" 时 args 为默认图像名加 statespec/value 对,kw 可带 border(四边整数 padding)、height、padding、sticky("n"/"s"/"w"/"e" 组合)、width;"from" 时克隆已有元素。
主题管理:theme_create(themename, parent=None, settings=None)、theme_settings(themename, settings)(临时切换主题应用 settings 后恢复)、theme_names()、theme_use(themename=None)(设置/查询当前主题,切换时刷新所有控件并发出 <<ThemeChanged>> 事件)。
theme_settings 示例——微调默认主题的 Combobox:
from tkinter import ttk
import tkinter
root = tkinter.Tk()
style = ttk.Style()
style.theme_settings("default", {
"TCombobox": {
"configure": {"padding": 5},
"map": {
"background": [("active", "green2"),
("!disabled", "green4")],
"fieldbackground": [("!disabled", "green3")],
"foreground": [("focus", "OliveDrab1"),
("!disabled", "OliveDrab2")]
}
}
})
combo = ttk.Combobox().pack()
root.mainloop()
Layouts 布局机制:布局可为 None,或是指定元素排布方式的 dict;它使用 pack 几何管理器的简化版:给定初始 cavity,每个元素被分配一个 parcel。合法键:side(top/right/bottom/left,省略则占满整个 cavity)、sticky(元素在其 parcel 内的位置)、unit(0/1,为 1 时元素及其后代被当作单元素处理,用于带手柄的滚动条滑块等)、children(子元素列表,每项为 (layoutname, layout) 元组)。
十九、IDLE:基于 tkinter 的 Python IDE
tkinter.rst 所在章节的 toctree 还包含 idle.rst,介绍 IDLE——Python 自带的集成开发与学习环境。它与 tkinter 的关系是实现级的:IDLE 用 100% 纯 Python 编写,基于 tkinter GUI 工具包;跨平台(Windows、Unix、macOS 行为基本一致)。源码位于 Lib/idlelib/。
主要特性:
- Python shell 窗口(交互式解释器),代码输入、输出、错误信息均带着色;
- 多窗口文本编辑器:多级撤销、Python 着色、智能缩进、call tips、自动补全等;
- 任意窗口内搜索、编辑器内替换、多文件搜索(grep);
- 支持持久断点、单步、查看全局/局部命名空间的调试器;
- 配置、浏览器与其他对话框。
IDLE 有两类主窗口:Shell 窗口与 Editor 窗口(可同时打开多个编辑器窗口)。输出窗口(如 "Edit => Find in Files")是编辑器窗口的子类型。
二十、其他 GUI 工具包横向概览
othergui.rst 罗列了 Python 可用的其他跨平台 GUI 工具包,供对比选型:
| 工具包 | 说明 |
|---|---|
| PyGObject | 为基于 GObject 的 C 库提供内省绑定,其中之一是 GTK+ 3 控件集;控件数量远多于 Tkinter |
| PyGTK | 较老版本 GTK+ 2 的绑定,提供略高于 C 层的面向对象接口,另有 GNOME 绑定 |
| PyQt | 基于 sip 封装 Qt 工具包(Unix/Windows/macOS 可用的 C++ GUI 框架) |
| PySide | 较新的 Qt 绑定,许可证对比 PyQt 对非开源应用更友好 |
| wxPython | 基于 wxWidgets C++ 工具包,尽量使用各平台原生控件,实现 Windows/macOS/Unix 的原生外观 |
文档指出:PyGTK、PyQt、wxPython 的外观与控件数量都胜过 Tkinter;更完整的清单见 Python Wiki 的 "GUI Programming" 页。
二十一、仓库中的实现证据:随附源码与扩展模块
以上内容并非空谈——本仓库中 tkinter 的完整实现与文档是一一对应的:
- Python 层实现:Lib/tkinter/ 目录下有
__init__.py、constants.py、scrolledtext.py、colorchooser.py、commondialog.py、dialog.py、dnd.py、filedialog.py、font.py、messagebox.py、simpledialog.py、tix.py、ttk.py以及__main__.py入口,文档中描述的每一个控件与选项都能在对应源码中找到实现。 - C 扩展层:Modules/_tkinter.c 是
_tkinter扩展的源码,内含Tcl_CreateInterp()(第 622 行附近创建 Tcl 解释器)与_tkinter_tkapp_createfilehandler_impl(第 2624 行附近实现文件句柄回调注册)等关键逻辑,印证了文档所述"线程安全机制允许 Python 与 Tcl 交互"以及"内部模块不应被直接使用"的定位;配套的 Modules/tkinter.h 与 Modules/tkappinit.c 提供初始化支持。 - 测试代码:Lib/tkinter/test/ 存放
tkinter的测试套件,可帮助你在修改/阅读源码时验证行为。
对使用 KBEngine 内置 Python 编写运维或开发辅助工具的开发者而言,这意味着无需额外安装即可获得一整套文档齐备、源码在手的 GUI 开发能力;编写纯本地(本机窗口)的工具时可直接依赖 tkinter,需要更现代外观时优先选择 ttk,而 tix 仅建议在维护遗留代码时使用。
小结
围绕 tk.rst 这一 Tk 图形界面章节入口,本文完整覆盖了 tkinter 的定位与架构、Tk/Tcl 核心类、Tk 到 Tkinter 的映射、Hello World、选项设置与 5 元组返回值、Packer 布局、变量耦合、窗口管理器、选项数据类型、事件绑定、index 参数、图像与文件句柄回调,以及 scrolledtext、tix、ttk(含 Style 样式体系)、IDLE 与其他 GUI 工具包。仓库内 Lib/tkinter/ 与 Modules/_tkinter.c 提供了从 Python 封装到 C 扩展的完整实现证据,你可以对照文档逐行验证,并在此基础上自由扩展自己的 GUI 工具。