基本小部件#

本章介绍了您几乎在任何用户界面中都会发现的基本 Tk 小部件:框架、标签、按钮、复选按钮、单选按钮、输入框和组合框。通过本章的学习,您将知道如何使用所有典型的填写表单类型用户界面所需的小部件。

Frame#

Frame 是显示为简单矩形的小部件。框架有助于组织用户界面,通常在视觉上和编码层面上都有所帮助。框架经常充当几何管理器(如 grid)的主小部件,该管理器负责管理包含在框架中的从属小部件。

frame = ttk.Frame(parent)

框架可以接受几个不同的配置选项,这些选项可以改变它们的显示方式。

请求的大小#

通常,框架的大小由其中包含的任何小部件的大小和布局决定。反过来,这由管理框架本身的内容的几何管理器控制。

如果出于某种原因,您想要不包含其他小部件的空框架,您可以使用宽度和/或高度配置选项明确设置其大小(否则,您会得到一个非常小的框架)。

屏幕距离如宽度和高度通常以像素数指定。您也可以通过几种后缀之一来指定它们。例如,350 表示 350 像素,350c 表示 350 厘米,350m 表示 350 毫米,350i 表示 350 英寸,350p 表示 350 打印点(1/72 英寸)。

填充#

填充配置选项用于请求小部件内部的额外空间。如果您在框架内放置其他小部件,四周都会有边距。您可以指定所有边的相同填充、不同的水平和垂直填充,或者单独为每一边指定填充。

f['padding'] = 5           # 5 pixels on all sides
f['padding'] = (5,10)      # 5 on left and right, 10 on top and bottom
f['padding'] = (5,7,10,12) # left: 5, top: 7, right: 10, bottom: 12 

边框#

您可以在框架小部件周围显示边框,以在视觉上将其与周围环境区分开来。您经常会看到这种效果用于使用户界面的一部分看起来凹陷或凸起。为此,您需要设置 borderwidth 配置选项(默认值为 0,即无边框)和 relief 选项,该选项指定边框的视觉效果。这可以是以下之一:平的(flat)(默认)、凸起( raised)、凹陷(sunken)、实心(solid)、脊状(ridge)或槽状(groove)。

frame['borderwidth'] = 2
frame['relief'] = 'sunken'

更改样式#

框架有样式配置选项,这是所有主题小部件共有的。这让您可以控制它们的许多其他外观或行为方面。这稍微高级一些,所以我们现在不会详细讨论。但这里有一个快速示例,创建一个带有红色背景和凸起边框的“危险”框架。

s = ttk.Style()
s.configure('Danger.TFrame', background='red', borderwidth=5, relief='raised')
ttk.Frame(root, width=200, height=200, style='Danger.TFrame').grid()

Label#

Label 是显示文本或图像的小部件,通常用户只会查看而不会与之交互。标签用于标识控件或用户界面的其他部分、提供文本反馈或结果等。

label = ttk.Label(parent, text='Full name:')

Frame 类似,标签可以接受几个不同的配置选项,这些选项可以改变它们的显示方式。

显示文本#

text配置选项(如上所示,在创建标签时使用)是最常用的,特别是当标签纯粹是装饰性或解释性的。您可以通过修改这个配置选项来更改显示的文本。这可以在任何时候进行,而不仅仅是在首次创建标签时。

您还可以让小部件监视脚本中的变量。每当变量发生变化时,标签将显示该变量的新值。这是通过 textvariable 选项实现的:

resultsContents = StringVar()
label['textvariable'] = resultsContents
resultsContents.set('New value to display')

Tkinter 只允许您将小部件附加到 StringVar 类的实例上,而不是任意的 Python 变量。这个类包含所有监视变化并在变量与 Tk 之间来回通信的逻辑。使用 getset 方法来读取或写入变量的当前值。

显示图像#

标签还可以显示图像而不是文本。如果您只想在用户界面中显示一个图像,这通常是实现方式。我们将在后面的章节中更详细地讨论图像,但现在让我们假设您想要显示存储在磁盘上的文件中的一个 GIF 图像。这是一个两步的过程。首先,您将创建一个图像“对象”。然后,您可以通过其 image 配置选项告诉标签使用该对象:

image = PhotoImage(file='myimage.gif')
label['image'] = image

标签还可以同时显示图像和文本。您经常会在工具栏按钮中看到这种情况。为此,请使用 compound 配置选项。默认值是 none,这意味着如果存在图像则仅显示图像;如果没有图像,则显示由 texttextvariable 选项指定的文本。compound 选项的其他可能值有 text(仅文本)、image(仅图像)、center(文本在图像中心)、top(图像在文本上方)、leftbottomright

字体、颜色等#

与框架一样,通常您不希望直接更改字体和颜色等。如果您需要更改它们(例如,创建特殊类型的标签),首选方法是创建一个新的样式,然后通过 style 选项将其应用于小部件。

与大多数主题小部件不同,标签小部件还提供了明确的特定于小部件的配置选项作为替代方案。同样,只有在使用样式不一定有意义的情况下才应使用这些选项。

您可以使用 font 配置选项指定用于显示标签文本的字体。虽然我们将在后面的章节中更详细地讨论字体,但这里列出了一些您可以使用的预定义字体的名称:

  • TkDefaultFont: 所有未特别指定的 GUI 项目的默认字体。

  • TkTextFont: 用于输入小部件、列表框等。

  • TkFixedFont: 标准的固定宽度字体。

  • TkMenuFont: 菜单项使用的字体。

  • TkHeadingFont: 用于列表和表格中列标题的字体。

  • TkCaptionFont: 窗口和对话框标题栏使用的字体。

  • TkSmallCaptionFont: 子窗口或工具对话框中使用的较小标题字体。

  • TkIconFont: 图标标题使用的字体。

  • TkTooltipFont: 提示信息使用的字体。

label['font'] = "TkDefaultFont"

标签的前景色(文本)和背景色也可以通过 foregroundbackground 配置选项进行更改。颜色将在后续章节详细讨论,但您可以将其指定为颜色名称(例如红色)或十六进制 RGB 代码(例如 #ff340a)。

标签还接受前面讨论的用于框架的 relief 配置选项,使其看起来凹陷或凸起。

布局#

几何管理器决定了标签的整体布局(即其在用户界面中的位置以及其大小)。然而,有几个选项可以帮助您控制在几何管理器给出的矩形内如何显示标签。

如果给标签的框比标签内容所需的要大,您可以使用 anchor 选项指定标签应该附加到哪个边缘或角落,这将在相对的边缘或角落留下任何空白空间。可能的值以指南针方向指定:n(北,或顶部边缘),ne(东北,或右上角),esesswwnwcenter

多行标签#

标签可以显示多行文本。为此,可以在 text(或 textvariable)字符串中嵌入换行符(\n)。标签还可以通过 wraplength 选项自动将文本折成多行,该选项指定一行的最大长度(以像素、厘米等为单位)。

备注

多行标签是经典 Tk 中旧版 message 小部件的替代品。

您还可以通过 justify 选项控制文本的对齐方式。它可以取值为 leftcenterright。如果您只有单行文本,可能需要使用 anchor 选项。

Button#

按钮与框架或标签不同,它主要用于交互。用户按下按钮来执行操作。像标签一样,它们可以显示文本或图像,但接受额外的选项来改变其行为。

button = ttk.Button(parent, text='Okay', command=submitForm)

通常,按钮的内容和命令回调是在创建按钮时一起指定的。与其他小部件一样,按钮接受几个配置选项来改变它们的外观和行为,包括标准样式选项。

文本或图像#

按钮接受与标签相同的 texttextvariable(很少使用)、imagecompound 配置选项。这些控制按钮是否显示文本和/或图像。

按钮有 default 配置选项。如果指定为 active 状态,这告诉 Tk 该按钮是用户界面中的默认按钮;否则,它是普通的。默认按钮在用户按下 ReturnEnter 键时被调用。一些平台和样式会以不同的边框或高亮显示这个默认按钮。注意,设置此选项不会创建事件绑定,使 ReturnEnter 键激活按钮;您必须自己完成。

命令回调#

使用 command 选项可以方便地将按钮的动作与您的应用程序连接起来。当用户按下按钮时,提供的脚本会被解释器评估执行。

您还可以从您的应用程序中请求按钮调用命令回调。这样,您就不需要在程序中多次重复要调用的命令。如果更改了按钮附加的命令,也无需在其他位置进行更改。这听起来确实是一种有用的方法,可以为我们的默认按钮添加事件绑定,不是吗?

action = ttk.Button(root, text="Action", default="active", command=myaction)
root.bind('<Return>', lambda e: action.invoke())

备注

在大多数平台上,对话框和其他窗口的标准行为是为窗口设置绑定,当按下回车键(<Return><Key-Return>)时调用活动按钮(如果存在)。如果有“关闭”或“取消”按钮,则应创建绑定到 Escape 键(<Key-Escape>)。在 macOS 上,还应将键盘上的 Enter 键(<KP_Enter>)绑定到活动按钮,并将 Command-period(<Command-.>)绑定到关闭或取消按钮。

按钮状态#

按钮和许多其他小部件一开始处于正常状态。按钮会响应鼠标移动,可以被按下,并会调用其命令回调函数。按钮也可以被置于禁用状态,此时按钮会变灰,不会响应鼠标移动,也不能被按下。当按钮的命令在特定时间点不适用时,程序会禁用该按钮。

所有主题化的小部件都维护内部状态,表示为一系列二进制标志。每个标志可以设置为开(on)或清除(off)。你可以设置或清除这些不同的标志,并使用 stateinstate 方法检查当前设置。按钮使用禁用标志来控制用户是否可以按下按钮。例如:

b.state(['disabled'])          # set the disabled flag
b.state(['!disabled'])         # clear the disabled flag
b.instate(['disabled'])        # true if disabled, else false
b.instate(['!disabled'])       # true if not disabled, else false
b.instate(['!disabled'], cmd)  # execute 'cmd' if not disabled

备注

这些命令接受一个状态标志数组作为它们的参数。

主题小部件可用的完整状态标志列表包括:active(活动)、disabled(禁用)、focus(聚焦)、pressed(按下)、selected(选中)、background(背景)、readonly(只读)、alternate(交替)和 invalid(无效)。这些在主题小部件参考中有描述。虽然所有小部件都有相同的一组状态标志,但并非所有状态对所有小部件都有意义。同时,还可以在 stateinstate 方法中进行复杂操作,同时指定多个状态标志。

备注

在 Tk 8.5 中,stateinstate 方法取代了旧的状态配置选项(该选项可取值为 normaldisabled)。实际上,这一配置选项仍然适用于主题化小部件,但仅“只写”,意味着更改此选项会调用相应的状态命令。这主要是为了方便,允许你在首次创建小部件时就指定它应被禁用。然而,使用新的状态命令所做的任何更改都不会更新配置选项。为了避免混淆,请将你的代码更新为对所有主题化小部件使用 state 标志。

Checkbutton#

复选按钮控件就像一个普通的按钮,但同时它还能保存某种形式的二进制值(即切换)。当被按下时,复选按钮会翻转该切换状态,然后调用其回调函数。复选按钮控件常用于允许用户开启或关闭某个选项。

measureSystem = StringVar()
check = ttk.Checkbutton(
    parent, text='Use Metric', 
    command=metricChanged, variable=measureSystem,
    onvalue='metric', offvalue='imperial'
)

复选框按钮使用了许多与普通按钮相同的选项,但增加了一些额外的功能。texttextvariableimagecompound 配置选项控制标签的显示(位于复选框旁边)。同样地,命令选项允许你指定一个每次用户切换复选框时调用的命令;invoke 方法也会执行相同的命令。stateinstate 方法允许你操作 disabled 状态标志以启用或禁用复选框。

组件的值#

与普通按钮不同,复选框(checkbutton)也持有值。我们已经看到如何使用 textvariable 选项将小部件的标签链接到变量。对于复选框,variable 选项的行为类似,只是它将变量链接到小部件的当前值。每当切换小部件时,变量都会更新。默认情况下,当复选框被选中时,其值为 1;未被选中时,其值为 0。可以使用 onvalueoffvalue 选项将其更改为其他值。

复选框不会自动设置(或创建)链接的变量。因此,您的程序需要将其初始化为适当的起始值。

当链接的变量既不包含 onvalue 也不包含 offvalue (甚至不存在)时会发生什么?在这种情况下,复选框会被置于一种特殊的“三态”或不确定模式。在此模式下,复选框可能会显示单独的破折号,而不是保持为空或持有勾选标记。在内部,状态标志 alternate 会被设置,您可以通过 instate 方法检查它:

check.instate(['alternate'])

虽然我们一直使用 StringVar 类的实例,但 Tkinter 还提供了可以持有布尔值、整数或浮点数的其他变量类。您可以始终使用 StringVar(因为 Tkinter 使用的 Tcl API 是基于字符串的),但如果存储的数据适合其他类型,可以选择其中之一。所有这些都是基类 Variable 的子类。

在英尺到米的示例中,我们看到您可以调用 Variableget() 方法来检索其值,或者调用 set() 方法来提供新值。在实例化时也可以提供初始值。

s = StringVar(value="abc")   # 默认值为''
b = BooleanVar(value=True)   # 默认值为False
i = IntVar(value=10)         # 默认值为0
d = DoubleVar(value=10.5)    # 默认值为0.0

Radiobutton#

单选按钮组件允许您在几个互斥选项中选择一个。与复选按钮不同,它们不限于仅两个选项。单选按钮总是成组使用,其中多个单选按钮组件绑定到一个单一的选择或偏好。当选项数量相对较少时(例如3-5个),使用单选按钮是恰当的。

phone = StringVar()
home = ttk.Radiobutton(parent, text='Home', variable=phone, value='home')
office = ttk.Radiobutton(parent, text='Office', variable=phone, value='office')
cell = ttk.Radiobutton(parent, text='Mobile', variable=phone, value='cell')

单选按钮(radiobuttons)和复选框(checkbuttons)共享大部分相同的配置选项。一个例外是,onvalueoffvalue 选项被单个值选项所替代。在一组单选按钮中,每个按钮都会有相同的链接变量但具有不同的值。当变量持有匹配的值时,相应的单选按钮会在视觉上指示它已被选中。如果不匹配,则该单选按钮将显示为未选中状态。如果链接的变量不存在,或者你未使用 variable 选项指定变量,单选按钮也会显示为“三态”或不确定状态。这可以通过 alternate 状态标志进行检查。

Entry#

一个输入小部件为用户提供了单行文本字段,他们可以在这里输入字符串值。这些值可以是各种内容,比如名字、城市、密码、社会保险号等。

username = StringVar()
name = ttk.Entry(parent, textvariable=username)

可以指定 width 度配置选项,以确定条目应占据的字符数。例如,这允许您为邮政编码或邮递区号显示较短的条目。

Entry 内容#

已经知道,单选按钮和复选框控件都有与之关联的值。输入框也是如此,这个值通常通过 textvariable 配置选项指定的链接变量来访问。

与各种按钮不同,输入框旁边没有文本或图像来标识它们。为此可以使用单独的标签控件。

你也可以不通过链接变量来获取或更改输入框的值。get() 方法返回当前值,而 delete()insert() 方法允许你更改内容,例如:

print(f'current value is {name.get()}')
name.delete(0,'end')          # 删除两个索引之间的内容,基于0的索引
name.insert(0, 'your name')   # 在给定索引处插入新文本

监测变化#

输入小部件没有提供 command 选项来在每次输入发生变化时调用回调函数。为了监测变化,你应该监视关联变量的变化。

def it_has_been_written(*args):
    ...
username.trace_add("write", it_has_been_written)

如果你使用 stick 如上所示的简单 trace_add 用法,你将没有问题。你可能想知道这是观察变量并在它们被读取、写入或删除时触发回调的更复杂系统中的很小的部分。你可以触发多个回调,添加或删除它们(trace_remove),并对它们进行内省(trace_info)。

备注

这些方法还取代了现已废弃的旧方法集(trace, trace_variable, trace_vdeletetrace_vinfo),这些旧方法不应该再被使用。

Tkinter 允许你监测 StringVar(或 Variable 的任何子类)上的变化。新旧追踪工具都是对 Tcl 的 trace 命令非常薄(且不太符合 Python 风格)的前端接口。

验证#

用户可以通过输入框输入任何文本。然而,如果您希望限制他们可以输入的内容,可以使用验证功能来实现。例如,输入框可能只接受整数或有效的邮政编码。

您的程序可以指定什么使输入有效或无效,以及何时检查其有效性。正如我们很快将看到的,这两者是相关的。我们将从简单的例子开始,一个只能容纳最多五位数字的整数输入框。

验证标准通过输入框的 validatecommand 配置选项来指定。您提供代码片段,其作用是验证输入内容。它的作用类似于小部件回调或事件绑定,只是它返回值(输入是否有效)。将在每次按键时验证输入;这是通过在 validate 配置选项中提供 key 值来实现的。

import re
def check_num(newval):
    return re.match('^[0-9]*$', newval) is not None and len(newval) <= 5
check_num_wrapper = (root.register(check_num), '%P')

num = StringVar()
e = ttk.Entry(root, textvariable=num, validate='key', validatecommand=check_num_wrapper)
e.grid(column=0, row=0, sticky='we')

有几件事值得注意。首先,与事件绑定一样,我们可以通过百分号替换访问触发验证的条件的更多信息。在这里使用了其中一个:%P 是如果验证通过,输入的新值。我们将使用简单的正则表达式和长度检查来确定更改是否有效。要拒绝更改,我们的验证命令可以返回假值,使输入保持不变。

利用这些百分号替换需要一些技巧。您会记得,Tkinter 在事件绑定回调中抽象了百分号替换。所有事件参数都被包装成传递给回调的事件对象。验证回调没有等效的抽象。相反,我们必须选择我们对哪些百分号替换感兴趣。register 方法(可以调用任何小部件,不仅仅是 root)创建 Tcl 过程,它将调用我们的 Python 函数。我们选择的百分号替换将作为参数传递给它。

让我们扩展我们的例子,使输入框接受美国邮政编码,格式为 "#####""#####-####"# 可以是任何数字)。我们仍然会在每次按键时进行一些验证(只允许输入数字或连字符)。然而,我们不能在每次按键时完全验证输入;如果他们刚刚输入第一个数字,它就还不有效。因此,完全验证只有在输入失去焦点时才会发生(例如,用户离开它时)。Tk 将这种重新验证称为重新验证,与预验证(每次按键都接受更改)相对。

我们应该如何响应错误?让我们添加一条消息提醒用户格式。如果他们输入错误的键或在输入不包含有效邮政编码时离开输入框,消息将出现。当他们返回到输入框或输入有效键时,我们将移除消息。我们还将添加一个(虚拟)按钮来“处理”邮政编码,该按钮将在邮政编码无效时禁用。最后,我们还将添加“姓名”输入框,这样您就可以从邮政编码输入框中切换。

import re
errmsg = StringVar()
formatmsg = "Zip should be ##### or #####-####"

def check_zip(newval, op):
    errmsg.set('')
    valid = re.match('^[0-9]{5}(\\-[0-9]{4})?$', newval) is not None
    btn.state(['!disabled'] if valid else ['disabled'])
    if op=='key':
        ok_so_far = re.match('^[0-9\\-]*$', newval) is not None and len(newval) <= 10
        if not ok_so_far:
            errmsg.set(formatmsg)
        return ok_so_far
    elif op=='focusout':
        if not valid:
            errmsg.set(formatmsg)
    return valid
check_zip_wrapper = (root.register(check_zip), '%P', '%V')

zip = StringVar()
f = ttk.Frame(root)
f.grid(column=0, row=0)
ttk.Label(f, text='Name:').grid(column=0, row=0, padx=5, pady=5)
ttk.Entry(f).grid(column=1, row=0, padx=5, pady=5)
ttk.Label(f, text='Zip:').grid(column=0, row=1, padx=5, pady=5)
e = ttk.Entry(f, textvariable=zip, validate='all', validatecommand=check_zip_wrapper)
e.grid(column=1, row=1, padx=5, pady=5)
btn = ttk.Button(f, text="Process")
btn.grid(column=2, row=1, padx=5, pady=5)
btn.state(['disabled'])
msg = ttk.Label(f, font='TkSmallCaptionFont', foreground='red', textvariable=errmsg)
msg.grid(column=1, row=2, padx=5, pady=5, sticky='w')

请注意,validate 配置选项已从 key 更改为 all。这意味着 validatecommand 回调不仅会在按键时被调用,还会在其他触发器上被调用。触发器通过 %V 百分号替换传递给回调函数。回调函数可以区分 key 和焦点失去(focusout)触发器(您也可以检查焦点获得,即 focusin)。

小技巧

关于验证还有一些其他需要注意的事项。首先,如果您的 validatecommand 生成错误(或不返回布尔值),那么该小部件的验证将被禁用。您的回调可以修改输入内容,例如,更改其 textvariable 属性。您可以随时通过调用其 validate 方法来要求小部件进行验证,如果验证通过,该方法将返回 true%V 百分比替换设置为 forced)。

还有 invalidcommand 配置选项(其工作方式类似于 validatecommand),每当验证失败时都会被调用。您可以使用它来完成一些棘手的事情,比如强制将焦点重新定位到未通过验证的小部件上。实际上,很少使用它。如前所述,输入框的无效状态标志(可以通过 instate invalid 方法检查)会在验证成功或失败时自动更新。

其他的百分比替换允许您获取编辑前的输入框内容(%s)、区分插入和删除(%d)、插入或删除发生的位置(%i)、正在插入或删除的内容(%S)、当前 validate 选项的设置(%v)和小部件的名称(%W)。

示例:密码#

Entry可用于密码,其中实际内容显示为项目符号或其他符号。 为此,将 "show" 配置选项设置为要显示的字符,例如 “*”。

下面直接看一个例子:

from tkinter import ttk, Tk, StringVar

if __name__ == '__main__':
    root = Tk() # 主窗口
    root.title('Entry 测试')
    root.geometry('400x300') # 设定窗口大小
    user_name = StringVar() # 记录用户名称
    user_name.set('Tom')
    # 设置字符个数不得超过 7
    name = ttk.Entry(root, textvariable=user_name, width=7) # root 上的 Entry 小部件
    name.pack()
    # 隐藏名称
    hide_name = ttk.Entry(root, textvariable=user_name, show=' ')
    hide_name.pack()
    # 隐藏为 *
    hide_name1 = ttk.Entry(root, textvariable=user_name, show='*')
    hide_name1.pack()
    root.mainloop()

显示结果为:

图3 Entry 的一个例子

示例:验证输入内容的合法性的#

我们需要利用 ttk.Entry'validate''validatecommand' 选项,检查输入的文本是否合法,具体的步骤是:

  1. 定义一个负责检查输入内容的回调函数,如果合法则返回 True,否则返回 False

  2. 使用 ttk.Entry 的方法 register 将回调函数封装为 Tcl,它会返回一个字符串,用它来设定 'validatecommand' 选项;

  3. 设置 'validate',声明调用回调函数的时机,常用的选项有:

    • 'focus':输入框获取或者失去焦点时

    • 'focusin':输入框获取焦点时

    • 'focusin':输入框失去焦点时

    • 'key':内容改变时

    • 'all':以上任何情况发生时

    • 'none':关闭内容检查,这是默认值

具体的选项描述见下表:

选项

描述

‘validate’

该选项设置是否启用内容验证

‘invalidcommand’

1. 指定当输入框输入的内容“非法”时调用的函数;2. 也就是指定当 'validatecommand' 选项指定的函数返回 False 时的函数

‘validatecommand’

1. 该选项指定一个验证函数,用于验证输入框内容是否合法;2. 验证函数需要返回 TrueFalse 表示验证结果;3. 注意,该选项只有当 'validate' 的值非 "none" 时才有效

比如,下面的代码验证输入内容是否为 “Python”:

class Window(Tk):
    def __init__(self):
        super().__init__()
        self.in_var = StringVar() # 输入变量
        self.out_var = StringVar()  # 输出变量
        self.input_entry = ttk.Entry(textvariable=self.in_var)
        self.input_entry['validate'] = "focusout"
        self.input_entry['validatecommand'] = self.test
        # 测试 self.input_entry 光标 离开之后的验证
        self.show_entry = ttk.Entry(textvariable=self.out_var)
        self._layout()
        
    def test(self):
        '''验证输入内容是否为 Python'''
        if self.input_entry.get() == 'Python':
            self.out_var.set('输入正确')
            return True
        else:
            self.out_var.set('输入错误')
            return False
        
    def _layout(self):
        self.input_entry.grid()
        self.show_entry.grid()

window = Window()
window.mainloop()

添加如下两个设置:

self.input_entry['invalidcommand'] = self.test2
def test2(self):   
    self.show_entry.insert('end', " 我被调用了......")    
    return True

便可在验证 'invalidcommand' 的使用情况。

示例:使用注册机制验证输入内容的合法性的#

我们也可以使用注册机制验证输入内容的合法性的。即使用配置选项 validatecommand=(register_func, s1, s2, ...),其中 register_func 为验证函数名,s1s2 这些是额外的选项,这些选项会作为参数依次传给 register_func 函数。下表列出额外的选项的描述:

额外选项

含义

‘%d’

操作代码:0 表示删除操作;1 表示插入操作;2 表示获得、失去焦点或 ‘textvariable’ 变量的值被修改

‘%i’

1. 当用户尝试插入或删除操作的时候,该选线表示插入或删除的位置(索引号);2. 如果是由于获得、失去焦点或 ‘textvariable’ 变量的值被修改而调用验证函数,那么该值是 -1

‘%P’

1. 当输入框的值允许改变的时候,该值有效;2. 该值为输入框的最新文本内容

‘%s’

该值为调用验证函数前输入框的文本内容

‘%S’

1. 当插入或删除操作触发验证函数的时候,该值有效;2. 该选项表示文本被插入和删除的内容

‘%v’

该组件当前的 ‘validate’ 选项的值

‘%V’

1. 调用验证函数的原因;2. 该值是 ‘focusin’,‘focusout’,‘key’ 或 ‘forced’(‘textvariable’ 选项指定的变量值被修改)中的一个

‘%W’

该组件的名字

下面看一个例子:

class Window(Tk):
    def __init__(self):
        super().__init__()
        self.in_var = StringVar() # 输入变量
        self.out_var = StringVar()  # 输出变量
        self.input_entry = ttk.Entry(textvariable=self.in_var)
        self.input_entry['validate'] = "focusout"
        self.test_cmd= self.register(self.test) # 注册
        self.input_entry['validatecommand'] = (self.test_cmd, '%P', '%v', '%W')
        # 测试 self.input_entry 光标 离开之后的验证
        self.show_label = ttk.Label(textvariable=self.out_var)
        self._layout()
        
        
    def test(self, content, reason, name):
        '''验证输入内容是否为 Python'''
        if content == 'Python':
            str1 = '输入正确\n'
            str1 += f"{content}, {reason}, {name}"
            self.out_var.set(str1)
            return True
        else:
            str1 = '输入错误\n'
            str1 += f"{content}, {reason}, {name}"
            self.out_var.set(str1)
            return False
        
    def _layout(self):
        self.input_entry.grid()
        self.show_label.grid()

window = Window()
window.mainloop()

显示效果:

图4 验证输入内容

您也可以使用类的重写功能修改验证内容,比如:

class ValidatingEntry(Entry):
    # base class for validating entry widgets

    def __init__(self, master, value="", **kw):
        apply(Entry.__init__, (self, master), kw)
        self.__value = value
        self.__variable = StringVar()
        self.__variable.set(value)
        self.__variable.trace("w", self.__callback)
        self.config(textvariable=self.__variable)

    def __callback(self, *dummy):
        value = self.__variable.get()
        newvalue = self.validate(value)
        if newvalue is None:
            self.__variable.set(self.__value)
        elif newvalue != value:
            self.__value = newvalue
            self.__variable.set(self.newvalue)
        else:
            self.__value = value

    def validate(self, value):
        # override: return value, new value, or None if invalid
        return value

Combobox#

组合框小部件将输入框和选项列表结合起来。这使得用户既可以从你提供的一组值中选择(例如,常见设置),也可以输入自己的值(例如,针对不常见的情况)。

countryvar = StringVar()
country = ttk.Combobox(parent, textvariable=countryvar)

与条目类似,textvariable 选项将程序中的变量与组合框的当前值链接起来。与其他小部件一样,您应该在自己的代码中初始化链接的变量。

组合框会产生 <<ComboboxSelected>> 虚拟事件,每当其值发生变化时,您可以绑定到此事件。(正如我们在之前讨论的几个小部件中所看到的,您还可以追踪 textvariable 的变化。由于直接绑定到事件更为直接,因此这通常是我们的首选方法。)

country.bind('<<ComboboxSelected>>', function)

预定义值#

您可以使用 values 配置选项提供一个用户可选择的值列表:

country['values'] = ('USA', 'Canada', 'Australia')

如果设置了 readonly 状态标志,用户将只能从预定义的值列表中进行选择,但不能输入自己的值(尽管如果组合框的当前值不在列表中,它也不会改变)。

country.state(["readonly"])

备注

如果您在只读模式下使用组合框,我建议当值发生变化时(即在 <<ComboboxSelected>> 事件上),调用 selection_clear 方法。如果不这样做,视觉上会显得有些奇怪。

您还可以使用 get 方法获取当前值,并使用 set 方法更改当前值(该方法接受一个参数,即新值)。

为了补充 getset 方法,您还可以使用 current 方法来确定选中的是预定义值列表中的哪个项目。调用 current 时不带任何参数;它将返回列表中的基于 0 的索引,或者如果当前值不在列表中,则返回 -1。您可以通过传递基于 0 的索引参数来选择列表中的项。

示例#

您可以使用 "current" 方法来确定在预定义值列表中选择了哪个项目。比如:

root = Tk() 
root.geometry('200x100')

label_top = ttk.Label(root, text = "选择您最喜欢的水果")
label_top.grid(column=0, row=0)

fruits = ["香蕉", '苹果', '西瓜', '葡萄', '樱桃']
combo = ttk.Combobox(root, values=fruits)

combo.grid(column=0, row=1)
combo.current(2) # 设定默认选项
root.mainloop()

显示结果为:

ttk.Combobox 设定可读状态的

如果设置 ttk.Combobox 的状态为 "readonly"(即combo['state'] = "readonly"),则无法直接修改选项的值。

"<ComboboxSelected>"#

可以使用 ttk.Combobox 的 get() 获取其当前的取值,使用 set 方法设定新的值。ttk.Combobox 会生成 "<ComboboxSelected>" 虚拟事件,提供 bind 函数使用。比如,添加如下设定:

out_var = StringVar()
out_label = ttk.Label(root, textvariable=out_var)
out_label.grid(column=0, row=2)

def callback_func(event):
    index = combo.current() # 获取当前索引
    value = combo.get() # 获取当前的值
    out_var.set(f"索引 是 {index}, 选择的值是 {value}")
    
combo.bind("<<ComboboxSelected>>", callback_func)

这样每次您选择一个选项时,便会触发虚拟事件,效果见下图:

图2 触发虚拟事件 "<>"

示例:创建英尺转换为米的工具#

代码:

from tkinter import ttk, Tk, StringVar
from tkinter import N, W, E, S

class App(ttk.Frame):
    def __init__(self, master=None):
        super().__init__(master)
        self.master = master
        self.feet = StringVar()
        self.meters = StringVar()
        self._layout()
        
        for child in self.winfo_children(): 
            child.grid_configure(padx=5, pady=5)
        
    @property
    def widgets(self):
        _widgets = [
            [ttk.Entry(self, width=14, textvariable=self.feet),
             ttk.Label(self, text="feet")],
            [ttk.Label(self, text="is equivalent to"),
             ttk.Label(self, textvariable=self.meters),
             ttk.Label(self, text="meters")],
            [ttk.Button(self, text="Calculate", command=self.calculate)]
        ]
        return _widgets
    
    def grid_layout(self):
        for n_row, row in enumerate(self.widgets):
            for n_col, widget in enumerate(row):
                widget.grid(column=n_col, row=n_row, sticky=(E, W))
    
    def _layout(self):
        # 设定 master 的 Resize
        self.master.columnconfigure(0, weight=1)
        self.master.rowconfigure(0, weight=1)
        self['padding'] = 3, 3, 12, 12
        self.grid(column=0, row=0, sticky=(N, W, E, S))
        self.grid_layout()
        
    def calculate(self, *args):
        try:
            value = float(self.feet.get())
            self.meters.set((0.3048 * value * 10000.0 + 0.5)/10000.0)
        except ValueError:
            pass

root = Tk()
root.title('Feet to Meters')
app = App(master=root)
app.mainloop()

显示结果为:

图11 英尺转换为米

为了支持 <Enter> (键盘回车)键盘触发事件,可以这样:

class App1(App):
    def __init__(self, master=None):
        super().__init__(master)
        self.widgets[0][0].focus()
        self.master.bind('<Return>', self.calculate)
# 键盘触发事件 
root = Tk()
root.title('Feet to Meters')
app = App1(master=root)
app.mainloop()

如果想要为 ttk.Frame 添加统一的配置,可以这样:

class App(ttk.Frame):
    def __init__(self):
        super().__init__()
        self.option_add("*Font", "arial 40 bold") # 添加统一的字体设定