tkinter.Text:将变量绑定到小部件文本内容 [英] tkinter.Text: binding a variable to widget text contents

查看:39
本文介绍了tkinter.Text:将变量绑定到小部件文本内容的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

针对此问题使用独立于 Python 3.3 平台.

Using Python 3.3 platform independent for this question.

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

For the Entry widget, you can bind a variable to this widget's text contents like so (note the textvariable parameter in Entry constructor):

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 行开始.

For the Text widget however, there is no such variable binding feature. Class Text definition should likely begin at line 2927 in %python dir%/Lib/tkinter/__init__.py for Python 3.3 in Windows releases if interested.

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

How can I best emulate this variable binding feature with the Text widget? My idea is to bind a tkinter.StringVar to a Text widget and just get/set all text.

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

I ended up inheriting tkinter.Frame as a Text wrapper which takes in a textvariable constructor parameter expected as a tkinter.Variable instance. The only reason in my example below why I didn't inherit from Text is just because I wanted a scrollbar too, but that's not important.

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

The following is my experimental code. For exact relevance to my original question and how the problem was resolved (?), the important lines are self.textvariable.get = self.GetText and self.textvariable.set = self.SetText. Basically, I'm overriding the passed-in tkinter.Variable object's get and set methods to my own devices...

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

旁注:很明显,我来自基于间距的不同语言.对不起,我没办法.

Side note: It's probably pretty obvious I'm coming from a different language based on spacing. I'm sorry, I can't help it.

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

I think I answered my own question. Whether or not this is the right thing to do to override the known methods of tkinter.Variable objects passed into my functions like I just did is a separate question I'll have to ask/research even though this is a private bit of code that will never be used outside my app. And I acknowledge that this does beg the question whether this is an effective solution at all.

推荐答案

如果你愿意生活在危险的境地,可以连接到文本小部件的内部,让它在内容改变时调用一个函数,不管它是如何改变的.

If you're willing to live dangerously, it's possible to hook in to the internals of the text widget, and have it call a function whenever the contents change, regardless of how it changed.

诀窍是用代理替换底层 tk 小部件命令.该代理负责执行真实文本小部件会执行的任何操作,然后如果它执行的是插入或删除文本,则发送一个虚拟事件.

The trick is to replace the underlying tk widget command with a proxy. This proxy is responsible for doing whatever the real text widget would do, then send a virtual event if what it did was insert or delete text.

有了它,只需设置与该事件的绑定,并在变量上放置读取跟踪即可.当然,如果您尝试在文本中插入小部件或图像,它们将不会反映在 textvariable 中.

With that in place, it's just a matter of setting up a binding to that event, and putting a read trace on the variable. Of course, if you try inserting widgets or images into the text they won't be reflected in the textvariable.

这是一个快速而肮脏的例子,根本没有经过任何真实的测试.这使用了我用来在文本小部件中实现行号的相同技术(参见 https://stackoverflow.com/a/16375233)

Here's a quick and dirty example, not tested at all in anything real. This uses the same technique that I used to implement line numbers in a text widget (see 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()

这篇关于tkinter.Text:将变量绑定到小部件文本内容的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆