You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何用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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.21 20:33:21