Tk 概念#

Tk 所需的三个广泛概念:小部件、几何管理和事件处理。

小部件#

小部件是所有你在屏幕上看到的东西。小部件通常被称为“控件”。有时你也会看到它们被称为“窗口”,特别是在 Tk 的文档中。这是从其 X11 根源遗留下来的(在该术语下,你的顶级应用程序窗口和按钮都被称为窗口)。

所有小部件都提供 Misc 和几何管理(geometry management)方法,配置管理(configuration management)方法以及由微件本身定义的其他方法。另外,Toplevel 类还提供了窗口管理器界面(window manager interface)。

Mixins#

tkinter 模块提供了与 Tk 中的各种窗口小部件类型相对应的类,以及许多 Mix-In 和其他帮助程序类(mixin 是旨在使用多重继承与其他类组合的类)。使用 tkinter 时,切勿直接访问 mixin 类。

Misc 由根窗口和小部件类用作 mixin。 它提供了大量与 Tk 和窗口相关的服务,因此可用于所有 tkinter 核心小部件。 这是由 \(delegation\) 完成的; 小部件仅将请求转发到适当的内部对象。

Wm 类被根窗口(root window)和 Toplevel 小部件类用作 mixin。 它也通过委派 (delegation) 提供窗口管理器服务。

像这样使用委派简化了您的应用程序代码:一旦有了小部件,就可以使用小部件实例上的方法访问 tkinter 的所有部分。

小部件类将 GridPackPlace 类用作 mixin。 它们还可以通过委派来访问各种几何管理器。

  • Grid:Grid 几何管理器允许您通过在二维网格中组织小部件来创建类似表格的布局。 要使用此几何图形管理器,请使用 grid 方法。

  • Pack:Pack 几何管理器允许您通过将小部件“打包”到父部件中(将它们视为放置在框架中的矩形块)来创建布局。要将此几何图形管理器用于窗口小部件,请使用该窗口小部件上的 pack 方法进行设置。

  • Place:Place 几何图形管理器使您可以将小部件明确放置在给定位置。要使用此几何图形管理器,请使用 place 方法。

创建小部件#

每个单独的小部件都是 Python 对象。实例化小部件时,你必须将其父级作为参数传递给小部件类。唯一的例外是“根”窗口,即包含所有其他内容的顶层窗口。当你实例化 Tk 时,它会自动创建。它没有父级。例如:

root = Tk()
content = ttk.Frame(root)
button = ttk.Button(content)

是否将小部件对象保存在变量中完全取决于你,这取决于你以后是否需要引用它。由于对象被插入到小部件层次结构中,即使你不保留自己的引用,它也不会被垃圾回收。

如果你偷看一下 Tcl 如何管理小部件,你会发现每个小部件都有一个特定的路径名;你还会在 Tk 参考文档中看到这个路径名的引用。Tkinter 在幕后为你选择和管理所有这些路径名,所以你永远不必担心它们。如果你需要,可以通过调用 str(widget) 来获取小部件的路径名。

使用 str(widget) 获取该 widget 的层级结构(有点类似于文件的路径):

str(root), str(content), str(button)

显示结果为:

    ('.', '.!frame', '.!frame.!button')

可以看出 root 是最顶层的,其次是 content,最后是 button

配置选项#

所有小部件都有一些配置选项,用于控制小部件的显示方式或行为。

当然,小部件的可用选项取决于小部件类。不同小部件类之间有很多一致性,所以执行类似功能的一些选项通常具有相同的名称。例如,按钮和标签都有一个文本选项来调整小部件显示的文本,而滚动条则没有文本选项,因为它不需要。同样,按钮有一个命令选项,告诉它在被按下时做什么,而标签只持有静态文本,因此没有这个选项。

可以在创建小部件时通过指定它们的名称和值作为可选参数来设置配置选项。稍后,你可以检索这些选项的当前值,并在极少数例外情况下随时更改它们。

如果你不确定小部件支持哪些配置选项,可以要求小部件描述它们。这会给出它的所有选项的长列表。

所有这些都可以通过与解释器的以下交互式对话框最好地说明。

>>> from tkinter import *
>>> from tkinter import ttk
>>> root = Tk()
create a button, passing two options:
>>> button = ttk.Button(root, text="Hello", command="buttonpressed")
>>> button.grid()
check the current value of the text option:
>>> button['text']
'Hello'
change the value of the text option:
>>> button['text'] = 'goodbye'
another way to do the same thing:
>>> button.configure(text='goodbye')
check the current value of the text option:
>>> button['text']
'goodbye'
get all information about the text option:
>>> button.configure('text')
('text', 'text', 'Text', '', 'goodbye')
get information on all options for this widget:
>>> button.configure()
{'cursor': ('cursor', 'cursor', 'Cursor', '', ''), 'style': ('style', 'style', 'Style', '', ''), 
'default': ('default', 'default', 'Default', <index object at 0x00DFFD10>, <index object at 0x00DFFD10>), 
'text': ('text', 'text', 'Text', '', 'goodbye'), 'image': ('image', 'image', 'Image', '', ''), 
'class': ('class', '', '', '', ''), 'padding': ('padding', 'padding', 'Pad', '', ''), 
'width': ('width', 'width', 'Width', '', ''), 
'state': ('state', 'state', 'State', <index object at 0x0167FA20>, <index object at 0x0167FA20>), 
'command': ('command', 'command' , 'Command', '', 'buttonpressed'), 
'textvariable': ('textvariable', 'textVariable', 'Variable', '', ''), 
'compound': ('compound', 'compound', 'Compound', <index object at 0x0167FA08>, <index object at 0x0167FA08>), 
'underline': ('underline', 'underline', 'Underline', -1, -1), 
'takefocus': ('takefocus', 'takeFocus', 'TakeFocus', '', 'ttk::takefocus')}

您可以看到,对于每个选项,Tk都会显示该选项的名称及其当前值(以及其他三个通常可以忽略的属性)。

好的,如果您真的想知道,这里是每个配置选项提供的五项数据的详细信息。最有用的两项是第一项,即选项的名称,以及第五项,即选项的当前值。第四项是选项的默认值,换句话说,如果您没有更改它,它将具有的值。其他两项与所谓的选项数据库有关。当我们讨论菜单时会简要提到它,但它在现代应用程序中并不使用。第二项是选项在数据库中的名称,第三项是它的类别。

小部件自省#

Tk 提供了关于每个小部件的丰富信息,您的应用程序可以利用这些信息。其中大部分信息可以通过 winfo 设施获得。

这个简短的例子遍历了小部件层次结构,使用每个小部件的 winfo_children 方法来识别需要检查的子小部件。对于每个小部件,我们打印一些基本信息,包括其类别(按钮、框架等)、宽度、高度以及相对于其父小部件的位置。

def print_hierarchy(w, depth=0):
    print('  '*depth + w.winfo_class() + ' w=' + str(w.winfo_width()) + ' h=' + str(w.winfo_height()) + ' x=' + str(w.winfo_x()) + ' y=' + str(w.winfo_y()))
    for i in w.winfo_children():
        print_hierarchy(i, depth+1)

print_hierarchy(root)

以下是一些最有用的方法:

winfo_class

标识小部件类型的类,例如 TButton 表示主题按钮。

winfo_children

层次结构中某个小部件的直接子小部件列表。

winfo_parent

层次结构中小部件的父小部件。

winfo_toplevel

包含此小部件的顶级窗口。

winfo_width, winfo_height

小部件的当前宽度和高度;在屏幕上显示之前可能不准确。

winfo_reqwidth, winfo_reqheight

小部件请求几何管理器的宽度和高度(稍后会详细介绍)。

winfo_x, winfo_y

小部件相对于其父小部件的左上角位置。

winfo_rootx, winfo_rooty:

小部件相对于整个屏幕的左上角位置。

winfo_vieweable

小部件是否显示或隐藏(层次结构中的所有祖先小部件都必须可见,它才能被看到)。

几何管理#

如果您已经运行过交互式代码,您可能已经注意到,仅仅通过创建小部件并不能使它们在屏幕上显示。将小部件放置在屏幕上(以及精确放置的位置)是一个单独的步骤,称为几何管理。

在我们的示例中,每个小部件的定位是通过 grid 命令完成的。我们指定了我们希望每个小部件所在的列和行、网格中的对齐方式等。Grid 是几何管理器的一个示例(Tk 中有几种几何管理器,grid 是最有用的之一)。现在,我们将总体看一下几何管理;我们将在后面的章节讨论 grid。

几何管理器的任务是准确地确定这些小部件将被放在哪里。这实际上是一个复杂的优化问题,一个好的几何管理器依赖于相当复杂的算法。一个好的几何管理器提供了灵活性、功能强大且易于使用的特点,这使得程序员感到满意。它还使得无需费尽周折就能轻松创建吸引人的用户界面布局。毫无疑问,Tk 的 grid 是绝对最好的几何管理器之一。

事件处理#

与大多数用户界面工具包一样,Tk 运行事件循环,该循环从操作系统接收事件。这些事件包括按钮按下、键盘敲击、鼠标移动、窗口调整大小等。

通常,Tk 会为您管理这个事件循环。它会确定事件适用于哪个小部件(用户是否点击了这个按钮?如果按下了键,哪个文本框有焦点?),并相应地派发事件。各个小部件知道如何响应事件;例如,当鼠标移到按钮上时,按钮可能会改变颜色,当鼠标离开时恢复原状。

命令回调#

您通常希望您的程序以特定方式处理某些事件,例如在按下按钮时执行某些操作。对于这些最常自定义的事件(如果没有在按下按钮时发生某些事情,按钮还有什么用呢?),小部件将允许您将其指定为小部件配置选项的回调。我们在按钮的示例中看到了这一点。

def calculate(*args):
    ...

ttk.Button(mainframe, text="Calculate", command=calculate)

在 Tk 中的回调通常比在与编译语言一起使用的用户界面工具包中的回调要简单(其中回调必须是一个具有特定参数集的过程或具有特定签名的对象方法)。相反,回调只是解释器评估的普通代码片段。虽然它可以像您想要的那样复杂,但大多数时候,您只是想让您的回调调用其他过程。

绑定到事件#

对于没有小部件特定命令回调关联的事件,您可以使用 Tk 的 bind 来捕获任何事件,然后(像回调一样)执行任意代码片段。

示例:显示标签响应不同的事件。当事件发生时,事件的描述会显示在标签中。

from tkinter import *
from tkinter import ttk
root = Tk()
l =ttk.Label(root, text="Starting...")
l.grid()
l.bind('<Enter>', lambda e: l.configure(text='Moved mouse inside'))
l.bind('<Leave>', lambda e: l.configure(text='Moved mouse outside'))
l.bind('<ButtonPress-1>', lambda e: l.configure(text='Clicked left mouse button'))
l.bind('<3>', lambda e: l.configure(text='Clicked right mouse button'))
l.bind('<Double-1>', lambda e: l.configure(text='Double clicked'))
l.bind('<B3-Motion>', lambda e: l.configure(text='right button drag to %d,%d' % (e.x, e.y)))
root.mainloop()

前两个绑定非常简单,只是监视简单事件。<Enter> 事件意味着鼠标移动到小部件上,而 <Leave> 事件是在鼠标移动到另一个不同的小部件外部时生成的。

下一个绑定查找鼠标点击事件,具体是 <ButtonPress-1> 事件。这里,<ButtonPress> 是实际事件,但 -1 是事件细节,指定鼠标上的主按钮(左键)。该绑定仅在生成涉及主鼠标按钮的 <ButtonPress> 事件时触发。如果单击了另一个鼠标按钮,则此绑定会忽略它。

下一个绑定查找 <3> 事件。这实际上是 <ButtonPress-3> 的简写。它会响应右键单击事件。下一个绑定 <Double-1>(即 <Double-ButtonPress-1> 的简写)添加了另一个修饰符 Double,因此它将响应左键双击事件。

最后一个绑定也使用了修饰符:捕获鼠标移动(Motion),但仅当按住右键(B3)时。这个绑定还展示了如何使用事件参数的示例。许多事件携带额外的信息,例如鼠标点击时的位置。Tk 通过使用百分号替换(percent substitutions)在 Tcl 回调脚本中提供对这些参数的访问。这些百分号替换允许您捕获它们以便在脚本中使用。

Tkinter 抽象掉了这些百分号替换,而是将所有事件参数封装在一个事件对象中。在上面,我们使用了 x 和 y 字段来检索鼠标位置。稍后我们将在另一个上下文中看到百分号替换的使用,即条目小部件验证。

小技巧

这些 Lambda 表达式是怎么回事?Tkinter 期望您提供一个函数作为事件回调,该函数的第一个参数是一个表示触发回调的事件的事件对象。有时,为一次性的琐碎回调定义常规的命名函数不值得,比如在这个例子中。相反,我们只是使用了 Python 的匿名函数(通过 lambda)。在实际应用中,您几乎总是会使用一个常规函数,例如我们在英尺到米示例中的 calculate 函数,或者一个对象的方法。

tkinter 希望您将函数作为事件回调,其第一个参数是表示触发回调的事件的事件对象(格式为 <modifier-type-detail>)。

  • type:是 event 的核心,通常用于描述事件的类型,比如点击鼠标

  • modifier:event 的可选部分,通常用于描述事件的组合键,例如 Ctrl + S

  • detail:event 的可选部分,通常用于描述事件的具体按键,例如 Button-3 表示鼠标右键

下面简单介绍几个事件:

事件

描述

<Button-1>

鼠标事件,1 代表左键,可简写为 <1>

<Button-2>

鼠标事件,2 代表中键,可简写为 <2>

<Button-3>

鼠标事件,3 代表右键,可简写为 <3>

<Button-4>

鼠标事件,4 代表鼠标滚轮上滚

<Button-5>

鼠标事件,5 代表鼠标滚轮下滚

<B1-Motion>

鼠标拖动事件,1 代表左键拖动,还有 2, 3 分别代表中键拖动,右键拖动

<ButtonRelease-1>

鼠标按下后释放

<Double-Button-1>

双击鼠标左键

<Enter>

表示鼠标指针进入到 widget 里面

<Leave>

表示鼠标指针离开 widget

<KeyPress-D>

用户点击按键 D

<Control-Shift-KeyPress-Y>

用户点击组合键 Ctrl + Shift + Y

事件对象的属性:

属性

描述

widget

产生的事件的组件

x, y

当前的鼠标相对于窗口的位置,单位:像素

x_root, y_root

当前的鼠标相对于电脑显示屏左上角的位置,单位:像素

char

仅键盘事件支持,表示按键对应的字符

num

按钮数字,仅支持鼠标事件

width, height

widget 的大小

type

事件类型

对于所有不同的事件名称、修饰符和每个事件参数的完整描述,最好查看的是"绑定"命令引用

bind 还有两个变体:

  1. bind_all:参数同 bind,作用是绑定到全局

  2. bind_class:有 3 个参数,即 (类名, 事件类型, 对应的操作),比如: app.bind_class('ttk.Entry', '<Control-C>', my_copy) 绑定了所有的输入文本框的 Ctrl + C 表示复制

事件绑定的多重性#

我们刚刚看到了如何为单个小部件设置事件绑定。当该小部件接收到匹配的事件时,绑定将被触发。但这还不是全部功能。

您的绑定不仅可以捕获单一事件,还可以捕获一系列简短的事件序列。<Double-1> 绑定在短短的时间内发生两次鼠标点击时触发。您可以做同样的事情来捕获按顺序按下的两个键,例如 <KeyPress-A><KeyPress-B> 或简单地 <ab>

您还可以在顶层窗口上设置事件绑定。当在该窗口中的任何地方发生匹配事件时,绑定将被触发。在我们的示例中,我们在主应用程序顶层窗口上设置了 Return 键的绑定。如果在任何顶层窗口中的任何小部件具有焦点时按下 Return 键,该绑定将被触发。

较少见的是,您可以创建在整个应用程序中的任何地方发生匹配事件时触发的事件绑定,甚至针对给定类的所有小部件接收到的事件,例如所有按钮。

小技巧

一个事件可以触发多个绑定。这使事件处理程序简洁且范围有限,意味着代码更具模块化。例如,Tk 中每个小部件类的行为本身是通过脚本级事件绑定定义的。这些绑定与应用程序中的事件绑定是分开的。事件绑定也可以更改或删除。它们可以被修改以改变特定类的小部件或应用程序部分的事件处理方式。您可以重新排序、扩展或更改每个小部件将触发的事件绑定序列;如果您感兴趣,请查看 bindtags 命令参考。

好的,我会将你提供的段落翻译为中文,内容如下:

可用事件 下面描述了最常用的事件以及它们生成的情况。有些事件在某些平台上生成,而在其他平台上不生成。要查看所有不同事件名称、修饰符和每个事件可用的不同参数的完整描述,最好的地方是 bind 命令参考。

<Activate>

窗口已变为活动状态。 <Deactivate>

窗口已被停用。 <MouseWheel>

鼠标滚轮被移动。 <KeyPress>:

键盘上的键被按下。 <KeyRelease>

按键被释放。 <ButtonPress>

鼠标按钮被按下。 <ButtonRelease>

鼠标按钮被释放。 <Motion>

鼠标被移动。 <Configure>

小部件的大小或位置已改变。 <Destroy>

小部件正在被销毁。 <FocusIn>

小部件已获得键盘焦点。 <FocusOut>

小部件已失去键盘焦点。

<Enter>

鼠标指针进入小部件。

<Leave>:

鼠标指针离开小部件。 鼠标事件的详细情况是按下的按钮,例如 1、2 或 3。对于键盘事件,它是具体的键,例如 A、9、空格、加号、逗号、等号。完整的列表可以在 keysyms 命令参考中找到。

事件修饰符可以包括,例如 B1 或 Button1 表示按住主鼠标按钮,Double 或 Triple 表示相同事件的序列。当键盘上的键被按住时,可以使用 Control、Shift、Alt、Option 和 Command 作为键修饰符。

虚拟事件#

到目前为止我们看到的事件是低级别的操作系统事件,例如鼠标点击和窗口调整大小。许多小部件还生成更高级别的、语义化的虚拟事件。这些事件在事件名称周围用双尖括号表示,例如 <<foo>>

例如,列表框小部件在其选择更改时会生成一个 <<ListboxSelect>> 虚拟事件。无论用户是通过单击项目、使用箭头键移动到它还是其他方式,都会生成相同的虚拟事件。虚拟事件避免了设置多个可能特定于平台的事件绑定以捕获常见变化的问题。小部件可用的虚拟事件将在该小部件类的文档中列出。

Tk 还为不同平台触发的不同方式定义了常见的操作虚拟事件。这些包括 <<Cut>><<Copy>><<Paste>>

您可以定义自己的虚拟事件,这些事件可以特定于您的应用程序。这是一种有用的方法,可以在单个模块中隔离特定于平台的细节,同时在您的应用程序中使用虚拟事件。您的代码可以生成以与 Tk 生成的虚拟事件完全相同的方式工作的虚拟事件。

root.event_generate("<<MyOwnEvent>>")