Tcl Source Code

View Ticket
Login
Ticket UUID: ce6b428620dd89bc46786654e3aa1d62d74d3fc8
Title: <<Increment>> event is endlessly triggered once messagebox closes
Type: Bug Version: TkVersion 8.6
Submitter: GOLosinas Created on: 2022-02-10 02:40:25
Subsystem: 02. Event Loops Assigned To: fvogel
Priority: 5 Medium Severity: Important
Status: Closed Last Modified: 2022-02-25 23:25:20
Resolution: Fixed Closed By: fvogel
    Closed on: 2022-02-25 23:25:20
Description:
I am using a ttk.Spinbox to capture some number and I want to display a messagebox once the value in the spinbox reaches the limit. I bound the spinbox to the <code> <<Increment>> </code> event to perform a check, and display the message but the event is endlessly triggered once you close the messagebox. I made a small application to illustrate the issue.

class App(Tk):
    def __init__(self):
        super().__init__()
        self.title('Sample')
        self.geometry('300x100')
        Selector(self).pack()

class Selector(ttk.Frame):
        def __init__(self, container):
            super().__init__(container)
            self.max_length = 10
            self.input = IntVar(0)
            self.check_length_calls = 0
            
            self.label = ttk.Label(self, text="Quantity: ").pack()
            self.spinbox = ttk.Spinbox(self, from_=0,
                                       to=10,
                                       textvariable=self.input)
            self.spinbox.pack()
            self.spinbox.bind('<<Increment>>', lambda e: self.check_length(e))

            ttk.Button(self, text="print func calls", command= self.print_check_length_calls).pack()


        def check_length(self, e):
            self.check_length_calls += 1
            if str(self.spinbox) != str(self.focus_get()):
                return
            if self.input.get() == self.max_length:
                messagebox.showwarning("Max length reached", "Too long!")               
                self.focus_set()

        def print_check_length_calls(self):
            print(self.check_length_calls)
        
        
if __name__ == "__main__":
    app = App()
    app.mainloop()
User Comments: fvogel added on 2022-02-25 23:25:20:
Merged in core-8-6-branch and trunk (of Tk).

fvogel added on 2022-02-18 21:22:34:

The fix obviously is in the Tk repository:

https://core.tcl-lang.org/tk/info/fb9a3632373e63e3

The present ticket should have been opened there btw.


fvogel added on 2022-02-18 21:20:20:

The problem is due to the following. When clicking the up arrow of the spinbox, an <<Increment>> virtual event is generated from ttk::spinbox::Press. This event is repeatedly generated:

ttk::Repeatedly event generate $w <<Increment>>

ttk::Repeatedly runs its provided script (event generate $w <<Increment>>) and after this script returns schedules a repeated run of this same script. The order of what is happening here is important.

This re-scheduling repeats until the up arrow button is released (mouse button up), at which time ttk::spinbox::Release gets called, which calls ttk::CancelRepeat to, well... cancel the repeat!

This implementation allows to increment repeatedly the spinbox value by pressing and maintaining the up arrow down. Incrementation starts when pushing down the mouse button on the up arrow, and it stops when releasing the mouse button.

Now, with this implementation, when the script bound to event generate $w <<Increment>> includes instructions that allow the event loop to run before the script returns, the ttk::CancelRepeat call from the mouse button release will run from inside the bound script instead of after this script has finished. Since the Repeat(timer) value that is used to identify what needs to be cancelled is only set AFTER the return of the bound script, the cancel call will have no effect.

The fix therefore is to swap the order of script execution and repetition scheduling, see [fb9a363237].


fvogel added on 2022-02-18 20:59:00:

Trimmed down script allowing to reproduce the issue on Windows (at least):

package require Tk
pack [ttk::spinbox .s]
bind .s <<Increment>> {tk_messageBox -message "Message!"}
# and click on the up arrow


fvogel added on 2022-02-16 10:37:24:
Christopher's script below triggers the reported problem on Windows 10, with Tk 8.6.9, or core-8-6-branch, or main.

chrstphrchvz added on 2022-02-16 09:58:59:

The reporter may likely need to provide more information; what is the output of Tk().getvar('tk_patchLevel') and Tk().tk.eval('tk windowingsystem')?

I ran the Tkinter example (need to do from tkinter import *; from tkinter import ttk, messagebox first) and did not observe the reporter's issue for Tcl/Tk 8.6.12 on X11. However if I tweak Francois' Tcl example to resemble the reporter's Tkinter a little more (no separate toplevel, show a messagebox, focus on the spinbox afterward):

package require Tk
set max_length 10
set check_length_calls 0
set input 0
proc check_length {} {
  global check_length_calls max_length
  incr check_length_calls
  if {[focus] ne ".s"} {return}
  if {[.s get] == $max_length} {
    tk_messageBox -title "Max length reached" -message "Too long!" -icon warning
    focus .s
  }
}
wm geometry . 300x100
ttk::label .l -text "Quantity: "
ttk::spinbox .s -from 0 -to 10 -textvariable input
bind .s <<Increment>> {check_length}
ttk::button .b -text "Print fun calls" -command {puts $check_length_calls}
pack .l .s .b

…then I do observe that every time the messagebox is dismissed, another will appear shortly. But I wonder if this behavior is intentional/as documentation would suggest, and just something Tk programs need to beware.


fvogel added on 2022-02-10 07:21:58:

Thank you for your report. Please note that this is the Tcl tracker, not the Tk tracker. If the issue gets confirmed this ticket should be closed and reopened in the Tk tracker.

Furthermore your code seems to be in Python. What I believe is the equivalent Tcl/Tk code is as follows:

package require Tk
set max_length 10
set check_length_calls 0
set input 0
proc check_length {} {
  global check_length_calls max_length
  incr check_length_calls
  if {[focus] ne ".t.s"} {return}
  if {[.t.s get] == $max_length} {
    puts "Too long"
  }
}
toplevel .t
wm geometry .t 300x100
ttk::label .t.l -text "Quantity: "
ttk::spinbox .t.s -from 0 -to 10 -textvariable input
bind .t.s <<Increment>> {check_length}
ttk::button .t.b -text "Print fun calls" -command {puts $check_length_calls}
pack .t.l .t.s .t.b

But with this code I fail to reproduce the problem you describe.