使用Python 3.3平台独立于此问题。
对于Entry
小部件,您可以将变量绑定到此小部件的文本内容,如下所示(请注意textvariable
构造函数中的Entry
参数):
var = tkinter.StringVar()
entryField = tkinter.Entry(master, textvariable=var)
e.pack()
var.set("a new value") # entryField text now updated with this value
s = var.get() # whatever text now appears in entryField
但是对于Text
小部件,没有这样的变量绑定功能。如果感兴趣,类Text
定义应该可以从%python dir%/ Lib / tkinter / __ init__.py中的第2927行开始,适用于Windows版本中的Python 3.3。
如何使用Text
窗口小部件最好地模拟此变量绑定功能?我的想法是将tkinter.StringVar
绑定到Text
小部件,然后获取/设置所有文本。
我最终继承tkinter.Frame
作为Text
包装器,它接受textvariable
构造函数参数作为tkinter.Variable
实例。我下面的例子中我唯一没有继承Text
的原因只是因为我想要一个滚动条,但这并不重要。
以下是我的实验代码。与我原始问题以及问题如何解决(?)完全相关,重要的行是self.textvariable.get = self.GetText
和self.textvariable.set = self.SetText
。基本上,我将覆盖传入的tkinter.Variable
对象的get和set方法改为我自己的设备......
class TextExtension( tkinter.Frame ):
"""Extends Frame. Intended as a container for a Text field. Better related data handling
and has Y scrollbar now."""
def __init__( self, master, textvariable = None, *args, **kwargs ):
self.textvariable = textvariable
if ( textvariable is not None ):
if not ( isinstance( textvariable, tkinter.Variable ) ):
raise TypeError( "tkinter.Variable type expected, {} given.".format( type( textvariable ) ) )
self.textvariable.get = self.GetText
self.textvariable.set = self.SetText
# build
self.YScrollbar = None
self.Text = None
super().__init__( master )
self.YScrollbar = tkinter.Scrollbar( self, orient = tkinter.VERTICAL )
self.Text = tkinter.Text( self, yscrollcommand = self.YScrollbar.set, *args, **kwargs )
self.YScrollbar.config( command = self.Text.yview )
self.YScrollbar.pack( side = tkinter.RIGHT, fill = tkinter.Y )
self.Text.pack( side = tkinter.LEFT, fill = tkinter.BOTH, expand = 1 )
def Clear( self ):
self.Text.delete( 1.0, tkinter.END )
def GetText( self ):
text = self.Text.get( 1.0, tkinter.END )
if ( text is not None ):
text = text.strip()
if ( text == "" ):
text = None
return text
def SetText( self, value ):
self.Clear()
if ( value is not None ):
self.Text.insert( tkinter.END, value.strip() )
附注:很明显,我是基于间距的另一种语言。对不起,我帮不了。
我想我回答了自己的问题。是否正确的做法是覆盖传递到我的函数中的tkinter.Variable
对象的已知方法,就像我刚才所做的那样是一个单独的问题,我将不得不问/研究,即使这是一个私有的永远不会在我的应用之外使用的代码。我承认这确实是一个问题,这是否是一个有效的解决方案。
答案 0 :(得分:6)
如果您愿意危险地生活,可以挂钩文本小部件的内部,并在内容发生变化时让它调用函数,无论它如何变化。
诀窍是用代理替换底层的tk widget命令。此代理负责执行真实文本小部件的操作,然后发送虚拟事件,如果它执行的操作是插入或删除文本。
有了这个,只需要设置一个绑定到该事件,并在变量上放置一个读取跟踪。当然,如果您尝试在文本中插入小部件或图像,它们将不会反映在textvariable中。
这是一个快速而肮脏的例子,没有任何真实的测试。这使用了我用来在文本小部件中实现行号的相同技术(参见https://stackoverflow.com/a/16375233)
import Tkinter as tk
import random
import timeit
class TextWithVar(tk.Text):
'''A text widget that accepts a 'textvariable' option'''
def __init__(self, parent, *args, **kwargs):
try:
self._textvariable = kwargs.pop("textvariable")
except KeyError:
self._textvariable = None
tk.Text.__init__(self, parent, *args, **kwargs)
# if the variable has data in it, use it to initialize
# the widget
if self._textvariable is not None:
self.insert("1.0", self._textvariable.get())
# this defines an internal proxy which generates a
# virtual event whenever text is inserted or deleted
self.tk.eval('''
proc widget_proxy {widget widget_command args} {
# call the real tk widget command with the real args
set result [uplevel [linsert $args 0 $widget_command]]
# if the contents changed, generate an event we can bind to
if {([lindex $args 0] in {insert replace delete})} {
event generate $widget <<Change>> -when tail
}
# return the result from the real widget command
return $result
}
''')
# this replaces the underlying widget with the proxy
self.tk.eval('''
rename {widget} _{widget}
interp alias {{}} ::{widget} {{}} widget_proxy {widget} _{widget}
'''.format(widget=str(self)))
# set up a binding to update the variable whenever
# the widget changes
self.bind("<<Change>>", self._on_widget_change)
# set up a trace to update the text widget when the
# variable changes
if self._textvariable is not None:
self._textvariable.trace("wu", self._on_var_change)
def _on_var_change(self, *args):
'''Change the text widget when the associated textvariable changes'''
# only change the widget if something actually
# changed, otherwise we'll get into an endless
# loop
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):
'''Change the variable when the widget changes'''
if self._textvariable is not None:
self._textvariable.set(self.get("1.0", "end-1c"))
class Example(tk.Frame):
def __init__(self, parent):
tk.Frame.__init__(self, parent)
self.textvar = tk.StringVar()
self.textvar.set("Hello, world!")
# create an entry widget and a text widget that
# share a textvariable; typing in one should update
# the other
self.entry = tk.Entry(self, textvariable=self.textvar)
self.text = TextWithVar(self,textvariable=self.textvar,
borderwidth=1, relief="sunken",
background="bisque")
self.entry.pack(side="top", fill="x", expand=True)
self.text.pack(side="top",fill="both", expand=True)
if __name__ == "__main__":
root = tk.Tk()
Example(root).pack(fill="both", expand=True)
root.mainloop()
答案 1 :(得分:1)
我看到问题中提出的类实际上没有像典型的Tkinter小部件那样处理textvariable,所以我自己做了一些重写,使它成为一个“正确的”小部件。 : - )
通常,文本变量实例不会被传递给它的类修改,而是在更改变量(通过跟踪检测)时调用其get()函数,并通过某些函数调用set() - 函数内钩。这样,它可以被其他小部件使用。此外,猴子补丁可能不是最安全的做法。
在这种情况下,使用Text小部件bind-method和<<Modified>>
- 标记。它不是一个典型的“on_change”事件,它继续触发,而是在有人修改窗口小部件的内容时触发,在那里提供一种机制来帮助发出信号已被修改的信号。因此,每次触发后,都需要使用Text.edit_modified(False)重置它,如text_modified和var_modified函数中所示,它将再次触发。但是它有效,我没有让<<Change>>
为我工作。
最后,text_modified暂时禁用了textvariable的跟踪,以避免不必要的循环。此外,如果在显式删除父窗口时使用窗口小部件,则应调用unhook()方法进行清理,例如在模式窗口中以避免出现问题。如果没有,可以忽略它。
你走了:
from tkinter import Frame, Variable, Scrollbar, Text
from tkinter.constants import VERTICAL, RIGHT, LEFT, BOTH, END, Y
class TextExtension(Frame):
"""Extends Frame. Intended as a container for a Text field. Better related data handling
and has Y scrollbar."""
def __init__(self, master, textvariable=None, *args, **kwargs):
super(TextExtension, self).__init__(master)
# Init GUI
self._y_scrollbar = Scrollbar(self, orient=VERTICAL)
self._text_widget = Text(self, yscrollcommand=self._y_scrollbar.set, *args, **kwargs)
self._text_widget.pack(side=LEFT, fill=BOTH, expand=1)
self._y_scrollbar.config(command=self._text_widget.yview)
self._y_scrollbar.pack(side=RIGHT, fill=Y)
if textvariable is not None:
if not (isinstance(textvariable, Variable)):
raise TypeError("tkinter.Variable type expected, " + str(type(textvariable)) + " given.".format(type(textvariable)))
self._text_variable = textvariable
self.var_modified()
self._text_trace = self._text_widget.bind('<<Modified>>', self.text_modified)
self._var_trace = textvariable.trace("w", self.var_modified)
def text_modified(self, *args):
if self._text_variable is not None:
self._text_variable.trace_vdelete("w", self._var_trace)
self._text_variable.set(self._text_widget.get(1.0, END))
self._var_trace = self._text_variable.trace("w", self.var_modified)
self._text_widget.edit_modified(False)
def var_modified(self, *args):
self.set_text(self._text_variable.get())
self._text_widget.edit_modified(False)
def unhook(self):
if self._text_variable is not None:
self._text_variable.trace_vdelete("w", self._var_trace)
def clear(self):
self._text_widget.delete(1.0, END)
def set_text(self, _value):
self.clear()
if (_value is not None):
self._text_widget.insert(END, _value)
您可以在on_post_merge_sql-function中查看正在使用的代码,并在此处使用取消隐藏。
干杯!
答案 2 :(得分:1)
不确定这是否是您尝试做的,但这对我有用:
import tkinter as tk
text_area = tk.Text(parent)
text_area.bind('<KeyRelease>', lambda *args: do_something())
每次在文本小部件中发布密钥时,它都会运行do_something