3

I noticed a behaviour in the ttk.Checkbuttons widget. That is, any applied bindings or event handlers to this widget always occurs before the widget's command option method/function is executed.

Significance:

  1. Given that the change in the state of the ttk.Checkbuttons is performed by the command option method/function, the value of the ttk.Checkbuttons['variable'] accessed through an event handler will always be of the old state and not the new state that is defined by the widget's command option method/function.

  2. Changing the value of ttk.Checkbuttons['variable'] in an event handler will mess up the performance of the widget's command option method/function. Hence, ttk.Checkbuttons['variable'] should be set in the widget's command option method/function.

Question:

Given the above, so what is the purpose of binding event handlers to a ttk.Checkbuttons widget?

Event handlers are executed when an event occurs, e.g. when <ButtonRelease-1> has occurred at the ttk.Checkbutton widget. If I want to design a follow-up action based on the state of the widget, I can't do that via event handler as the widget instate has not been updated yet. A workaround is to assume the widget instate and variable obtained in an event handler is the opposite of the reported instate or variable.get() values. However, such an approach seems presumptuous.

How do should I use event handlers for a ttk.Checkbutton to program follow-up actions when there is a state change? Or should I not use it and only use the widgets command option method/function to design a follow-up action based on the state of the widget?

Sun Bear
  • 7,594
  • 11
  • 56
  • 102
  • Did you try using `after?` or a `trace_add` to the variable? :P BTW, I like your questions. You ask things I struggeled myself with. :D – Thingamabobs Nov 11 '21 at 15:21
  • @Atlas435 I have added an insight I learnt while using the `.trace_add()` method as an answer. Maybe it can be of help to u. ;) – Sun Bear Nov 12 '21 at 09:01

2 Answers2

3

Given the above, so what is the purpose of binding event handlers to a ttk.Checkbuttons widget?

So that you can define your own behavior that overrides the default behavior.

How do should I use event handlers for a ttk.Checkbutton to program follow-up actions when there is a state change?

One solution is to not use event handlers. Instead, set a trace on the associated variable. With a variable trace, your callback function will be called after the variable has been set, and will be called every time the value changes even when the change is done by something other than an event.

cb_var = tk.StringVar(value="off")
cb = ttk.Checkbutton(root, variable=cb_var, onvalue="on", offvalue="off", text="Ready?")
cb_var.trace_add('write', callback)

Another solution is to create a custom binding tag that comes after the binding tag of the class.

Example:

cb_var = tk.StringVar(value="off")
cb = ttk.Checkbutton(root, variable=cb_var, onvalue="on", offvalue="off", text="Ready?")
tag = f'custom_{cb}'
cb.bindtags((cb, 'TCheckbutton', '.', tag, 'all'))
cb.bind_class(tag, "<ButtonRelease-1>", callback)

With the above, the callback will be called on the <ButtonRelease-1> button after that event has been processed by the default binding on the widget class. If you do this, you should also add a binding to the spacebar in a similar fashion since you can also set the value of a checkbutton with the spacebar.

For another example of bindtags with a bit more of a discussion about how they work, see this answer to the question How to bind self events in Tkinter Text widget after it will binded by Text widget?. Also, see this answer to the question Basic query regarding bindtags in tkinter.

Bryan Oakley
  • 370,779
  • 53
  • 539
  • 685
  • As I learned something new here. I would like to ask for `when='tail'` since I didnt figured this out by myself yet. [link to docs](https://www.tcl.tk/man/tcl8.7/TkCmd/event.html#M34) – Thingamabobs Nov 11 '21 at 15:58
  • @Atlas435: the `when` attribute doesn't apply here. That's an option to `event_generate` and this answer doesn't use `event_generate`. – Bryan Oakley Nov 11 '21 at 16:02
  • thanks for your reply. Now I got it `check= ttk.Checkbutton(root,text='Test', command=lambda:[check.event_generate('<>',when='tail'), p_command(),],` – Thingamabobs Nov 11 '21 at 16:04
  • @Atlas435: I don't think that does what you think it does. – Bryan Oakley Nov 11 '21 at 16:06
  • I'll better ask a question about it. – Thingamabobs Nov 11 '21 at 16:06
  • For the `.trace_add()` method, I noticed the callback requires 4 positional arguments: `self`, `control variable`, `?`, `write`. May I know what is the 3rd argument suppose to be? – Sun Bear Nov 11 '21 at 17:20
  • @SunBear: https://stackoverflow.com/q/29690463/7432 – Bryan Oakley Nov 11 '21 at 17:26
  • I wrote up my findings and conclusion from using your answer. Can you check it and let me know if corrects are needed? Thanks. :) – Sun Bear Nov 13 '21 at 09:26
0

Below are my findings and conclusion from using .trace_add() method and from using custom binding tag that comes after the binding tag of the class that were shared by @ByranOakley. Hope that it can benefit tkinter users.


Instead, set a trace on the associated variable. With a variable trace, your callback function will be called after the variable has been set, and will be called every time the value changes even when the change is done by something other than an event.

I discovered the following:

  1. The callback executed by the .trace_add() method will always precede the ttk.Checkbutton widget's command option method/function.
  2. In addition, if the value of the variable of the ttk.Checkbutton changes during the execution of the ttk.Checkbutton widget's command option method/function, the callback of the .trace_add() method will execute midway of the widget's command option method/function. Meaning, the callback of the .trace_add() method can be executed more than once during one complete event flow.

To illustrate the above points, below is the printout statements of a test code that I have written. The test code contains 4 ttk.Checkbutton widgets with control variable values B1, B2, B3, B4, respectively, when switched on and '0' value when switched off. When clicked, the ttk.Checkbutton will be toggled between on and off condition. However, at least one ttk.Checkbutton must always be switched on. Initially, these buttons are all switched on. Next, these buttons are clicked sequentially to switched off. The statement ### Callback #### is printout from the callback of the .trace_add() method. The printouts from ### Command Trigger 0 ### to ### Command Trigger 1 ### occurs from the start and end of the method/function of the ttk.Checkbutton widget's command option.

Printout Statements:

# B1 ttk.Checkbutton clicked on
#++ Callback ++++  Button variable values = ['0', 'B2', 'B3', 'B4']  3
### Command Trigger 0 ### Button variable values = ('0', 'B2', 'B3', 'B4') 3
cb=.!App.!checkbutton   text=B1   variable=B1 Checkbutton variable
nchosen != 0
### Command Trigger 1 ### Button variable values = ('0', 'B2', 'B3', 'B4') 3

# B2 ttk.Checkbutton clicked on
#++ Callback ++++  Button variable values = ['0', '0', 'B3', 'B4']  2
### Command Trigger 0 ### Button variable values = ('0', '0', 'B3', 'B4') 2
cb=.!App.!checkbutton2   text=B2   variable=B2 Checkbutton variable
nchosen != 0
### Command Trigger 1 ### Button variable values = ('0', '0', 'B3', 'B4') 2

# B3 ttk.Checkbutton clicked on
#++ Callback ++++  Button variable values = ['0', '0', '0', 'B4']  1
### Command Trigger 0 ### Button variable values = ('0', '0', '0', 'B4') 1
cb=.!App.!checkbutton3   text=B3   variable=B3 Checkbutton variable
nchosen != 0
### Command Trigger 1 ### Button variable values = ('0', '0', '0', 'B4') 1

# B4 ttk.Checkbutton clicked on
#++ Callback ++++  Button variable values = ['0', '0', '0', '0']  0
### Command Trigger 0 ### Button variable values = ('0', '0', '0', '0') 0
cb=.!App.!checkbutton4   text=B4   variable=B4 Checkbutton variable
nchosen == 0

#++ Callback ++++  Button variable values = ['0', '0', '0', 'B4']  1
### Command Trigger 1 ### Button variable values = ('0', '0', '0', 'B4') 1

Significance:

As the callback of the .trace_add() method always precede the execution of the method/function of the ttk.Checkbutton widget's command option and can occur during the execution of the method/function of the ttk.Checkbutton widget's command option during each event flow, the content of the callback of the .trace_add() method must anticipate such an occurrence.


Another solution is to create a custom binding tag that comes after the binding tag of the class.

..., the callback will be called on the button after that event has been processed by the default binding on the widget class.

I believe the term callback should be called an event handler instead. In any case, printout statements of such an arrangement is shown below. It evidences that the event handler will always execute after the ttk.Checkbutton widget's command option method/function is fully executed,. Unlike the process flow from using a .trace_add() method, the execution of the ttk.Checkbutton widget's command option method/function is never interrupted.

Printout Statements:

# B1 ttk.Checkbutton clicked on
### Command Trigger 0 ### Button variable values = ('0', 'B2', 'B3', 'B4') 3
cb=.!App.!checkbutton   text=B1   variable=jpg variable
nchosen != 0
### Command Trigger 1 ### Button variable values = ('0', 'B2', 'B3', 'B4') 3
#&& run_event_handler &&&&  Button variable values = ('0', 'B2', 'B3', 'B4') 3

# B2 ttk.Checkbutton clicked on
### Command Trigger 0 ### Button variable values = ('0', '0', 'B3', 'B4') 2
cb=.!App.!checkbutton2   text=B2   variable=B2 variable
nchosen != 0
### Command Trigger 1 ### Button variable values = ('0', '0', 'B3', 'B4') 2
#&& run_event_handler &&&&  Button variable values = ('0', '0', 'B3', 'B4') 2

# B3 ttk.Checkbutton clicked on
### Command Trigger 0 ### Button variable values = ('0', '0', '0', 'B4') 1
cb=.!App.!checkbutton3   text=B3   variable=B3 variable
nchosen != 0
### Command Trigger 1 ### Button variable values = ('0', '0', '0', 'B4') 1
#&& run_event_handler &&&&  Button variable values = ('0', '0', '0', 'B4') 1

# B4 ttk.Checkbutton clicked on
### Command Trigger 0 ### Button variable values = ('0', '0', '0', '0') 0
cb=.!App.!checkbutton4   text=B4   variable=tif variable
nchosen == 0
### Command Trigger 1 ### Button variable values = ('0', '0', '0', 'B4') 1
#&& run_event_handler &&&&  Button variable values = ('0', '0', '0', 'B4') 1

Conclusions:

  1. My need was to fully configure the ttk.Checkbutton widgets before executing follow-up process(s). As such, using the widget's command option method/function to first configure the widget and using customised binding tags that comes after the binding tag of the widget class to perform follow-up process(s) were found to be the best approach.
  2. After an event occurs at the widget, the sequence of the execution of the following methods and handlers was observed:
    1. event handler of the .bind() method
    2. method/function of the widget's command option
    3. event handler of the .bind_class() method that has a custom binding tag that comes after the binding tag of the class of the widget.
  3. The callback of a .trace_add() method always occurred before the execution of item 2. Furthermore, it can also occur in the midst of executing items 2 and 3.
Sun Bear
  • 7,594
  • 11
  • 56
  • 102