1

I would like to repeatedly get the contents of a Text widget, so I can analyse it and gets stats about what's been entered. These stats would need to be updated in real time as the user types, hence why I need the variable currentContent to update every loop. What I'd like to do is something like this.

main = tk.Tk()
# Y'know, all the typical window setup stuff.

currentContent = inputBox.get(0.0,END)
textBlobContent = TextBlob(currentContent)
# Basically here I'd do a bunch of stuff using TextBlob.

main.mainloop()

However, that doesn't work. It gets the content once, as soon as the window loads, and then stops. Surely mainloop runs repeatedly, and it should keep getting the contents of the Text widget?

Harry
  • 29
  • 4
  • I think you are looking for the `after` method: https://stackoverflow.com/questions/25753632/tkinter-how-to-use-after-method – j_4321 Oct 20 '17 at 11:11
  • @j_4321 The `after` method? Wouldn't that do it based on time instead of each loop? – Harry Oct 20 '17 at 11:16
  • You can use a StringVar/IntVar and `.trace` method depending on your widget. Note that this will make `trace` run on every change. Similar idea but using `.bind` on `Text` widget shown here: https://stackoverflow.com/questions/17120429/validation-on-tkinter-text-widget – Lafexlos Oct 20 '17 at 11:32
  • @Lafexlos: the text widget does not support the use of `StringVar` – Bryan Oakley Oct 20 '17 at 11:38
  • @BryanOakley That's why I wrote 'depending on your widget' above but it seems like it was redundant since question is about text wdiget. Thanks for clarifying that. :) – Lafexlos Oct 20 '17 at 11:42
  • @HarryWright yes, but it is a way to create a loop inside of the mainloop without freezing the GUI. I had not read your question carefully enough and just stopped on 'loop'. Detecting the changes of the `Text` widget content like in Bryan Oakley's answer is more appropriate for what you want to do. – j_4321 Oct 20 '17 at 11:59
  • 1
    BTW, `0.0` is not a valid index. The index of the first character is the string `"1.0"`. `0.0` works only because tkinter is a bit liberal in what it expects. Indexes should always be a string of the form `"line.character"` with lines starting at 1 and characters starting at 0. – Bryan Oakley Oct 20 '17 at 13:10

1 Answers1

5

A simple solution that works most of the time would be to put a binding on <KeyRelease>. That will enable a function to be called whenever the user is typing. This won't trigger the callback whenever data is pasted with the mouse, or inserted via other means (such as a toolbar button).

A more robust solution is to set up a proxy for the widget, so that an event is generated whenever anything is inserted or deleted in the widget. This proxy can look at what is being done with the widget (insert, delete, selection changed, etc) and generate an event. You can then bind to this event to do whatever you want.

Here's an example of a custom text class that generates a <<TextModified>> event whenever data is inserted or deleted:

import tkinter as tk

class CustomText(tk.Text):
    def __init__(self, *args, **kwargs):
        """A text widget that report on internal widget commands"""
        tk.Text.__init__(self, *args, **kwargs)

        # create a proxy for the underlying widget
        self._orig = self._w + "_orig"
        self.tk.call("rename", self._w, self._orig)
        self.tk.createcommand(self._w, self._proxy)

    def _proxy(self, command, *args):
        cmd = (self._orig, command) + args
        result = self.tk.call(cmd)

        if command in ("insert", "delete", "replace"):
            self.event_generate("<<TextModified>>")

        return result

This proxy does four things:

  1. First, it calls the actual widget command, passing in all of the arguments it received.
  2. Next it generates an event for every insert and every delete
  3. Then it then generates a virtual event
  4. And finally it returns the results of the actual widget command

You can use this widget exactly like any other Text widget, with the added benefit that you can bind to <<TextModified>>.

For example, if you wanted to display the number of characters in the text widget you could do something like this:

import tkinter as tk

# ... import of definition of CustomText goes here ...

root = tk.Tk()
label = tk.Label(root, anchor="w")
text = CustomText(root, width=40, height=4)

label.pack(side="bottom", fill="x")
text.pack(side="top", fill="both", expand=True)

def onModification(event):
    chars = len(event.widget.get("1.0", "end-1c"))
    label.configure(text="%s chars" % chars)

text.bind("<<TextModified>>", onModification)

root.mainloop()
Bryan Oakley
  • 370,779
  • 53
  • 539
  • 685