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

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 6 years, 6 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.

Add a comment

Sign in to comment

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

Required Modules

Other Information and Tasks