Cross-Platform GUI Programming with wxWidgets

If you find threads daunting, you may well be able to get away with a simpler approach, using timers, idle time processing, or both.

Using wxTimer

The wxTimer class lets your application receive periodic notification, either as a "single shot" or repeatedly. You can use wxTimer as an alternative to threads if you can break your task up into small chunks that are performed every few milliseconds, giving enough time for the application to respond to user interface events.

You can choose how your code will be notified. If you prefer to use a virtual function, derive a class from wxTimer and override the Notify function. If you prefer to receive a wxTimerEvent event, pass a wxEvtHandler pointer to the timer object (in the constructor or using SetOwner), and use EVT_TIMER(id, func) to connect the timer to an event handler function.

Optionally, you can pass an identifier that you passed to the constructor or SetOwner to uniquely identify the timer object and then pass that identifier to EVT_TIMER. This technique is useful if you have several timer objects to handle.

Start the timer by calling Start, passing a time interval in milliseconds and wxTIMER_ONE_SHOT if only a single notification is required. Calling Stop stops the timer, and IsRunning can be used to determine whether the timer is running.

The following example shows the event handler approach.

#define TIMER_ID 1000 class MyFrame : public wxFrame { public: ... void OnTimer(wxTimerEvent& event); private: wxTimer m_timer; }; BEGIN_EVENT_TABLE(MyFrame, wxFrame) EVT_TIMER(TIMER_ID, MyFrame::OnTimer) END_EVENT_TABLE() MyFrame::MyFrame() : m_timer(this, TIMER_ID) { // 1 second interval m_timer.Start(1000); } void MyFrame::OnTimer(wxTimerEvent& event) { // Do whatever you want to do every second here }

Note that your event handler is not guaranteed to be called exactly every n milliseconds; the actual interval depends on what other processing was happening before the timer event was processed.

While we're on the subject of marking time, wxStopWatch is a useful class for measuring time intervals. The constructor starts the timer; you can pause and resume it and get the elapsed time in milliseconds. For example:

wxStopWatch sw; SlowBoringFunction(); // Stop the watch sw.Pause(); wxLogMessage("The slow boring function took %ldms to execute", sw.Time()); // Resume the watch sw.Resume(); SlowBoringFunction(); wxLogMessage("And calling it twice took %ldms in all", sw.Time());

Idle Time Processing

Another way your application can be notified periodically is by implementing idle event handlers. The application object and all windows are sent idle events when other event processing is finished. If an idle event handler calls wxIdleEvent::RequestMore, then idle events will be generated again; otherwise, no more idle events will be sent until after the next batch of user interface events has been found and processed. You should usually call wxIdleEvent::Skip so that base class idle handlers can be called.

In this example, a hypothetical function FinishedIdleTask does portions of a task, and when it's finished, it returns TRue.

class MyFrame : public wxFrame { public: ... void OnIdle(wxIdleEvent& event); // Do a little bit of work, return true if // task finished bool FinishedIdleTask(); }; BEGIN_EVENT_TABLE(MyFrame, wxFrame) EVT_IDLE(MyFrame::OnIdle) END_EVENT_TABLE() void MyFrame::OnIdle(wxIdleEvent& event) { // Do idle processing, ask for more idle // processing if we haven't finished the task if (!FinishedIdleTask()) event.RequestMore(); event.Skip(); }

Although we used a frame in this example, idle event processing is not limited to top-level windows; any window can intercept idle events. For example, you might implement an image display custom control that only resizes its image to fit its window size in idle time to avoid aggressive flicker as the window is resized. To be sure that the application's idle events don't accidentally interfere with the control's implementation, you can override the virtual function OnInternalIdle in your control. Call the base class's OnInternalIdle from your overridden function. The image control might use code that looks like this:

void wxImageCtrl::OnInternalIdle() { wxControl::OnInternalIdle(); if (m_needResize) { m_needResize = false; SizeContent(); } } void wxImageCtrl::OnSize(wxSizeEvent& event) { m_needResize = true; }

Sometimes you might want to force idle event processing, even when there are no other pending events to force idle event processing to happen. You can kick-start idle event processing with the function wxWakeUpIdle. Another method is to start a wxTimer that performs no work; because it sends timer events, it will also cause idle event processing to happen every so often. To process all idle events immediately, call wxApp::ProcessIdle, but note that this might affect internal idle updating, depending on platform (on GTK+, window painting is done in idle time).

User interface update handling, covered previously in Chapter 9, "Creating Custom Dialogs," is a form of idle event processing that enables controls to update themselves by handling wxUpdateUIEvent.

Yielding

When an application is busy doing a lengthy task and the user interface locks up, you might get away with calling wxApp::Yield (or its synonym wxYield) periodically to process pending events. This technique should be used sparingly because it can lead to unwanted side effects, such as reentrancy. For example, Yield might process user command events, leading to the task being executed again, even while it's still in progress. The function wxSafeYield disables all windows, yields, and then enables the windows again to guard against user interaction calling reentrancy. If you pass TRue to wxApp::Yield, it will only yield if it's not already in a yield, which is another way to mitigate reentrancy problems.

If you're trying to update a specify display periodically, try calling wxWindow::Update instead. This processes just the pending paint events for this window.

    Категории