Wednesday, February 24, 2010

Click, Drag and Drop widgets in wxPython

Recently, I was running through a few posts at http://python-forum.org during my free time today and I came across someone who need help in creating a frame, where in the widgets (namely a push button) could be pressed and dragged around the frame, and dropped off at any part of the frame once it was depressed.

At first, I thought it was nothing to break my head over. In fact I wrote a little snipped of code in IDLE (on Windows Vista) and it worked! What was a bit disconcerting was the fact that the widget moved around alright, but didn't really flow smoothly along the frame. The transition from one point to the other was really jarring.

I have worked extensively on wxPython and know how faulty its behavior is on Windows. The true test for an application in wxPython is on a Linux box. I booted into my Linux partition, in a hope to recreate the effect that I had so easily created in windows. A few shell commands later, I could see the familiar window, but to my disappointment, the buttons didn't drag. They just sat there in one place, doing nothing.

Determined to solve this issue, I dug deeper into wxPython. Starting out with penning down the sequence of events that occur, I understood that we have to organize the events starting with the pressing of the left mouse button (only when the pointer is on the push button); followed by the drag/motion event of the mouse and finally concluding with the mouse up event.

However, what was challenging was the fact that once the mouse button was pressed down, the event loop didn't recognize any other mouse events on the button. So how does one go about recognizing the drag/motion event?? I tried to skip (event.Skip()) after receiving the mouse down event, but that didn't seem to work. After about an hour of google, I devised a mechanism to complete the desired effect.

After receiving the mouse down event, we turn on something called "mouse capture" - this focuses all the events coming from the mouse (motion, right click etc) to be directed to the frame and not to the button or the panel. That way, I was able to catch and handle the motion/dragging event. This was bound to the frame and not to any widget in particular. Once the dragging around of the button was complete, the mouse button is depressed causing the "mouse up" event to be released (again directed/bound to the frame, not to any widget in particular). In the handler for the mouse up event, I make a simple check to see if the mouse capture is turned on. If it is, then we simply "release capture" of mouse events, restoring the frame back to its original state.

Here is what the results looked like:

video

The source code for the same is available here (look for a file named wx_DragButtons.py).

The code contains the bulk of the documentation. This is a small, yet elegant solution, which only makes me wonder the kind of effort that goes into creating dynamic user interfaces. Alas, I hope the folks at the forum find this post useful.

0 comments:

Post a Comment