0

In another answer here, a user created an inherited TextWithVar class that provides instances of the Tkinter.Text element but with textvariable functionality like Tkinter.Entry has. However, in testing, this new class behaves differently than a Text element when when using the Grid manager. To demonstrate, this code is copied from that answer, with the addition of some test calls at the end:

import Tkinter as tk

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, *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"))

root = tk.Tk()
text_frame = TextWithVar(root)
text_frame.grid(row=1, column=1)
test_button = tk.Button(root, text='Test')
test_button.grid(row=1, column=1, sticky='NE')
root.mainloop()

root2 = tk.Tk()
frame = tk.Frame(root2)
frame.grid(row=1, column=1)
text_frame2 = TextWithVar(frame)
text_frame2.grid(row=1, column=1)
test_button2 = tk.Button(frame, text='Test')
test_button2.grid(row=1, column=1, sticky='NE')
root2.mainloop()

In this example, when the TextWithVar element is directly inside root, it acts like it should - the Button element is placed on top of it in the corner. However, when both are inside a Frame, the Button element is nowhere to be seen. Now change both the TextWithVar calls to tk.Text. Both of them work the way they should, with the Button in plain view. According to Bryan, who made the new class, these should work the exact same way, which I tend to agree with. So why do they work differently?

Community
  • 1
  • 1
Sam Krygsheld
  • 2,060
  • 1
  • 11
  • 18

1 Answers1

0

It's a bug in TextWithVar, in this line of code:

tk.Text.__init__(self, *args, **kwargs)

The code needs to include the parent parameter:

tk.Text.__init__(self, parent, *args, **kwargs)
Bryan Oakley
  • 370,779
  • 53
  • 539
  • 685