Welcome, guest | Sign In | My Account | Store | Cart

A Tkinter.Text can notice when its contents are changed. This recipe shows how to make use of the virtual event this generates to call your own callback.

Python, 92 lines
 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
92
class ModifiedMixin:
    '''
    Class to allow a Tkinter Text widget to notice when it's modified.

    To use this mixin, subclass from Tkinter.Text and the mixin, then write
    an __init__() method for the new class that calls _init().

    Then override the beenModified() method to implement the behavior that
    you want to happen when the Text is modified.
    '''

    def _init(self):
        '''
        Prepare the Text for modification notification.
        '''

        # Clear the modified flag, as a side effect this also gives the
        # instance a _resetting_modified_flag attribute.
        self.clearModifiedFlag()

        # Bind the <<Modified>> virtual event to the internal callback.
        self.bind_all('<<Modified>>', self._beenModified)

    def _beenModified(self, event=None):
        '''
        Call the user callback. Clear the Tk 'modified' variable of the Text.
        '''

        # If this is being called recursively as a result of the call to
        # clearModifiedFlag() immediately below, then we do nothing.
        if self._resetting_modified_flag: return

        # Clear the Tk 'modified' variable.
        self.clearModifiedFlag()

        # Call the user-defined callback.
        self.beenModified(event)

    def beenModified(self, event=None):
        '''
        Override this method in your class to do what you want when the Text
        is modified.
        '''
        pass

    def clearModifiedFlag(self):
        '''
        Clear the Tk 'modified' variable of the Text.

        Uses the _resetting_modified_flag attribute as a sentinel against
        triggering _beenModified() recursively when setting 'modified' to 0.
        '''

        # Set the sentinel.
        self._resetting_modified_flag = True

        try:

            # Set 'modified' to 0.  This will also trigger the <<Modified>>
            # virtual event which is why we need the sentinel.
            self.tk.call(self._w, 'edit', 'modified', 0)

        finally:
            # Clean the sentinel.
            self._resetting_modified_flag = False
    

if __name__ == '__main__':
    from Tkinter import Text, BOTH

    class T(ModifiedMixin, Text):
        '''
        Subclass both ModifiedMixin and Tkinter.Text.
        '''

        def __init__(self, *a, **b):

            # Create self as a Text.
            Text.__init__(self, *a, **b)

            # Initialize the ModifiedMixin.
            self._init()

        def beenModified(self, event=None):
            '''
            Override this method do do work when the Text is modified.
            '''
            print 'Hi there.'

    t = T()
    t.pack(expand=1, fill=BOTH)
    t.mainloop()

You can have a Tkinter.Text widget call a callback you define whenever its contents are modified. There's a <<Modified>> Tk virtual event that's generated whenever the text is modified, whether through typing, deleting, pasting, or programmatic insertion or deletion.

In order for it to be generated though, there's a Tk variable 'modified' on the Tk text widget (the one that the Text widget wraps) that must be set to 0 after every change. Just generating the virtual event doesn't reset this variable, so you have to do it yourself. However, resetting this variable to 0 will also trigger the <<Modified>> virtual event.

That's why we have to guard against a recursive call to _beenModified() with the _resetting_modified_flag attribute.

1 comment

Bob Hossley 17 years, 4 months ago  # | flag

bind() not bind_all(). In function ModifiedMixin.__init() the self.bind_all(...) should be changed to self.bind(...). This becomes obvious when you have two or more Tkinter.Text widgets and this "mixin" only works for the last one created.

Created by Simon Forman on Tue, 13 Dec 2005 (PSF)
Python recipes (4591)
Simon Forman's recipes (2)

Required Modules

Other Information and Tasks