首页 > 编程问答 >Tkinter 的带有 wrap=none 的文本无法水平滚动超过可见行的宽度

Tkinter 的带有 wrap=none 的文本无法水平滚动超过可见行的宽度

时间:2024-07-28 05:34:13浏览次数:12  
标签:python tkinter textbox tcl horizontal-scrolling

当我注意到如果长行不可见时,ScrolledText 小部件的水平滚动条将消失时,我一直在修改 PAGE 滚动小部件的实现。事实证明这与水平滚动条无关,是 原始 Tk 行为 :

pathName xview

返回包含两个元素的列表。每个元素都是 0 到 1 之间的实数分数;它们一起描述了窗口中可见的文档水平范围的部分。例如,如果第一个元素为 0.2,第二个元素为 0.6,则 20% 的文本位于左侧屏幕外,中间 40% 在窗口中可见,40% 的文本位于屏幕外 - 分数仅指窗口中实际可见的行:如果窗口中的行都很短,因此它们完全可见,则返回的分数将为 0 和 1,即使文本中还有比窗口宽得多的其他行。

使用自动隐藏滚动条,垂直滚动会使水平滚动条不断缩小、变宽,有时会消失(稍后会影响垂直滚动条)。

如何我绕过这个?

相关来源(主要由 PAGE 生成,还有我为另一个滚动小部件添加的一些内容):

class AutoScroll(object):
    """Configure the scrollbars for a widget."""

    def __init__(self, master, cbl=None):
        try:
            vsb = ttk.Scrollbar(master, orient='vertical', command=self.yview)
        except:
            pass
        hsb = ttk.Scrollbar(master, orient='horizontal', command=self.xview)

        try:
            self.configure(yscrollcommand=self._autoscroll(vsb, cbl))
        except:
            pass
        self.configure(xscrollcommand=self._autoscroll(hsb, cbl))

        self.grid(column=0, row=0, sticky='nsew')
        try:
            vsb.grid(column=1, row=0, sticky='ns')
        except:
            pass
        hsb.grid(column=0, row=1, sticky='ew')

        master.grid_columnconfigure(0, weight=1)
        master.grid_rowconfigure(0, weight=1)
        # Copy geometry methods of master (taken from ScrolledText.py)

        methods = (
            Pack.__dict__.keys() |
            Grid.__dict__.keys() |
            Place.__dict__.keys()
        )

        for meth in methods:
            if (meth[0] != "_" and meth not in ("config", "configure") and
                    meth not in type(self).__bases__[0].__dict__):
                setattr(self, meth, getattr(master, meth))

    def clscroll(self, cbl):
        # Checklist elements scroll
        root.update()
        for i, w in enumerate(cbl[1]):
            bb = ttk.Treeview.bbox(self, i, "#0")
            if bb == "":
                w.place_forget()
            else:
                x, y, _, _ = bb
                w.place(x=x+(tvhh-2)/2, y=y+tvrh/2, anchor='center')
                cbl[0].place(x=x+(tvhh-2)/2, anchor='center')
        root.update()

    # @staticmethod
    def _autoscroll(self, sbar, cbl):
        """Hide and show scrollbar as needed."""
        def wrapped(first, last):
            if cbl:
                self.clscroll(cbl)
            first, last = float(first), float(last)
            if first <= 0 and last >= 1:
                sbar.grid_remove()
            else:
                sbar.grid()
            sbar.set(first, last)
        return wrapped

    def __str__(self):
        return str(self.master)

def _create_container(func):
    """Creates a ttk Frame with a given master, and use this new frame to
    place the scrollbars and the widget."""
    def wrapped(cls, master, **kw):
        container = ttk.Frame(master)
        return func(cls, container, **kw)
    return wrapped

class ScrolledText(AutoScroll, Text):
    """A standard Tkinter Text widget with scrollbars that will
    automatically show/hide as needed."""
    @_create_container
    def __init__(self, master, **kw):
        Text.__init__(self, master, **kw)
        AutoScroll.__init__(self, master)

由 TheLizzard 编辑

更短的 mre:

import tkinter as tk


class MyScrollbar(tk.Canvas):
    def __init__(self, master):
        super().__init__(master, bg="red", height=10, width=1,
                         highlightthickness=0)

    def set(self, low, high):
        if (low == "0.0") and (high == "1.0"):
            self.pack_forget()
        elif (low != "0.0") or (high != "1.0"):
            self.pack(side="bottom", fill="x")


if __name__ == "__main__":
    root = tk.Tk()

    scrollbar = MyScrollbar(root)
    scrollbar.pack(side="bottom", fill="x")

    text = tk.Text(root, width=30, height=10, wrap="none",
                   xscrollcommand=scrollbar.set)
    text.insert("end", "aa"+"\n"*12+"a"*31)
    text.pack(side="top", fill="both")

    # Scroll up/down to see the scrollbar (in red) disappear and reappear

要解决这个问题,需要在 Tkinter 的 Text 小部件中跟踪最长行的宽度,并在水平滚动条的逻辑中使用它,而不是依赖 xview 返回的值。

以下是一种修改后的 AutoScroll 类,它可以实现这一点:

import tkinter as tk
from tkinter import ttk

class AutoScroll(object):
    """为小部件配置滚动条."""

    def __init__(self, master, text_widget, cbl=None):
        self.text_widget = text_widget  # 存储对 Text 小部件的引用
        self.max_line_width = 0  # 初始化最长行的宽度

        vsb = ttk.Scrollbar(master, orient='vertical', command=self.yview)
        hsb = ttk.Scrollbar(master, orient='horizontal', command=self.xview)

        self.configure(yscrollcommand=self._autoscroll(vsb, cbl))
        self.configure(xscrollcommand=self._autoscroll(hsb, cbl))

        self.grid(column=0, row=0, sticky='nsew')
        vsb.grid(column=1, row=0, sticky='ns')
        hsb.grid(column=0, row=1, sticky='ew')

        master.grid_columnconfigure(0, weight=1)
        master.grid_rowconfigure(0, weight=1)

        # 绑定 Text 小部件的修改事件
        text_widget.bind("<KeyRelease>", self.update_max_line_width)
        text_widget.bind("<Configure>", self.update_max_line_width)

        # ... (其他方法保持不变)

    def update_max_line_width(self, event=None):
        """更新最长行的宽度."""
        font = self.text_widget.cget("font") or ''
        if isinstance(font, tuple):
            font = font[0]
        font_width = tk.font.Font(font=font).measure("0")
        lines = self.text_widget.get("1.0", "end").splitlines()
        self.max_line_width = max([font_width * len(line) for line in lines] + [0])

    def _autoscroll(self, sbar, cbl):
        """根据需要隐藏和显示滚动条."""
        def wrapped(first, last):
            if cbl:
                self.clscroll(cbl)
            first, last = float(first), float(last)

            # 水平滚动条逻辑:
            if sbar.cget("orientation") == "horizontal":
                text_width = self.text_widget.winfo_width()  # 获取 Text 小部件的宽度
                if self.max_line_width > text_width:  # 如果最长行比 Text 小部件宽
                    sbar.grid()
                else:
                    sbar.grid_remove()
            else:
                if first <= 0 and last >= 1:
                    sbar.grid_remove()
                else:
                    sbar.grid()
            sbar.set(first, last)

        return wrapped

 # ... (其他方法保持不变)

在这个修改后的代码中:

  1. 我们在 AutoScroll 类中添加了一个 update_max_line_width 方法,该方法计算并存储 Text 小部件中最长行的像素宽度。
  2. 我们在 Text 小部件上绑定了 <KeyRelease> <Configure> 事件,以便在文本内容或窗口大小更改时更新最长行的宽度。
  3. 我们在 _autoscroll 方法中添加了逻辑,根据最长行的宽度和 Text 小部件的宽度来控制水平滚动条的显示和隐藏。

使用此修改后的 AutoScroll 类,即使 wrap=none 并且长行不可见,水平滚动条也会按预期工作。

标签:python,tkinter,textbox,tcl,horizontal-scrolling
From: 35412972

相关文章

  • Python 中的“样板”代码?
    Google有一个Python教程,他们将样板代码描述为“不幸的”,并提供了以下示例:#!/usr/bin/python#importmodulesusedhere--sysisaverystandardoneimportsys#Gatherourcodeinamain()functiondefmain():print'Hellothere',sys.argv[1]#Command......
  • Python 3.9.1 中的 collections.abc.Callable 是否有 bug?
    Python3.9包含PEP585并弃用typing模块中的许多类型,转而支持collections.abc中的类型,现在它们支持__class_getitem__例如Callable就是这种情况。对我来说,typing.Callable和collections.abc.Ca......
  • 列表子类的 Python 类型
    我希望能够定义列表子类的内容必须是什么。该类如下所示。classA(list):def__init__(self):list.__init__(self)我想包含键入内容,以便发生以下情况。importtypingclassA(list:typing.List[str]):#Maybesomethinglikethisdef__init__(self):......
  • Python 中类型友好的委托
    考虑以下代码示例defsum(a:int,b:int):returna+bdefwrap(*args,**kwargs):#delegatetosumreturnsum(*args,**kwargs)该代码运行良好,只是类型提示丢失了。在Python中使用*args,**kwargs来实现​​委托模式是很常见的。如果有一种方法可......
  • 使用 python 支持构建自定义 vim 二进制文件
    背景Debian11vim软件包不包含python3支持。请参阅标题为“Debian11vim中不支持python-证据”的部分下面我需要vim支持python3YouCompleteMevim插件为了构建一个新的,我将vim9.0tarball下载到v......
  • 如何在Python 3.12+中正确使用泛型来提高代码质量?
    我正在尝试使用泛型来改进FastAPI应用程序中的类型注释。我有一个抽象存储库类,在其中使用泛型:fromabcimportABC,abstractmethodfromtypingimportListclassAbstractRepository[T](ABC):@abstractmethodasyncdefadd_one(self,data:dict)->T:......
  • python中的while循环不退出
    我试图完成第一年的python商业课程作业,但我的while循环无法退出,有人能帮忙吗?commisionTable=[{"admin_fee":100,"comm_rate":0.10},{"admin_fee":125,"comm_rate":0.12},{"admin_fee":150,"comm_rate":......
  • python---json文件写入
    ​ 使用到的知识点:os模块执行linux指令、json.dump()、withopenasf代码实现importsysimportosimportjson #向json文件file中添加内容data,其中data的类型为字典defwrite_json(file,data):    #如果文件存在,则删除    if(os.path.exists(fi......
  • python错题记录:布尔运算与逻辑值检测
    一前言环境:python3.10win10二布尔运算与逻辑值检测1案例案例1如上,在布尔运算时,有些时候代码只会运算前面的一部分,剩下的部分根本不会运算。以前在练习算法代码时,就利用这个规则来减少代码的工作量案例2如上,之前好长一段时间,上面的布尔运算总是让我感到困惑布尔运......
  • python---字典遍历
    1、三种常见的字典遍历实现defget_key_value(dics):  '''遍历所有键值对'''  forkey,valueindics.items():    print(f"{key}:{value}")defget_keys(dics):  '''遍历所有的键'''  forkeyindics......