Analysis
What I believe to be a Minimal, Complete, and Verifiable example for the above-mentioned issue:
import tkinter as tk
def callback(event):
#checkbutton.wait_variable(var)
checkbutton['text'] = var.get()
if __name__ == '__main__':
root = tk.Tk()
var = tk.BooleanVar()
checkbutton = tk.Checkbutton(root, text="Check", variable=var,
onvalue=True, offvalue=False)
checkbutton.pack()
checkbutton.bind("<ButtonRelease-1>", callback)
root.mainloop()
This happens simply because the virtual events, for example, the one that sets a Checkbutton
's value based on its state (checked / unchecked), are handled after events attached with bind
are handled(See the very last MCVE provided).
For more info see: https://stackoverflow.com/a/3513906/7032856
For the code you've provided, it means that Checkbutton
's value won't change until the bind
event handle filter_categ
method is finished. But filter_categ
won't move on unless Checkbutton
's value is changed.
Which makes your program to be stuck at a local event loop, waiting for a 'break'
, a 'break'
comes only if the loop is completed. Feels like a paradox.
Review the following example:
import tkinter as tk
import random
def modify_var(event):
var.set(random.choice((True, False)))
def callback(event):
checkbutton.wait_variable(var)
checkbutton['text'] = var.get()
if __name__ == '__main__':
root = tk.Tk()
var = tk.BooleanVar()
checkbutton = tk.Checkbutton(root, text="Check", variable=var,
onvalue=True, offvalue=False)
checkbutton.pack(fill='both', expand=True)
checkbutton.bind("<ButtonRelease-1>", callback)
root.bind_all("<Escape>", modify_var)
root.mainloop()
It has the same paradoxical behavior your code does, but with the only exception, when Escape is hit, the variable wait_variable
waits, var
, is modified, so the local event loop is broken.
Solutions
By using command
option in Checkbutton
Replace:
self.filter_checks[i].bind("<ButtonRelease-1>", self.filter_categ)
with:
self.filter_checks[i]['command'] = self.filter_categ
This is by far the simplest. Also, you can overwrite your method definition to:
def filter_categ(self):
...
unless it would be used later on by other events.
Its MCVE:
# By using command option in Checkbutton MCVE
import tkinter as tk
def callback():
checkbutton['text'] = var.get()
if __name__ == '__main__':
root = tk.Tk()
var = tk.BooleanVar()
checkbutton = tk.Checkbutton(root, text="Check", variable=var,
onvalue=True, offvalue=False)
checkbutton['command'] = callback
checkbutton.pack(fill='both', expand=True)
root.mainloop()
By using Tkinter Variable Class and, trace_add
replace:
self.filter_checks[i].bind("<ButtonRelease-1>", self.filter_categ)
with:
self.filter_categs_vars[i].trace_add('write', self.filter_categ)
with the above line, trace_add
will call its callback, self.filter_categ
with 3 arguments, for which your method needs to accept those arguments as well, whenever the variable it is attached to, self.filter_categs_vars[i]
, gets modified. Replace:
def filter_categ(self, event):
with:
def filter_categ(self, *args):
Its MCVE:
# By using Tkinter Variable Class and, trace_add
import tkinter as tk
def callback(*args):
checkbutton['text'] = var.get()
if __name__ == '__main__':
root = tk.Tk()
var = tk.BooleanVar()
checkbutton = tk.Checkbutton(root, text="Check", variable=var,
onvalue=True, offvalue=False)
var.trace_add('write', callback)
checkbutton.pack(fill='both', expand=True)
root.mainloop()
By shifting the order of event handle sequence
self.filter_checks[i].bind("<ButtonRelease-1>", self.filter_categ) # this line is not modified
self.filter_checks[i].bindtags((self.filter_checks[i].bindtags()[1:] + self.filter_checks[i].bindtags()[:1]))
This makes it so that "<ButtonRelease-1>"
event is handled the latest, as in Checkbutton
's variable value will change before self.filter_categ
is executed.
Its MCVE:
# By shifting the order of event handle sequence MCVE
import tkinter as tk
def callback(event):
checkbutton['text'] = var.get()
if __name__ == '__main__':
root = tk.Tk()
var = tk.BooleanVar()
checkbutton = tk.Checkbutton(root, text="Check", variable=var,
onvalue=True, offvalue=False)
checkbutton.pack(fill='both', expand=True)
checkbutton.bind("<ButtonRelease-1>", callback)
# comment the line below out to see the difference
checkbutton.bindtags((checkbutton.bindtags()[1:] + checkbutton.bindtags()[:1]))
root.mainloop()