【发布时间】:2018-09-17 19:40:33
【问题描述】:
背景
Python-2.7.14 x64、Tk-8.5.15(捆绑)
有据可查的是,Tk 中的 Treeview 小部件有很多问题,而 Tkinter 作为一个瘦包装器,并没有做太多的事情来处理这些问题。一个常见的问题是让 Treeview 在browse 模式下与水平滚动条一起正常工作。设置整个Treeview的宽度需要设置每一列的宽度。但是如果有很多列,这会将父容器水平拉伸,并且不会激活水平滚动条。
我发现的解决方案是,在设计时,对于每一列,将 width 设置为您想要的所需大小并将此值缓存在安全的地方。当你添加、编辑或删除一行时,你需要遍历列并查询它们当前的宽度值,如果它大于你的缓存宽度,将width设置回原来的缓存值,也 em> 将minwidth 设置为当前列宽。最后一列也需要将其stretch 属性设置为“True”,因此它可以消耗剩余的任何剩余空间。这将激活水平滚动条并允许它适当地平移 Treeview 的内容,而无需更改整个小部件的宽度。
警告:在某个时间点,Tk 在内部将width 重置为等于minwidth,但不会立即强制重绘。稍后当您更改小部件时会感到惊讶,例如添加或删除一行,因此每次更改小部件时都必须重复上述操作。如果您捕获所有可能发生重绘的地方,这并不是一个真正的大问题。
问题
更改 Ttk 样式的属性会触发整个应用程序的强制重绘,因此会弹出我上面提到的警告,Treeview 小部件水平扩展,并且水平滚动条停用。
演练
以下代码演示:
# imports
from Tkinter import *
from ttk import *
from tkFont import *
# root
root=Tk()
# font config
ff10=Font(family="Consolas", size=10)
ff10b=Font(family="Consolas", size=10, weight=BOLD)
# style config
s=Style()
s.configure("Foo2.Treeview", font=ff10, padding=1)
s.configure("Foo2.Treeview.Heading", font=ff10b, padding=1)
# init a treeview
tv=Treeview(root, selectmode=BROWSE, height=8, show="tree headings", columns=("key", "value"), style="Foo2.Treeview")
tv.heading("key", text="Key", anchor=W)
tv.heading("value", text="Value", anchor=W)
tv.column("#0", width=0, stretch=False)
tv.column("key", width=78, stretch=False)
tv.column("value", width=232, stretch=False)
tv.grid(padx=8, pady=(8,0))
# init a scrollbar
sb=Scrollbar(root, orient=HORIZONTAL)
sb.grid(row=1, sticky=EW, padx=8, pady=(0,8))
tv.configure(xscrollcommand=sb.set)
sb.configure(command=tv.xview)
# insert a row that has data longer than the initial column width and
# then update width/minwidth to activate the scrollbar.
tv.insert("", END, values=("foobar", "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"))
tv.column("key", width=78, stretch=False, minwidth=78)
tv.column("value", width=232, stretch=True, minwidth=372)
生成的窗口将如下所示:
请注意,水平滚动条处于活动状态,并且最后一列在小部件边缘之外有轻微溢出。对tv.column 的最后两次调用是启用此功能的原因,但是在转储最后一列的列属性时,我们看到 Tk 已经默默地将 width 和 minwidth 更新为相同:
tv.column("value")
{'minwidth': 372, 'width': 372, 'id': 'value', 'anchor': u'w', 'stretch': 1}
更改 any 样式的 any 属性将触发小部件的强制重绘。例如,下一行将派生的 Checkbutton 样式类的 foreground 颜色设置为红色:
s.configure("Foo2.TCheckbutton", foreground="red")
这是现在的 Treeview 小部件:
现在的问题是缩小整个小部件。可以通过将width 设置为最初缓存的大小,将stretch 设置为“False”,将minwidth 设置为最大列大小,可以将最后一列强制恢复为原始大小:
tv.column("value", width=232, stretch=False, minwidth=372)
但是 Treeview 小部件的宽度并没有缩小。
我找到了两种可能的解决方案,都在绑定到<Configure> 事件中,使用最后一个显示列:
-
表明主题的变化:
tv.column("value", width=232, stretch=True, minwidth=372) tv.event_generate("<<ThemeChanged>>") -
隐藏并重新显示:
tv.grid_remove() tv.column("value", width=232, stretch=True, minwidth=372) tv.grid()
两者都有效,因为它们是我能找到调用内部 Tk 函数TtkResizeWidget() 的唯一方法。第一个有效是因为<<ThemeChanged>> 强制重新计算小部件的几何图形,而第二个有效是因为如果在重新配置列时 Treeview 处于未映射状态,则 Treeview 将调用 TtkResizeWidget()。
这两种方法的缺点是您有时会看到窗口展开一帧然后又收缩回来。这就是为什么我认为两者都不是最佳方法,并希望其他人知道更好的方法。或者至少是在<Configure> 之前或扩展发生之前发生的可绑定事件,我可以从中使用上述方法之一。
一些参考资料表明这在 Tk 中已存在很长时间的问题:
而且,该问题仍然可在 Python-3.6.4(包括 Tk-8.6.6)上重现。
【问题讨论】: