关于python:tkinter.Text:将变量绑定到小部件文本内容

tkinter.Text: binding a variable to widget text contents

使用独立于此问题的Python 3.3平台。

对于Entry小部件,您可以像这样将变量绑定到此小部件的文本内容(请注意Entry构造函数中的textvariable参数):

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

但是,对于Text小部件,没有这种变量绑定功能。如果感兴趣的话,对于Windows版本中的Python 3.3,类Text的定义可能应该在%python dir%/ Lib / tkinter / __ init__.py中的第2927行开始。

如何最好地使用Text小部件模拟此变量绑定功能?我的想法是将tkinter.StringVar绑定到Text小部件,然后仅获取/设置所有文本。

更新:

我最终继承了tkinter.Frame作为Text包装器,该包装接受了预期作为tkinter.Variable实例的textvariable构造函数参数。在下面的示例中,为什么我不从Text继承的唯一原因仅仅是因为我也想要一个滚动条,但这并不重要。

以下是我的实验代码。为了与我最初的问题以及如何解决问题(?)紧密相关,重要的几行是self.textvariable.get = self.GetTextself.textvariable.set = self.SetText。基本上,我将传入的tkinter.Variable对象的get和set方法重写为我自己的设备...

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() )

旁注:很显然,我来自于基于间距的另一种语言。对不起,我无能为力。

我想我回答了我自己的问题。这是否是正确的做法,以覆盖像我刚才那样传递到函数中的tkinter.Variable对象的已知方法,这是一个单独的问题,尽管这是一段私密的代码,永远不会在我的应用程序之外使用。我承认这确实引出了一个问题,那就是这是否是一个有效的解决方案。


如果您愿意过着危险的生活,则可以连接到文本小部件的内部,并且无论内容如何更改,只要内容更改,都可以调用该函数。

诀窍是用代理替换基本的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())

每次在文本小部件中释放键时,它将运行do_something


我看到问题中提出的类实际上没有像典型的Tkinter小部件那样处理textvariable,因此我自己承担了一些重写工作,以使其成为"适当的"小部件。 :-)

通常,传递给它的类不会修改textvariable实例,而是在更改变量(通过trace检测)时调用其get()函数,并通过某个内部挂钩调用set()函数。这样,它可以被其他小部件使用。同样,修补猴子也许不是最安全的做法。

在这种情况下,将使用"文本"小部件绑定方法和<>-标记。这不是持续触发的典型" on_change"事件,而是在有人修改窗口小部件的内容时触发,在那里提供了一种机制来帮助发出信号,表明该值已被修改。因此,每次触发后,都需要使用Text.edit_modified(False)将其重置,如text_modified和var_modified函数所示,以便再次触发。但这行得通,我没有得到<>为我工作。

最后,在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中看到正在使用的代码以及使用摘机的代码。

干杯!