python - -textvariable 选项不适用于 Python Tkinter 中的 ScrolledText 小部件

标签 python python-3.x tkinter tcl tkinter-text

我最近在 StackOverflow 中发现了一个代码,它继承了 Text 类,为其添加 -textvariable 选项(因为 Text 小部件最初没有 -textvariable 选项),另一个代码也继承了 Text 类,为其添加了滚动条默认情况下(我从 Tkinter 的源代码中找到它)。

带有滚动条的文本:

class ScrolledText(Text):
    def __init__(self, master=None, **kwargs):
        self.frame = Frame(master)
        self.vbar = Scrollbar(self.frame)
        self.vbar.pack(side=RIGHT, fill=Y)

        kwargs.update({'yscrollcommand': self.vbar.set})
        Text.__init__(self, self.frame, **kwargs)
        self.pack(side=LEFT, fill=BOTH, expand=True)
        self.vbar['command'] = self.yview

        text_meths = vars(Text).keys()
        methods = vars(Pack).keys() | vars(Grid).keys() | vars(Place).keys()
        methods = methods.difference(text_meths)

        for m in methods:
            if m[0] != '_' and m != 'config' and m != 'configure':
                setattr(self, m, getattr(self.frame, m))

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

带有 -textvariable 选项的文本:

class TextWithVar(Text):
    def __init__(self, parent, *args, **kwargs):
        try:
            self._textvariable = kwargs.pop("textvariable")
        except KeyError:
            self._textvariable = None
        super().__init__(parent, *args, **kwargs)
        if self._textvariable is not None:
            self.insert("1.0", self._textvariable.get())
        self.tk.eval('''
            proc widget_proxy {widget widget_command args} {

                set result [uplevel [linsert $args 0 $widget_command]]

                if {([lindex $args 0] in {insert replace delete})} {
                    event generate $widget <<Change>> -when tail
                }

                return $result
            }
            ''')
        self.tk.eval('''
            rename {widget} _{widget}
            interp alias {{}} ::{widget} {{}} widget_proxy {widget} _{widget}
        '''.format(widget=str(self)))
        self.bind("<<Change>>", self._on_widget_change)

        if self._textvariable is not None:
            self._textvariable.trace("wu", self._on_var_change)

    def _on_var_change(self, *args):
        text_current = self.get("1.0", "end-1c")
        var_current = self._textvariable.get()
        if text_current != var_current:
            self.delete("1.0", "end")
            self.insert("1.0", var_current)

    def _on_widget_change(self, event=None):
        if self._textvariable is not None:
            self._textvariable.set(self.get("1.0", "end-1c"))

我尝试合并它们:

class ScrolledTextWithVar(Text):
    def __init__(self, master=None, *args, **kwargs):
        try:
            self._textvariable = kwargs.pop("textvariable")
        except KeyError:
            self._textvariable = None
        self.frame = Frame(master)
        self.vbar = Scrollbar(self.frame)
        self.vbar.pack(side=RIGHT, fill=Y)
        kwargs.update({'yscrollcommand': self.vbar.set})
        super().__init__(self.frame, **kwargs)
        self.pack(side=LEFT, fill=BOTH, expand=True)
        self.vbar['command'] = self.yview
        text_meths = vars(Text).keys()
        methods = vars(Pack).keys() | vars(Grid).keys() | vars(Place).keys()
        methods = methods.difference(text_meths)

        for m in methods:
            if m[0] != '_' and m != 'config' and m != 'configure':
                setattr(self, m, getattr(self.frame, m))

        if self._textvariable is not None:
            self.insert("1.0", self._textvariable.get())
        self.tk.eval('''
            proc widget_proxy {widget widget_command args} {

                set result [uplevel [linsert $args 0 $widget_command]]

                if {([lindex $args 0] in {insert replace delete})} {
                    event generate $widget <<Change>> -when tail
                }

                return $result
            }
            ''')
        self.tk.eval('''
            rename {widget} _{widget}
            interp alias {{}} ::{widget} {{}} widget_proxy {widget} _{widget}
        '''.format(widget=str(self)))
        self.bind("<<Change>>", self._on_widget_change)

        if self._textvariable is not None:
            self._textvariable.trace("wu", self._on_var_change)

    def _on_var_change(self, *args):
        text_current = self.get("1.0", "end-1c")
        var_current = self._textvariable.get()
        if text_current != var_current:
            self.delete("1.0", "end")
            self.insert("1.0", var_current)

    def _on_widget_change(self, event=None):
        if self._textvariable is not None:
            self._textvariable.set(self.get("1.0", "end-1c"))

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

尽管 ScrolledTextWithVar 类接受 -textvariable 作为参数,但当小部件内的文本发生更改时,它不会更新变量值。删除向小部件添加滚动条的行会使 -textvariable 选项再次起作用。我不知道滚动条和文本变量如何相互冲突。在外部添加滚动条是唯一的解决方案吗?

最佳答案

这里的问题是 ScrolledText 正在重载 __str__() 方法,因此 中的 widget=str(self) 会重载TextWithVar.__init__() 没有引用正确的小部件,即容器框架而不是文本小部件。您可以使用原始文本小部件方法修复该问题:

widget=str(tk.Text.__str__(self)))

此外,您实际上不必合并这两个类,您可以使您的类直接继承自 ScrolledText 而不是 Text:

import tkinter as tk
from tkinter.scrolledtext import ScrolledText

# same code as TextWithVar but inheriting from ScrolledText and with above mentioned fix
class ScrolledTextWithVar(ScrolledText):  
    def __init__(self, parent, *args, **kwargs):
        try:
            self._textvariable = kwargs.pop("textvariable")
        except KeyError:
            self._textvariable = None
        super().__init__(parent, *args, **kwargs)
        if self._textvariable is not None:
            self.insert("1.0", self._textvariable.get())
        self.tk.eval('''
            proc widget_proxy {widget widget_command args} {

                set result [uplevel [linsert $args 0 $widget_command]]

                if {([lindex $args 0] in {insert replace delete})} {
                    event generate $widget <<Change>> -when tail
                }

                return $result
            }
            ''')
        self.tk.eval('''
            rename {widget} _{widget}
            interp alias {{}} ::{widget} {{}} widget_proxy {widget} _{widget}
        '''.format(widget=str(tk.Text.__str__(self))))  # <-- use tk.Text str method
        self.bind("<<Change>>", self._on_widget_change)

        if self._textvariable is not None:
            self._textvariable.trace("wu", self._on_var_change)

    def _on_var_change(self, *args):
        text_current = self.get("1.0", "end-1c")
        var_current = self._textvariable.get()
        if text_current != var_current:
            self.delete("1.0", "end")
            self.insert("1.0", var_current)

    def _on_widget_change(self, event=None):
        if self._textvariable is not None:
            self._textvariable.set(self.get("1.0", "end-1c"))


root = tk.Tk()
var = tk.StringVar(root)
txt = ScrolledTextWithVar(root, textvariable=var)
txt.pack(side="left", fill="both", expand=True)

关于python - -textvariable 选项不适用于 Python Tkinter 中的 ScrolledText 小部件,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/70862575/

相关文章:

mysql - GAE Python 3.7 Standard 无法连接到 Cloud SQL 服务器

python - 属性错误 : 'function' object has no attribute 'upper'

python - Tkinter 输入的第一个字母

python - 如何使 python tkinter 输出显示在 GUI 而不是 python shell 中?

Python 与 dataframe 的复杂操作

python - interp2(X, Y, Z, XI, YI) 从 Matlab 到 Python

python - 仅在 pytube 的播放列表中下载音频

python - 递归如何找到最大值?

python - 我如何在 python 中将整数转换为 'binary'

python - R group_by %>% 总结 Pandas 中的等效项