+ MyImageDialog dlg(this);
+
+ dlg.ShowModal();
+}
+
+
+// ----------------------------------------------------------------------------
+// MyImageDialog
+// ----------------------------------------------------------------------------
+
+BEGIN_EVENT_TABLE(MyImageDialog, wxDialog)
+ EVT_THREAD(GUITHREAD_EVENT, MyImageDialog::OnGUIThreadEvent)
+ EVT_PAINT(MyImageDialog::OnPaint)
+END_EVENT_TABLE()
+
+MyImageDialog::MyImageDialog(wxFrame *parent)
+ : wxDialog(parent, wxID_ANY, "Image created by a secondary thread",
+ wxDefaultPosition, wxSize(GUITHREAD_BMP_SIZE,GUITHREAD_BMP_SIZE)*1.5, wxDEFAULT_DIALOG_STYLE),
+ m_thread(this)
+{
+ m_nCurrentProgress = 0;
+
+ CentreOnScreen();
+
+ // NOTE: no need to lock m_csBmp until the thread isn't started:
+
+ // create the bitmap
+ if (!m_bmp.Create(GUITHREAD_BMP_SIZE,GUITHREAD_BMP_SIZE) || !m_bmp.IsOk())
+ {
+ wxLogError("Couldn't create the bitmap!");
+ return;
+ }
+
+ // clean it
+ wxMemoryDC dc(m_bmp);
+ dc.SetBackground(*wxBLACK_BRUSH);
+ dc.Clear();
+
+ // draw the bitmap from a secondary thread
+ if ( m_thread.Create() != wxTHREAD_NO_ERROR ||
+ m_thread.Run() != wxTHREAD_NO_ERROR )
+ {
+ wxLogError(wxT("Can't create/run thread!"));
+ return;
+ }
+}
+
+MyImageDialog::~MyImageDialog()
+{
+ // in case our thread is still running and for some reason we are destroyed,
+ // do wait for the thread to complete as it assumes that its MyImageDialog
+ // pointer is always valid
+ m_thread.Delete();
+}
+
+void MyImageDialog::OnGUIThreadEvent(wxThreadEvent& event)
+{
+ m_nCurrentProgress = int(((float)event.GetInt()*100)/GUITHREAD_NUM_UPDATES);
+
+ Refresh();
+}
+
+void MyImageDialog::OnPaint(wxPaintEvent& WXUNUSED(evt))
+{
+ wxPaintDC dc(this);
+
+ const wxSize& sz = dc.GetSize();
+
+ {
+ // paint the bitmap
+ wxCriticalSectionLocker locker(m_csBmp);
+ dc.DrawBitmap(m_bmp, (sz.GetWidth()-GUITHREAD_BMP_SIZE)/2,
+ (sz.GetHeight()-GUITHREAD_BMP_SIZE)/2);
+ }
+
+ // paint a sort of progress bar with a 10px border:
+ dc.SetBrush(*wxRED_BRUSH);
+ dc.DrawRectangle(10,10, m_nCurrentProgress*(sz.GetWidth()-20)/100,30);
+ dc.SetTextForeground(*wxBLUE);
+ dc.DrawText(wxString::Format("%d%%", m_nCurrentProgress),
+ (sz.GetWidth()-dc.GetCharWidth()*2)/2,
+ 25-dc.GetCharHeight()/2);
+}
+
+// ----------------------------------------------------------------------------
+// MyThread
+// ----------------------------------------------------------------------------
+
+MyThread::MyThread()
+ : wxThread()
+{
+ m_count = 0;
+}
+
+MyThread::~MyThread()
+{
+ wxCriticalSectionLocker locker(wxGetApp().m_critsect);
+
+ wxArrayThread& threads = wxGetApp().m_threads;
+ threads.Remove(this);
+
+ if ( threads.IsEmpty() )
+ {
+ // signal the main thread that there are no more threads left if it is
+ // waiting for us
+ if ( wxGetApp().m_shuttingDown )
+ {
+ wxGetApp().m_shuttingDown = false;
+
+ wxGetApp().m_semAllDone.Post();
+ }
+ }
+}
+
+wxThread::ExitCode MyThread::Entry()
+{
+ wxLogMessage("Thread started (priority = %u).", GetPriority());
+
+ for ( m_count = 0; m_count < 10; m_count++ )
+ {
+ // check if the application is shutting down: in this case all threads
+ // should stop a.s.a.p.
+ {
+ wxCriticalSectionLocker locker(wxGetApp().m_critsect);
+ if ( wxGetApp().m_shuttingDown )
+ return NULL;
+ }
+
+ // check if just this thread was asked to exit
+ if ( TestDestroy() )
+ break;
+
+ wxLogMessage("Thread progress: %u", m_count);
+
+ // wxSleep() can't be called from non-GUI thread!
+ wxThread::Sleep(1000);
+ }
+
+ wxLogMessage("Thread finished.");
+
+ return NULL;
+}
+
+
+// ----------------------------------------------------------------------------
+// MyWorkerThread
+// ----------------------------------------------------------------------------
+
+// define this symbol to 1 to test if the YieldFor() call in the wxProgressDialog::Update
+// function provokes a race condition in which the second wxThreadEvent posted by
+// MyWorkerThread::Entry is processed by the YieldFor() call of wxProgressDialog::Update
+// and results in the destruction of the progress dialog itself, resulting in a crash later.
+#define TEST_YIELD_RACE_CONDITION 0
+
+MyWorkerThread::MyWorkerThread(MyFrame *frame)
+ : wxThread()
+{
+ m_frame = frame;
+ m_count = 0;
+}
+
+void MyWorkerThread::OnExit()
+{
+}
+
+wxThread::ExitCode MyWorkerThread::Entry()
+{
+#if TEST_YIELD_RACE_CONDITION
+ if ( TestDestroy() )
+ return NULL;
+
+ wxThreadEvent event( wxEVT_THREAD, WORKER_EVENT );
+
+ event.SetInt( 50 );
+ wxQueueEvent( m_frame, event.Clone() );
+
+ event.SetInt(-1);
+ wxQueueEvent( m_frame, event.Clone() );
+#else
+ for ( m_count = 0; !m_frame->Cancelled() && (m_count < 100); m_count++ )
+ {
+ // check if we were asked to exit
+ if ( TestDestroy() )
+ break;
+
+ // create any type of command event here
+ wxThreadEvent event( wxEVT_THREAD, WORKER_EVENT );
+ event.SetInt( m_count );
+
+ // send in a thread-safe way
+ wxQueueEvent( m_frame, event.Clone() );
+
+ wxMilliSleep(200);
+ }
+
+ wxThreadEvent event( wxEVT_THREAD, WORKER_EVENT );
+ event.SetInt(-1); // that's all
+ wxQueueEvent( m_frame, event.Clone() );
+#endif
+
+ return NULL;
+}
+
+
+// ----------------------------------------------------------------------------
+// MyGUIThread
+// ----------------------------------------------------------------------------
+
+wxThread::ExitCode MyGUIThread::Entry()
+{
+ // uncomment this to check that disabling logging here does disable it for
+ // this thread -- but not the main one if you also comment out wxLogNull
+ // line in MyFrame::OnStartGUIThread()
+ //wxLogNull noLog;
+
+ // this goes to the main window
+ wxLogMessage("GUI thread starting");
+
+ // use a thread-specific log target for this thread to show that its
+ // messages don't appear in the main window while it runs
+ wxLogBuffer logBuf;
+ wxLog::SetThreadActiveTarget(&logBuf);
+
+ for (int i=0; i<GUITHREAD_NUM_UPDATES && !TestDestroy(); i++)
+ {
+ // inform the GUI toolkit that we're going to use GUI functions
+ // from a secondary thread:
+ wxMutexGuiEnter();
+
+ {
+ wxCriticalSectionLocker lock(m_dlg->m_csBmp);
+
+ // draw some more stuff on the bitmap
+ wxMemoryDC dc(m_dlg->m_bmp);
+ dc.SetBrush((i%2)==0 ? *wxBLUE_BRUSH : *wxGREEN_BRUSH);
+ dc.DrawRectangle(rand()%GUITHREAD_BMP_SIZE, rand()%GUITHREAD_BMP_SIZE, 30, 30);
+
+ // simulate long drawing time:
+ wxMilliSleep(200);
+ }
+
+ // if we don't release the GUI mutex the MyImageDialog won't be able to refresh
+ wxMutexGuiLeave();
+
+ // notify the dialog that another piece of our masterpiece is complete:
+ wxThreadEvent event( wxEVT_THREAD, GUITHREAD_EVENT );
+ event.SetInt(i+1);
+ wxQueueEvent( m_dlg, event.Clone() );
+
+ if ( !((i + 1) % 10) )
+ {
+ // this message will go to the buffer
+ wxLogMessage("Step #%d.", i + 1);
+ }
+
+ // give the main thread the time to refresh before we lock the GUI mutex again
+ // FIXME: find a better way to do this!
+ wxMilliSleep(100);
+ }
+
+ // now remove the thread-specific thread target
+ wxLog::SetThreadActiveTarget(NULL);
+
+ // so that this goes to the main window again
+ wxLogMessage("GUI thread finished.");
+
+ return (ExitCode)0;