Sunday, January 20, 2013

About the benefits of procrastination

And no, I'm not going to speak about why postponing 2.9.5 release for so long after its initially scheduled October 2012 release date is a good thing. Instead, I'd like to explain how can the recently added CallAfter() function make your life simpler.

CallAfter() is a method of wxEvtHandler, which means that it's available in the code of any classes deriving from it, including wxWindow but also wxApp and several others. And it basically allows to call another method of the class in which it is used asynchronously later. Notice that no threads are involved, the call to that other method still happens on the same main GUI thread but at a slightly later time. To be precise, the control first must return to the event loop and only then (i.e. "after") the specified method will be called.

Using it is quite simple: if you want to call some method Foo() taking 0, 1 or 2 arguments (this is the current maximum but it could be extended in the future if really needed), you can just write

void SomeWindow::OnSomeEvent(wxEvent& event) { // If the method doesn't take any arguments: CallAfter(&SomeWindow::Foo); // If the method takes an argument: CallAfter(&SomeWindow::Foo, arg1); // Or if it takes two: CallAfter(&SomeWindow::Foo, arg1, arg2); }
Notice that Foo() must be a method of the same class. But other than that, this is pretty straightforward.

The above explains how to use CallAfter() but doesn't answer the really interesting question which is, of course, why would you ever want to do this instead of just calling Foo() directly? The answer is that some things often just can't be done immediately in a GUI program, either because doing them would interfere with its normal flow of control and generate extra events, potentially resulting in unexpectedly re-entering the same event handler and infinite recursion. Or because the current state of the program simply doesn't allow them. An example of the first case is showing a message box from a window activation event handler: doing this would immediately deactivate the window back again which is probably unexpected. An example of the second case is showing a message box from event handlers for all events generated by mouse click, e.g. button press event: as the mouse is captured by the button when the click happens, its mouse grab is released unexpectedly and the standard control itself can get confused by it, e.g. the button could appear to be stuck in pressed state.

The message box example is actually a poor one because wxWidgets already provides an easy way to prevent such problems from happening by using wxLogMessage() function which already postpones showing the message box until the next event loop iteration. What CallAfter() does is to provide you with a generic way to do the same thing very simply.

As a more realistic example, let's consider a common GUI layout with a tree control on the left and some window displaying the items in the tree on the right. Suppose you'd like to update the display when a tree item is double-clicked. There is a wxEVT_COMMAND_TREE_ITEM_ACTIVATED event corresponding to this action so typically you'd do something like this:

TreeViewFrame::TreeViewFrame() { wxTreeCtrl* tree = new wxTreeCtrl(this, ...); tree->Bind(wxEVT_COMMAND_TREE_ITEM_ACTIVATED, &TreeViewFrame::OnActivated, this); // This is the window showing the current item. m_view = new ViewWindow(this); DisplayItem(initial); } void TreeViewFrame::OnActivated(wxTreeEvent& event) { ... retrieve the item to show from the event (typically using client data) ... DisplayItem(item); } void TreeViewFrame::DisplayItem(ItemType item) { m_view->ShowItem(item); // Give the focus to the view to allow the user to easily use the keyboard. m_view->SetFocus(); }
And this works perfectly well except that the view doesn't get the focus after double clicking an item in the tree under Windows. The reason for this is that the native tree control there sends the activation event first and then processes the (second) mouse click which resulted in it itself, and as part of this processing it sets the focus to the control itself (as it's normal for the control to become focused when clicked). So while the view does get focus during the activation event handler execution, it loses it almost immediately afterwards.

In a perfect world, wxWidgets would somehow isolate you from this peculiarity of Windows tree control implementation. But doing this is rather not trivial and, remember, this is just one of several examples. So in the imperfect world we live in, you need to deal with this problem at your application level. CallAfter() reduces the imperfectness of having to do it to acceptably low level as all you need to change in the snippet above to make it work is to replace the last line of the event handler with the call to it:

void TreeViewFrame::OnActivated(wxTreeEvent& event) { ... retrieve the item to show from the event (typically using client data) ... CallAfter(&TreeViewFrame::DisplayItem, item); }
All we did was to delay the item display until the next event loop iteration after the one which dispatched the activated event itself and this is enough to get rid of the focus problem.

So if you have any capture, focus or other global state problems (and notice that they can stem from your own code as well, and not just behaviour of the native controls or wxWidgets itself), you can often use CallAfter() to easily solve them by just postponing whatever you need to do until slightly later. Remember that laziness is a cardinal virtue for a programmer -- and now wxWidgets helps you to cultivate it!


P.S. But we'll still try to release 2.9.5 soon...
Post a Comment