如何用Tkinter实现带双向滚动的动态表格,完整显示长文本
用Tkinter实现带双向滚动的动态表格(完整显示超长文本)
我想用Tkinter实现一个带双向滚动条的动态表格,核心要求是单元格内的超长字符串能通过滚动完整查看。原本以为实现难度不高,但始终没找到满意的解决方案:
- Stack Overflow上的相关方案多为临时hack,稳定性不足,且未考虑文本长度,仍会出现截断问题
- 使用Canvas、Label等容器也无法达到最优效果
我知晓tktable这类第三方封装库,但希望仅用原生Tkinter完成实现。为此我编写了最小可复现示例(MWE),该示例已实现大部分所需功能,但无法通过水平滚动条完整查看超长字符串,代码如下:
import tkinter as tk from tkinter import ttk import random import string class MWEapp(tk.Tk): def __init__(self): tk.Tk.__init__(self) # these containers are perhaps a bit too much for this example # but in the full code, this class is inheriting from a larger design self.wndcnt = ttk.Frame(self) self.wndcnt.pack(fill=None, expand=False, side="top", padx=20, pady=20) # add container to table tblcnt = ttk.Frame(self) tblcnt.pack(anchor="center") # add scrollbar scrollbary = ttk.Scrollbar(tblcnt, orient="vertical") scrollbary.grid(row=0, column=1, sticky="ns") scrollbarx = ttk.Scrollbar(tblcnt, orient="horizontal") scrollbarx.grid(row=1, column=0, sticky="ew") # use treeview to create table self.dynamictable = ttk.Treeview( tblcnt, column=("#1", "#2", "#3", "#4", "#5"), show="headings", height=30, xscrollcommand=scrollbarx.set, yscrollcommand=scrollbary.set ) # bind mouse click to scroll callbacks self.dynamictable.bind("<Button-1>", self._scroll_init) self.dynamictable.bind("<B1-Motion>", self._scroll) self.last_y = 0 self.dynamictable.grid(row=0, column=0, sticky="nsew") scrollbarx["command"] = self.dynamictable.xview scrollbary["command"] = self.dynamictable.yview # add headers self.dynamictable.heading("#1", anchor="w", text="Header 1") self.dynamictable.heading("#2", anchor="w", text="Header 2") self.dynamictable.heading("#3", anchor="w", text="Header 3") self.dynamictable.heading("#4", anchor="w", text="Header 4") self.dynamictable.heading("#5", anchor="w", text="Header 5") # add columns self.dynamictable.column("#1", anchor="w", width=100, stretch=False) self.dynamictable.column("#2", anchor="w", width=200, stretch=False) self.dynamictable.column("#3", anchor="w", width=200, stretch=False) self.dynamictable.column("#4", anchor="w", width=150, stretch=False) self.dynamictable.column("#5", anchor="w", width=500, stretch=False) # fill table at constant rate self._fill_tbl() def _rstr(self, N): #generate pseudo string with N characters. return ''.join(random.choice(string.ascii_uppercase + string.ascii_lowercase + string.digits) for _ in range(N)) def _dummy_data(self): # dummy data longstring = """Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.""" # dummy data is a list of 5-tuples containing strings database_data = [] for i in range(50): if i == 10: database_data.append((str(i),self._rstr(10),self._rstr(15),self._rstr(6),longstring)) else: database_data.append((str(i),self._rstr(10),self._rstr(15),self._rstr(6),self._rstr(30))) return database_data def _fill_tbl(self): database_data = self._dummy_data() # clear previous table self.dynamictable.delete(*self.dynamictable.get_children()) # add dummy data for row in database_data: self.dynamictable.insert("","end",values=(row[0],row[1],row[2],row[3],row[4],),) # keep refreshing data each second self.after(1000, self._fill_tbl) # callbacks for touch scroll def _scroll_init(self, event): self.last_y = event.y def _scroll(self, event): delta = -1 if event.y > self.last_y else 1 self.last_y = event.y self.dynamictable.yview_scroll(delta, "units") if __name__ == "__main__": app = MWEapp() app.mainloop()
后续经@Bryan Oakley建议,我通过计算列中最长字符串的字符数,结合字体缩放因子设置列宽,解决了截断问题,同时适配了水平滚动的回调函数。优化代码片段如下:
# find longest string in column 4 longest_string = len(max(database_data, key=lambda t: len(t[4]))[4]) # found optimal length manually to 2450 for 445 characters (the lorem ipsum text used above). # this gives a factor of 5.5. Since my font is not monospace # we add 0.05 to be sure. offset = int(longest_string*5.55) self.dynamictable.update() self.dynamictable.column("#5", anchor="w", width=offset, stretch=False)
希望得到进一步的改进建议。
内容的提问来源于stack exchange,提问作者Yuki
相关产品推荐
相关产品推荐

