tkinter.Text: binding a variable to widget text contents
使用独立于此问题的Python 3.3平台。
对于
1 2 3 4 5 6 | 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 |
但是,对于
如何最好地使用
更新:
我最终继承了
以下是我的实验代码。为了与我最初的问题以及如何解决问题(?)紧密相关,重要的几行是
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 | 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() ) |
旁注:很显然,我来自于基于间距的另一种语言。对不起,我无能为力。
我想我回答了我自己的问题。这是否是正确的做法,以覆盖像我刚才那样传递到函数中的
如果您愿意过着危险的生活,则可以连接到文本小部件的内部,并且无论内容如何更改,只要内容更改,都可以调用该函数。
诀窍是用代理替换基本的tk小部件命令。该代理负责执行纯文本窗口小部件的所有操作,然后发送虚拟事件(如果它所做的是插入或删除文本)。
设置好该位置后,只需设置与该事件的绑定,然后在该变量上放置一个读取跟踪即可。当然,如果您尝试在文本中插入小部件或图像,它们将不会反映在textvariable中。
这是一个简单又肮脏的示例,未经任何实际测试。这使用了与我在文本小部件中实现行号相同的技术(请参阅https://stackoverflow.com/a/16375233)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 | 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 2 3 4 5 | import tkinter as tk text_area = tk.Text(parent) text_area.bind('<KeyRelease>', lambda *args: do_something()) |
每次在文本小部件中释放键时,它将运行
我看到问题中提出的类实际上没有像典型的Tkinter小部件那样处理textvariable,因此我自己承担了一些重写工作,以使其成为"适当的"小部件。 :-)
通常,传递给它的类不会修改textvariable实例,而是在更改变量(通过trace检测)时调用其get()函数,并通过某个内部挂钩调用set()函数。这样,它可以被其他小部件使用。同样,修补猴子也许不是最安全的做法。
在这种情况下,将使用"文本"小部件绑定方法和
最后,在text_modified中暂时禁用了textvariable的跟踪,以避免不必要的循环。另外,如果在显式删除父项的情况下使用了小部件,则应调用unhook()方法进行清理,例如在模态窗口中,以避免出现问题。如果没有,它可以被忽略。
干得好:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 | 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中看到正在使用的代码以及使用摘机的代码。
干杯!