组织复杂的界面#

如果你有一个复杂的用户界面,你需要找到一些方法来组织它,以避免让用户感到不知所措。在设计用户界面时,通用的和特定于平台的人机交互指南都是很好的资源。

当我们在本章中讨论复杂性时,我们并不是指程序实现的底层技术复杂性。相反,我们是指它向用户展示的方式。一个用户界面可能由许多不同的模块组合而成,由数百个小部件在一个深度嵌套的层次结构中组合在一起,但这并不意味着用户需要将其视为复杂。

多窗口

在应用程序中使用多个窗口的一个好处是可以简化用户界面。如果做得好,它可以要求用户一次只关注一个窗口的内容来完成一项任务。强迫他们关注或在几个窗口之间切换也可能产生相反的效果。同样,仅显示与当前任务相关的小部件(即,通过网格)可以帮助简化用户界面。

空白空间

如果你确实需要同时在屏幕上显示大量小部件,请考虑如何从视觉上组织它们。我们已经看到,网格使得对齐小部件变得容易。空白空间是另一个有用的辅助工具。将相关的小部件放在一起(可能紧接一个解释性的标签),并通过空白空间将它们与其他小部件分开。这有助于用户在自己的脑海中组织用户界面。

备注

围绕不同小部件、小部件组之间的空白空间量,以及边界周围的空白空间等,高度依赖于特定的平台。虽然你可以不必过分担心确切的像素数就能做得足够好,但如果你想拥有一个高度打磨的用户界面,你将需要为每个平台调整这一点。

Separator#

在单一显示中分组组件的第二种方法是,在组件组之间放置一条细的水平或垂直分隔线;通常,这比使用空白更为节省空间,对于紧凑的显示来说可能更加适用。Tk为此目的提供了一个简便的分隔组件。

s = ttk.Separator(parent, orient=HORIZONTAL)

这里 “orient” 可指定 "horizontal" 或者 "vertical"

比如:

root = Tk()

lb = ttk.Label(root, text="分隔条")
sep = ttk.Separator(root, orient="horizontal")
lb1 = ttk.Label(root, text="注释")
entry = ttk.Entry(root)
lb.grid()
sep.grid(sticky='we')
lb1.grid()
entry.grid()
root.mainloop()

效果图:

图1 设置分隔线

标签框架#

标签框架小部件,通常也被称为分组框,提供了另一种将相关组件分组的方式。它的功能类似于普通的 ttk::frame,可以包含其他你在其内部网格排列的小部件。然而,它在视觉上与用户界面的其他部分有所区分。你可以可选地提供文本标签,显示在标签框架的外部。

lf = ttk.Labelframe(parent, text='Label')

看例子:

root = Tk()
root['background'] = 'blue'
lf = ttk.Labelframe(root,
                    text='外部标签')
lb = ttk.Label(lf, text='内部')
lb_outer = ttk.Label(root, text='外部', foreground='red')
lb.grid()
lf.grid(sticky='ns', columnspan=2)
lb_outer.grid(ipady=2)
root.mainloop()

效果图:

图2 ttk.Labelframe 的例子

窗格视窗#

分段窗口小部件(窗格视窗)允许你在上下(或左右)堆叠两个或多个可调整大小的小部件。用户可以通过拖动窗格之间的分隔条来调整它们相对的高度(或宽度)。通常,你添加到分段窗口的小部件会是包含许多其他小部件的框架。

p = ttk.Panedwindow(parent, orient=VERTICAL)
# 两个面板,每个面板中将嵌入组件网格:
f1 = ttk.Labelframe(p, text='Pane1', width=100, height=100)
f2 = ttk.Labelframe(p, text='Pane2', width=100, height=100)   
p.add(f1)
p.add(f2)

分割窗口可以是垂直的(其面板在垂直方向上堆叠)或水平的。重要的是,你添加到分割窗口的每个面板必须是分割窗口本身的直接子项。

调用add方法会在面板列表的末尾添加一个新面板。insert 位置子窗口方法允许你将面板放置在面板列表中的给定位置(0…n-1)。如果面板已经被分割窗口管理,它会被移动到新的位置。你可以使用 forget 子窗口来从分割窗口中移除一个面板(你也可以传递一个位置而不是子窗口)。

你可以为每个面板分配相对权重,以便如果整个分割窗口的大小发生变化,某些面板将被分配更多的空间。此外,你可以调整分割窗口中每个sash之间的位置。详细信息请参阅命令参考

下面看几个例子。

添加窗格#

使用 eval('ttk.'+widget) 命令可以直接调用指定的模块,具体的代码:

root = Tk()
panes = ttk.PanedWindow(orient='vertical')
panes.pack(fill='both', expand=1)
for widget in ['Label', 'Button', 'Checkbutton', 'Radiobutton']:
    panes.add(eval('ttk.'+widget)(panes, text='欢迎'))
root.mainloop()

效果图:

图3 添加 pane

图3 中的第二个“欢迎”是可以调整大小的。

移除窗格#

代码:

root = Tk()
panes = ttk.PanedWindow(orient='vertical')
panes.pack(fill='both', expand=1)

ws = []
for name in ['Label', 'Button', 'Checkbutton', 'Radiobutton']:
    ws.append(eval('ttk.'+name)(panes, text='欢迎'))

for widget in ws:
    panes.add(widget)
panes.forget(ws[-1]) # 删除最后一个 pane,也可以使用 remove 方法
root.mainloop()

插入新的位置的 pane#

代码:

root = Tk()
panes = ttk.PanedWindow(orient='vertical')
panes.pack(fill='both', expand=1)

ws = []
for name in ['Label', 'Button', 'Checkbutton', 'Radiobutton']:
    ws.append(eval('ttk.'+name)(panes, text='欢迎'))

for widget in ws:
    panes.add(widget)
# 在首位插入新的 pane
panes.insert(0, ttk.Label(panes, text='世界'))
root.mainloop()

笔记本小工具#

笔记本小工具采用带标签的笔记本隐喻,允许用户通过索引标签在多个页面之间切换。与分窗窗口不同,用户一次只能看到一个页面(类似于面板)。

n = ttk.Notebook(parent)
f1 = ttk.Frame(n)   # first page, which would get widgets gridded into it
f2 = ttk.Frame(n)   # second page
n.add(f1, text='One')
n.add(f2, text='Two')

操作带标签的笔记本与操作分窗格窗口类似。通常,每一页都是一个框架,并且必须是笔记本自身的直接子窗口(子窗体)。要添加一个新页面及其关联的标签,可以在最后一个标签之后使用“add subwindow ?option value...?”方法。文本标签选项用于设置标签上的文字;状态标签选项也很有用,它可以取值为 normal(正常)、disabled(不可选)或 hidden(隐藏)。

若要在列表中的非末尾位标签,请使用“insert position subwindow ?option value...?”方法,要移除指定的标签,则使用 forget 方法,传递其位置(0…n-1)或标签的子窗口。你可以通过 tabs 方法检索包含在笔记本中的所有子窗口的列表。

要检索当前选中的子窗口,请调用 select 方法,并通过传递标签的位置或子窗口本身作为参数来更改所选标签。

要更改标签选项(如标签上的文本标签或其状态),可以使用 tab(tabid, option=value) 方法(其中 tabid 再次是标签的位置或子窗口);省略=value以返回该选项的当前值。

每当选择新标签时,笔记本小部件会生成 <<NotebookTabChanged>> 虚拟事件。

同样,命令参考中详细列出了一些较少使用的选项和命令。