-textvariable 选项不适用于 Python Tkinter 中的 ScrolledText 小部件
-textvariable option not working with ScrolledText widget in Python Tkinter
我最近在 Whosebug 中发现了一个代码,它继承了 Text class 以向其添加 -textvariable 选项(因为 Text 小部件最初没有 -textvariable 选项)和另一个代码也继承了 Text class默认给它添加一个滚动条(我从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
class 接受 -textvariable 作为参数,它也不会在小部件内的文本更改时更新变量值。删除向小部件添加滚动条的行会使 -textvariable 选项再次起作用。我不知道滚动条和文本变量如何相互冲突。是否仅在外部添加滚动条解决此问题?
这里的问题是 ScrolledText
重载了 __str__()
方法,因此 TextWithVar.__init__()
中的 widget=str(self)
没有引用正确的小部件,即容器框架而不是文本小部件。您可以使用原始文本小部件方法修复该问题:
widget=str(tk.Text.__str__(self)))
此外,您实际上不必合并两个 class,您可以让 class 直接继承自 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)
我最近在 Whosebug 中发现了一个代码,它继承了 Text class 以向其添加 -textvariable 选项(因为 Text 小部件最初没有 -textvariable 选项)和另一个代码也继承了 Text class默认给它添加一个滚动条(我从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
class 接受 -textvariable 作为参数,它也不会在小部件内的文本更改时更新变量值。删除向小部件添加滚动条的行会使 -textvariable 选项再次起作用。我不知道滚动条和文本变量如何相互冲突。是否仅在外部添加滚动条解决此问题?
这里的问题是 ScrolledText
重载了 __str__()
方法,因此 TextWithVar.__init__()
中的 widget=str(self)
没有引用正确的小部件,即容器框架而不是文本小部件。您可以使用原始文本小部件方法修复该问题:
widget=str(tk.Text.__str__(self)))
此外,您实际上不必合并两个 class,您可以让 class 直接继承自 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)