窗口与对话框#

到目前为止,我们所完成的所有操作都是在一个单一的窗口中进行的。在本章中,我们将学习如何使用多个窗口、改变窗口的各种属性,并使用 Tk 中提供的一些标准对话框。

创建和销毁窗口#

我们已经看到,所有的 Tk 程序都从根顶层窗口开始,然后在这个根窗口下创建小部件作为其子项。创建新的顶层窗口与创建新的小部件几乎完全相同。

顶层窗口是通过 Toplevel 类来创建的:

t = Toplevel(parent)

注意:Toplevel 是经典Tk小部件的一部分,不是主题化的小部件。

与常规小部件不同,我们不需要将顶层窗口进行网格布局才能使其显示在屏幕上。一旦我们创建了一个新的顶层窗口,我们就可以在该顶层窗口内部创建其他小部件,并将它们进行网格布局。新创建的顶层窗口的行为与自动创建的根窗口完全相同。

要销毁窗口,使用它的 destroy 方法:

window.destroy()

注意,您可以对任何小部件使用 destroy 方法,不仅仅是顶层窗口。当您销毁窗口时,所有属于该窗口的子窗口(小部件)也会被销毁。小心!如果您销毁了根窗口(所有其他小部件都源于此),这将终止您的应用程序。

备注

在典型的文档导向应用程序中,我们希望允许关闭一个窗口而保留其他窗口打开。在这种情况下,我们可能希望为每个窗口创建一个新顶层窗口,并且根本不在根窗口内直接放置任何东西。虽然我们不能仅仅销毁根窗口,但我们可以使用其 withdraw 方法将其完全从屏幕上移除,稍后我们将看到这一点。

顶层窗口是使用 Toplevel 函数创建的:

t = Toplevel(parent)

与常规窗口小部件不同,您无需“网格化”(grid)顶层即可将其显示在屏幕上。 这里的 parent 可以是 Tk,ttk.Frame 等。 一旦创建了新的顶层(toplevel),就可以创建属于该顶层的子级的其他小部件,并将它们网格化到 toplevel 中。 换句话说,新的顶层行为与自动创建的根窗口(Tk)几乎完全一样。

Toplevel 与 Tk 之间的具体差异可以不用去管,只需知道 Toplevel 被称为顶层窗口,Tk 被称为根窗口即可。具体来讲,Tk 是全部窗口的“根”,如果被销毁,则 Toplevel 也会被销毁,反之,则不立。下面以一个例子来说明:

root = Tk()
root.geometry('200x100+50+100')

def create_toplevel():
    top = Toplevel()
    top.title('一个 toplevel')
    msg = ttk.Label(top, text='这是一个顶级窗口')
    msg.grid()

def create_root():
    root = Tk()
    root.title('一个 tk')
    msg = ttk.Label(root, text='这是一个根窗口')
    msg.grid()

top_button = ttk.Button(root, text='创建顶级窗口', command=create_toplevel)
root_button = ttk.Button(root, text='创建根窗口', command=create_root)
top_button .grid(row=0, column=0)
root_button.grid(row=0, column=1)
root.mainloop()

要销毁窗口,可以在小部件上使用 destroy 方法:

window.destroy()

显示的效果是:

图1 Toplevel 与 Tk

如果关闭图1 的 “1” 窗口,则 “2” 会被关闭,“3” 不会被关闭;关闭 “2”,则 “1”,“3”均不会被关闭;关闭“3”,则 “1”,“2” 均不会被关闭。

请注意,您可以在任何窗口小部件上使用 destroy ,而不仅仅是顶层窗口。 另外,销毁窗口时,作为该窗口子级的所有窗口(窗口小部件)也将被销毁。 因此,如果您碰巧破坏了根窗口(所有其他小部件均来自此根窗口),则通常会结束您的应用程序。

窗口行为与样式#

关于窗口的行为和外观,有许多方面是可以改变的。

窗口标题#

要检查或更改窗口的标题:

oldtitle = window.title()
window.title('New title')

尺寸与位置#

在Tk中,窗口在屏幕上的位置和大小被称为其几何属性。一个完整的几何规范看起来像这样:widthxheight±x±y

宽度和高度(通常以像素为单位)是相当直观的。X(水平位置)通过一个前导的加号或减号来指定,因此+25意味着窗口的左边缘应距离屏幕左边缘25像素,而-50意味着窗口的右边缘应距离屏幕右边缘50像素。类似地,Y(垂直)位置为+10意味着窗口的顶部边缘应在屏幕上方十像素处,而-100意味着窗口的底部边缘应在屏幕底部上方100像素处。

备注

几何属性指定了屏幕上的实际坐标。它不会为像macOS这样的系统留出菜单栏或底座的空间。因此,指定位置为+0+0 实际上会将窗口的上部放置在系统菜单栏下方。最好从屏幕边缘保留至少30像素的健康边距。

当您的系统中有多个显示器时,屏幕位置可能与您预期的不同。我们将稍后讨论这一点。

下面是改变尺寸和位置的一个示例。它将窗口放置在屏幕右上角:

window.geometry('300x200-5+40')

您可以用同样的方式检索当前几何属性;只是不要提供新的几何值。然而,如果您立即在更改几何属性后尝试这样做,您会发现它不匹配。记住,所有绘图实际上是在后台响应空闲时间通过事件循环进行的。直到进行该绘图之前,窗口的内部几何属性不会被更新。如果您确实想要立即强制更新,您可以这样做。

window.update_idletasks()
print(window.geometry())

备注

我们已经看到,窗口默认为其中网格化的控件所请求的大小。如果我们在解释器中交互式地创建并添加新控件,或者响应其他事件添加新控件,窗口大小会调整。这种行为将持续到我们显式地提供上述窗口的几何属性或用户调整窗口大小为止。此时,即使我们添加更多控件,窗口的大小也不会改变。您需要确保使用 grid 的所有功能(例如,sticky, weight),以便使一切适配得很好。

调整大小的行为#

默认情况下,包括根窗口在内的顶层窗口可以被用户调整大小。然而,有时您可能希望阻止用户调整窗口大小。您可以通过resizable方法做到这一点。其第一个参数控制用户是否可以更改宽度,第二个参数控制他们是否可以更改高度。因此,要禁用所有调整大小:

window.resizable(FALSE, FALSE)

如果窗口是可调整大小的,您可以指定最小和/或最大尺寸,您希望窗口的大小受到限制(同样,参数是宽度和高度):

window.minsize(200, 100)
window.maxsize(500, 500)

您之前已经看到了如何通过其几何属性获取窗口的当前大小。想知道如果您没有指定其几何属性,或者用户没有调整它的大小,它会有多大吗?您可以检索窗口请求的大小,即它从几何管理器请求多少空间。就像绘图一样,几何计算只在事件循环中的空闲时间进行,所以直到控件出现在屏幕上,您才不会得到有用的响应。

window.winfo_reqwidth()   # 或 winfo_reqheight

备注

您可以在任何控件上使用reqwidth和reqheight方法,而不仅仅是顶层窗口。还有其他winfo方法可以调用在任何控件上,比如widthheight,以获取实际(非请求)的宽度和高度。更多信息,请参阅winfo命令参考。

拦截关闭按钮#

大多数窗口的标题栏中都有一个关闭按钮。默认情况下,如果用户点击该按钮,Tk会销毁窗口。然而,你可以提供一个回调函数来代替执行默认操作。一个常见的用途是在文件被修改后,提示用户保存打开的文件。

window.protocol("WM_DELETE_WINDOW", callback)

名称略显晦涩的WM_DELETE_WINDOW起源于X11窗口管理器协议。

透明度#

可以通过指定 alpha 通道使窗口部分透明,范围从 0.0 (完全透明)到 1.0(完全不透明)。

window.attributes("-alpha", 0.5)

Tkinter对底层wm属性命令的包装器不会解释选项、处理关键字参数等。

在macOS上,你还可以指定一个-transparent属性(与-alpha相同的机制),这会使窗口背景透明并移除阴影。你还应该将窗口和任何框架的背景配置选项设置为系统透明色。

全屏#

你可以让窗口扩展到全屏:

window.attributes("-fullscreen", 1)

其他macOS特定属性#

除了上述的-transparent属性外,macOS窗口还有一些额外的属性。

标题栏中的(红色)关闭小部件可以指示窗口内的内容已被修改(例如,文件需要保存)。通过设置-modified属性为1来指示这一点,或设为0以移除修改指示符。

你可以通过在macOS dock中弹跳其图标来吸引用户对窗口的注意。为此,请设置窗口的-notify属性。

如果窗口包含文档内容,你可以在标题栏中放置一个图标,指定文档所引用的文件。用户可以将此图标作为在Finder中拖动文件的代理。设置窗口的-titlepath属性为文件的完整路径。请注意,这不会改变窗口的标题(你需要单独更改),只是提供图标。

在macOS上,窗口也可以根据不同目的采取各种外观,例如,实用程序窗口、模态对话框、浮动窗口等。Tk中一个不受支持的命令MacWindowStyle允许你为窗口分配其中一种外观。与Tk中的许多可后期更改的选项不同,这些外观必须在创建窗口后但在其出现在屏幕上之前分配。

t = Toplevel(root)
t.tk.call("::tk::unsupported::MacWindowStyle", "style", t._w, "utility")

除了实用程序外,其他有用的外观样式还包括浮动、普通和模态。

备注

虽然官方不支持,但这个功能在Tk中已经存在很长时间了。将来,它可能会迁移到wm属性命令。有关更多信息,包括不同外观和可选属性的更多细节,请参阅MacWindowStyle维基页面。

窗口图标化来临时将其从屏幕上移除#

在大多数系统中,您可以通过将窗口图标化来临时将其从屏幕上移除。在Tk中,无论窗口是否被图标化,都称为窗口的状态。窗口的可能状态包括正常和图标化(对于已图标化的窗口),以及其他几种状态:隐藏、图标或缩放。

您可以直接查询或设置当前窗口状态。还有 iconifydeiconifywithdraw 方法;这些是设置图标化、正常和隐藏状态的快捷方式。

thestate = window.state()
window.state('normal')
window.iconify()  # 最小化
window.withdrawn() # 隐藏
window.deiconify() # 恢复

对于以文档为中心的应用程序,如果您希望允许关闭任何窗口而不退出应用程序(就像销毁根窗口时会发生的那样),请使用根窗口上的withdraw方法将其从屏幕上移除,然后为用户界面使用新的顶层窗口。

堆叠顺序#

堆叠顺序指的是窗口在屏幕上“放置”的顺序,从底部到顶部。当两个窗口的位置重叠时,堆叠顺序中更接近顶部的窗口会遮挡或覆盖堆叠顺序中较低的窗口。

您可以确保一个窗口始终位于堆叠顺序的顶部(或至少在所有未设置此属性的其他窗口之上):

window.attributes("-topmost", 1)

您可以找到当前的堆叠顺序,从最低到最高列出:

root.tk.call('wm', 'stackorder', root._w)

这个方法在Tkinter中没有干净地暴露出来。它返回每个窗口的内部名称,而不是窗口对象。

您还可以检查一个窗口是否在另一个窗口之上或之下:

if (root.tk.call('tk', 'isabove', str(otherwindow)) == '1'): ...
if (root.tk.call('tk', 'isbelow', str(otherwindow)) == '1'): ...

您也可以提升或降低窗口,无论是到堆叠顺序的最顶部(底部)还是仅高于(低于)指定的窗口:

window.lift()
window.lift(otherwin)
window.lower()
window.lower(otherwin)

Tkinter使用name lift,因为raise是Python中的保留关键字。

为什么需要传递一个窗口来获取堆叠顺序?堆叠顺序不仅适用于顶层窗口,还适用于具有相同父级的任何同级小部件(那些在同一网格中但相互重叠的小部件)。如果您有多个网格在一起但相互重叠的小部件,可以相对彼此提升和降低它们:

from tkinter import *
from tkinter import ttk
root = Tk()
little = ttk.Label(root, text="Little")
bigger = ttk.Label(root, text='Much bigger label',
                   foreground='blue', font='Arial 15')
little.grid(column=0, row=0)
bigger.grid(column=0, row=0)
root.after(2000, lambda: little.lift())
root.mainloop()

这使用了定时事件,我们在事件循环章节中已经讨论过。after 命令安排脚本在未来的一定毫秒数内运行,但让事件循环继续。

效果图:

图2 堆叠 widgets

屏幕信息#

我们之前使用winfo命令查找特定小部件的信息。它也可以提供关于整个显示器或屏幕的信息。通常,参见winfo命令参考以获取详细信息。

例如,您可以确定屏幕的颜色深度(每像素多少位)和颜色模型(通常在现代显示器上为truecolor),像素密度和分辨率。

print("color depth=" + str(root.winfo_screendepth())+ " (" + root.winfo_screenvisual() + ")")
print("pixels per inch=" + str(root.winfo_pixels('1i')))
print("width=", str(root.winfo_screenwidth()) + " height=", str(root.winfo_screenheight()))

多显示器#

虽然通常不需要关注这一点,但如果您的系统上有多个显示器并且想要进行一些自定义操作,Tk有一些工具可以帮助您。

首先,有两种方法可以表示多个显示器。第一种是逻辑上分开的显示器。这通常是在X11系统上的情况,尽管它可以更改,例如使用xrandr系统实用程序。这种模型的缺点是一旦在屏幕上创建了一个窗口,就不能移动到另一个屏幕上。您可以确定Tk窗口运行在哪个屏幕上,这看起来像是一个X11格式的显示名称,例如:0.0。

root.winfo_screen()

当首次创建顶层窗口时,可以通过屏幕配置选项指定它应该创建在哪个屏幕上。

不同的显示器可能有不同的分辨率、颜色深度等。您会发现我们刚刚涵盖的所有屏幕信息调用都是针对特定小部件的方法。它们将返回有关该窗口所在屏幕的信息。

或者,多个显示器也可以表示为一个大虚拟显示器,这是在macOS和Windows上的情况。当您询问关于屏幕的信息时,Tk将返回主监视器的信息。例如,如果您有两个全高清显示器并排放置,屏幕分辨率将报告为1920 x 1080,而不是3840 x 1080。这可能是好事;这意味着如果我们正在定位或调整窗口大小,我们不必担心多个显示器,一切都会正确显示在主监视器上。

如果用户将窗口从一个主显示器移动到另一个显示器,如果询问其位置,它将相对于主显示器。因此,在我们并排放置的FHD显示器设置中,如果在靠近某个显示器边缘的位置调用窗口的winfo_x方法,它可能会返回100(如果在主显示器上),-1820(如果在主显示器左侧的显示器上),或2020(如果在主显示器右侧的显示器上)。您仍然可以使用我们之前看到的geometry方法将窗口放置在不同显示器上,即使几何规范看起来有点奇怪,例如,±1820+100。

您可以大致了解整个显示器的大小,跨越多个显示器。为此,检查顶级窗口的最大大小,即用户可以调整其大小的大小(当然,一旦您已经更改了它,就无法这样做)。这可能比显示器的完整尺寸略小。例如,在macOS上,它将减少屏幕上方菜单栏的大小。

root.wm_maxsize()

对话框#

对话框是一种在应用程序中使用的窗口类型,用于从用户那里获取信息、通知用户某些事件的发生、确认一个动作等。对话框的外观和用途通常在一个平台的风格指南中非常具体地描述。Tk 提供了一些内置的对话框,用于常见任务。这些帮助你遵循特定于平台的风格指导方针。

选择文件和目录#

Tk 提供几个对话框让用户选择文件或目录。在 Windows 和 macOS 上,这些直接调用底层操作系统的对话框。对话框中的“打开”变体用于当你希望用户选择一个现有文件时(比如在“文件 | 打开…”菜单命令中),而“保存”变体用于选择要保存的文件(通常由“文件 | 另存为…”菜单命令使用)。

from tkinter import filedialog
filename = filedialog.askopenfilename() # 打开文件
filename = filedialog.asksaveasfilename() # 保存文件
dirname = filedialog.askdirectory() # 打开目录

所有这些命令都产生模态对话框。这意味着,直到用户提交对话框,这些命令才会完成。这些命令返回用户选择的文件或目录的完整路径名,或者如果用户取消对话框,则返回空字符串。

可以将各种不同的选项传递给这些对话框,从而允许您设置允许的文件类型,默认文件名等。 这些在getOpenFile/getSaveFilechooseDirectory 参考手册页面中进行了详细说明。

选择颜色#

另一个模态对话框允许用户选择一个颜色。它会返回一个颜色值,例如 #ff62b8。这个对话框接受一个可选的 initialcolor 参数来指定一个现有的颜色,即用户可能想要替换的颜色。更多信息可以在 chooseColor 参考手册页中找到。

from tkinter import colorchooser
colorchooser.askcolor(initialcolor='#ff0000')

选择字体#

Tk 8.6新增了对另一个系统对话框的支持:字体选择器。虽然文件对话框和颜色选择器是模态对话框,会一直阻塞直到对话框被关闭并返回结果,但字体选择器的工作原理不同。

备注

由于Tk 8.5中没有提供字体选择器,如果你的代码需要支持更旧版本的Tk,你将需要考虑这一点。

尽管在某些平台(例如Windows)上系统字体对话框是模态的,但并非所有地方都是这样。在macOS上,系统字体选择器更像是绘图程序中的浮动工具栏,它一直可用,以便更改主应用程序窗口中选定文本的字体。Tk字体对话框API必须适应这两种模式。为此,它使用回调(和虚拟事件)来通知您的应用程序字体更改。更多详细信息可以在fontchooser参考手册页面中找到。

要使用字体对话框,首先提供一个初始字体和一个回调函数,当选择一个字体时将调用该回调函数。为了说明这一点,我们将使回调函数更改标签上的字体。

l = ttk.Label(root, text="Hello World", font="helvetica 24")
l.grid(padx=10, pady=10)

def font_changed(font):
    l['font'] = font

root.tk.call('tk', 'fontchooser', 'configure', '-font', 'helvetica 24', '-command', root.register(font_changed))
root.tk.call('tk', 'fontchooser', 'show')

Tkinter 尚未添加使用此新对话框的便捷方式,因此本示例代码直接使用了 Tcl API。您可以在 Issue#28694 查看最新的 Python API 进展并下载代码。

备注

您可以随时查询或更改将在对话框中显示的字体。

接下来,通过 show 方法将对话框显示在屏幕上。在字体对话框为模态的平台,您的程序将在此处阻塞,直到对话框被关闭。在其他平台上,show 方法会立即返回;对话框会在您的程序继续运行的同时保持显示。此时尚未选择字体。hide 方法用于将其从屏幕上移除(当字体对话框为模态时,这并不太有用)。

root.tk.call('tk', 'fontchooser', 'show')
root.tk.call('tk', 'fontchooser', 'hide')

如果字体对话框是模态的,并且用户选择了字体,对话框将调用您的回调函数,并传递一个字体规范。如果他们取消了对话框,则不会有回调。当对话框不是模态且用户选择字体时,也会调用您的回调函数。还会生成一个 <<TkFontchooserFontChanged>> 虚拟事件;您可以通过对话框的字体配置选项检索当前字体。如果关闭了字体对话框,则会生成一个 <<TkFontchooserVisibility>> 虚拟事件。您还可以通过可见配置选项(尽管更改它是错误的;请使用 showhide 方法)了解字体对话框是否当前在屏幕上可见。

由于平台之间的显著差异,在所有平台上提供良好的用户体验需要一些工作。在字体对话框为模态的平台,它可能从按钮或菜单项调用,例如“字体…”。在其他平台上,按钮或菜单项应在“显示字体”和“隐藏字体”之间切换。

备注

如果您的应用程序中有多个文本小部件可以设置不同的字体,当其中一个获得焦点时,应更新字体选择器以显示其当前字体。这也意味着来自字体对话框的回调可能会应用于与您最初调用 show 的文本小部件不同的文本小部件!最后,请注意在某些平台上,字体选择器本身可能会获得键盘焦点。

小技巧

截至 Tk 8.6.10,各种平台上的字体选择器存在一些错误。以下是快速概述,包括解决方法:

  • 在 macOS 上,如果您未通过字体配置选项提供字体,您的回调函数将不会被调用 ⇒ 始终提供一个初始字体

  • 在 X11 上,如果您未为所有配置选项提供值,那些未包含的配置选项将被重置为其默认值 ⇒ 每当您更改任何选项时,更改所有选项,即使只是将其设置为当前值

  • 在 X11 上,当您提供回调函数时,字体对话框会包含 Apply 按钮,但当您不提供回调函数时会省略它(只需监视虚拟事件);然而,其他错误意味着这些虚拟事件永远不会生成 ⇒ 始终提供一个命令回调

  • 在 Windows 上,您也可以通过不提供回调函数来省略 Apply 按钮;虽然在字体更改时会生成虚拟事件,但字体配置选项永远不会更新 ⇒ 始终提供一个命令回调,并自行保存字体,而不是尝试稍后从字体对话框获取它

  • 在 Windows 上,如果您在代码中更改字体配置选项,不会生成字体更改虚拟事件,尽管在 macOS 和 X11 上会生成 ⇒ 在代码中更改字体时采取必要的操作,而不是在虚拟事件处理程序中处理

由于平台之间的差异以及各种错误,在使用字体选择器时测试比使用其他系统对话框更为重要。

警告和确认对话框#

许多应用程序使用各种简单的模态窗口或对话框来通知用户事件,请求他们确认操作,或者通过点击按钮做出其他类似的选择。Tk 提供了一个多功能的“消息框”,它封装了所有这些不同类型的对话框。

from tkinter import messagebox
messagebox.showinfo(message='Have a good day')
messagebox.askyesno(
	   message='Are you sure you want to install SuperVirus?'
	   icon='question' title='Install')

就像我们之前看到的对话框一样,这些对话框是模态的,会将用户操作的结果返回给调用者。 确切的返回值取决于传递给命令的 “type” 选项,如下所示:

Type option

Possible return values

ok (default)

“ok”

okcancel

“ok” or “cancel”

yesno

“yes” or “no”

yesnocancel

“yes”, “no” or “cancel”

retrycancel

“retry” or “cancel”

abortretryignore

“abort”, “retry” or “ignore”

tkinter不使用 “type” 选项,而是为每种类型的对话框使用不同的方法名称。 可用的方法有:askokcancel,askquestion,askretrycancel,askyesno,askyesnocancel,showerror,showinfo,showwarning。

可能的选项的完整列表如下所示:

type

As described above.

message

The main message displayed inside the alert.

detail

A secondary message (if needed).

title

Title for the dialog window. Not used on macOS.

icon

Icon, one of “info” (default), “error”, “question” or “warning”.

default

Default button, e.g. “ok” or “cancel” for a “okcancel” dialog.

parent

Window of your application this dialog is being posted for.

窗口示例#

root = Tk()

# top level window
root.title('Toplevel Window')
root.geometry('300x300')
ttk.Label(root, text='我是主顶层窗口\n 这里的所有其他窗口都是我的孩子').pack()


# child toplevel
child_toplevel = Toplevel(root)
ttk.Label(child_toplevel, text='我是 root 的孩子\n 如果我散焦(focus),\
      我可能隐藏在顶层以下(toplevel), \n 如果根被毁灭了,我也会被毁灭了').pack()
child_toplevel.geometry('400x100+300+300')


# transient window
transient_toplevel = Toplevel(root)
ttk.Label(transient_toplevel, text='我是一个临时的根窗口\n 我总是站在父母的顶端\
     \n如果我的父窗口最小化,则隐藏').pack()
transient_toplevel.transient(root)


# no window decoration
no_window_decoration = Toplevel(root, bg='black')
ttk.Label(no_window_decoration, text='我是没有窗口管理器的顶级\n 我无法调整大小或移动',
          background='black', foreground='white').pack()
no_window_decoration.overrideredirect(1)
no_window_decoration.geometry('250x100+700+500')

root.mainloop()

显示:

图3 一个例子

自行创建模态对话框#

如果你需要自行创建模态对话框,有几件事情你需要注意。我们在本章的前面部分已经覆盖了大部分内容,例如设置窗口样式、定位窗口等。

首先,你需要确保用户只能与你的对话框交互。你可以使用 grab_set 来实现这一点。

如果你想让你的对话框功能阻塞应用程序(即,创建对话框的调用不应在对话框被关闭之前返回),这也是可能的。实际上没有理由这样做,因为你可以在运行正常的事件循环的同时响应回调、事件绑定等,然后销毁对话框并继续进行。

这个有些隐晦的例子包含了创建模态对话框所需的主要步骤。

ttk.Entry(root).grid()  # 创建一个输入框以便用户交互

def dismiss():
    dlg.grab_release()  # 释放对窗口的控制
    dlg.destroy()       # 销毁对话框

dlg = Toplevel(root)  # 创建一个新的顶级窗口作为对话框
ttk.Button(dlg, text="完成", command=dismiss).grid()  # 添加一个按钮,点击后调用dismiss函数
dlg.protocol("WM_DELETE_WINDOW", dismiss)  # 拦截关闭按钮事件,并调用dismiss函数
dlg.transient(root)   # 设置对话框为临时窗口,依赖于主窗口
dlg.wait_visibility() # 等待对话框变为可见状态,确保窗口已经显示出来
dlg.grab_set()        # 确保所有输入都定向到我们的对话框窗口
dlg.wait_window()     # 阻塞执行,直到对话框被销毁

备注

这种应用代码阻塞是运行嵌套事件循环的示例,我们通常不建议这样做,尽管在某些情况下它可能更为方便。

Tkinter的标准库中包含了 simpledialog 模块,该模块帮助构建自定义对话框。由于它使用的是经典的Tk小部件而非较新的有主题的小部件,我们不推荐直接使用它。然而,它确实展示了如何使用我们刚刚讨论过的一些技巧来使对话框表现得更符合预期。