Outils pour utilisateurs

Outils du site


issue136:python

Ceci est une ancienne révision du document !


A little while ago, I wrote a blog article about a problem with Python and Tkinter. It has to do with the Checkbox widget not responding properly to the click event, at least in my mind. Here is how it all came about.

I was working on a custom widget for use with a database that implemented a scrolled checked list box. I figured that it would be helpful to be able to show a series of selected items from a list of categories on a per-record basis. As normal, I was using Page to create the GUI.

I wanted the widget to raise an event whenever any of the checkboxes in the widget were clicked, then display a list of all the selected items. It didn’t really seem like a horribly daunting task at the time. However, when the event fired, and my callback code ran, the last checkbox that was clicked wasn’t on the list. To make matters worse, if I clicked on the same button again, basically unchecking the button, that button THEN showed up on the list. Now that didn’t make any sense to me. I started digging and found the problem.

As you probably know, the Checkbutton widget looks just like any GUI checkbutton. It’s an empty box that, when you click it, it shows a check in the box. If you click it again, the check goes away and the box is blank again. Under Tkinter, the Checkbutton widget provides a variable that you can monitor to show the state of the widget by using the .get() method of the widget. If it returns a “1”, then the checkbutton is checked, and if it returns a “0”, it’s unchecked. Pretty simple. I wanted to use the mouse-click event to be the trigger to check the state of the widget.

Most of my work these days in under Linux, so that’s the operating system that I did the base work under. I tried various workarounds and still couldn’t get the silly thing to work the way it should. So, I went back to the basics. I created a simple GUI using Page and started exploring.

It looked something like this…

Nothing spectacular, but it would help with my snooping. If you have used Page before, you know that you usually bind an event (either mouse or keyboard) to a particular widget. In this case, I set the binding to the <Button-1> event (left mouse button click) for the Checkbutton widget. Then, in the callback function for the event, I printed out the state of the Checkbutton. The code (below) was pretty simple…

The first two lines are provided by Page as a simple notification that the user has triggered the callback function. I usually leave these in until I’m done with the majority of the early testing. The last line of the function simply shows a ‘1’ or a ‘0’ in the terminal window, to show me what is going on. The thought being that when I click to check the button, I will see a 1 in the terminal.

However, what I saw when I ran the program and clicked the widget, was…

chkbtntest_support.on_ChkBtnClick

Now, I knew that I didn’t initialize the checkbutton, so the state was undetermined at startup, but the check showed on the widget, so the state should have been ‘1’. It wasn’t. I clicked it again and the check went away as expected, but the terminal showed:

chkbtntest_support.on_ChkBtnClick

1

It didn’t make any sense at all. The check value is 180 degrees out of sync with the visual indicator, just like in my custom widget. I’ve used the Checkbutton widget before, many times, and used the .get() method to query the status of that widget, but always from another event like a ‘Save’ button or something like that.

Frustrated to the MAX, I decided out of desperation to try the <ButtonRelease-1> event and see what happened then. I disabled the click event, and bound the other event to the widget. I wrote the same code for this event, and, low and behold, it worked properly. Everytime I let up on the mouse button, the state was queried and printed to the terminal correctly!

This made me feel much better, since now I have a workaround at least. I modified the code for my custom widget, and it still worked, so I was a happy camper. I even tried the code under both Python 2.x and 3.x and it worked as I wanted. However, this elation was only to be short lived.

I took my code over to a Windows 10 machine and started it up. Knowing that I had gotten it all figured out, I knew it would work just fine, since the code is all very basic (forgive the term) and nothing would go wrong.

Much to my surprise, it couldn’t have gone worse. Not only did the capture of the widget state not work on the mouse release event, it didn’t work using the mouse down event. I could query the state from a different “standard” button AFTER I had clicked the Checkbutton, but I couldn’t get a “live update” to save my life. Nothing that I tried worked.

Eventually I came up with a very “dirty” workaround for it under Windows and basically called it a day. I still wanted to find out what was going on, but other projects came up so that project was left for a while. Then, one day, I got some free time and decide to enhance the custom widget to support the scroll wheel. The scroll bar worked with the scroll wheel, but, if you tried to scroll from the center of the widget, nothing happened. That was so counterintuitive that I couldn’t let it go. After a bunch of research, I found a way to do it. Unfortunately, Linux handles the mouse scroll wheel differently than Windows did, so I was back wrapping code based on the operating system. I tried again to capture the Checkbutton state in “real time”, but I didn’t get any further. Once again, I put the code off to the side with the plan to get back to it “some day”.

The other day, I get an email from Don Rozenberg, who is the author of Page. Among other things, he said “Under Linux Mint, pressing and holding button-1 on one of the checkboxes causes it to toggle, whereas under Windows the checkbox doesn't toggle until the button-1 is released.”

I decided to go back to basics and try to figure out, once and for all, a way to monitor the check state correctly for both operating systems.

I recreated my simple Checkbutton test GUI app in Page and was in the middle of doing the bindings when I realized that I had forgotten that the Checkbutton widget also has a command attribute that you can use to respond to the mouse events. The main reason that I don’t use it more is that you can’t pass the event object into the callback function, which many times is useful to have since it includes the mouse position. This time, however, it isn’t needed, so I added it to the mix. So now I had callback functions for mouse down, mouse up, and the command event. On the following page (top right) is the code.

This allowed me to see what happened whenever the mouse got clicked in every way. I started under Linux, since I thought I knew what should be happening. When I ran the program, I got the following (next page, bottom right) in the terminal window…

That was what I wanted to see. The mouse-down event came first, then the command callback was fired, and finally the mouse-up fired. Good. So, I bundled up my app and booted up the Windows machine. I copied the code to a folder and ran it under Python. The form came up correctly and I clicked the Checkbutton to set it to Checked.

I fully expected to see pretty much the same thing, except that I wouldn’t see the correct state for the button just like I did before. Much to my surprise, this is what I got:

chkbtntest_support.on_ChkBtnClick Unchecked

chkbtntest_support.on_ChkBtnRelease Unchecked

chkbtntest_support.on_ChkBtnCommand Checked

Under Windows, the mouse up event fires before the command event. AND the command event can query the state of the Checkbutton. Just to make sure, I clicked it again to uncheck it and sure enough I got this:

chkbtntest_support.on_ChkBtnClick Checked

chkbtntest_support.on_ChkBtnRelease Checked

chkbtntest_support.on_ChkBtnCommand Unchecked

So now I know that under Windows, the command event happens AFTER the mouse-up event, and actually does correctly follow the state of the widget.

What this means for someone who uses Python and Tkinter under the Mac operating system, I have no idea, but I am guessing that it will pretty much follow the Linux results.

I intend to dig a bit further into this to see exactly where the “problem” is and then try to figure who I should report this to.

Just goes to show, don’t give up if, the first hundred times, things don’t go the way you expect them to.

Until next time, have a GREAT month.

issue136/python.1536128049.txt.gz · Dernière modification : 2018/09/05 08:14 de d52fr