I'm relatively new to wxWidgets and trying to get a grasp on how to properly implement threading and events and all that good stuff. I was able to get a simple frame running in my projects directory just fine, but as I ran into issues when trying to add in threading.
I ran the Thread demo given in the "samples" of the repo without issue but as I understand it, the more recent preferred method is to use wxThreadHelper because you don't need to pass around frame pointers.
I'm essentially starting off in the wxThreadHelper example that's given here. All I really did was separate things into header and src.
Header:
// For compilers that support precompilation, includes "wx/wx.h".
#include "wx/wxprec.h"
// for all others, include the necessary headers (this file is usually all you
// need because it includes almost all "standard" wxWidgets headers)
#ifndef WX_PRECOMP
#include "wx/wx.h"
#endif
#if !wxUSE_THREADS
#error "This sample requires thread support!"
#endif // wxUSE_THREADS
wxDECLARE_EVENT(EVT_COMMS_UPDATE, wxThreadEvent);
enum {LAYOUT_TEST_PROPORTIONS = wxID_HIGHEST+1,
LAYOUT_TEST_SIZER,
LAYOUT_TEST_NB_SIZER
};
class wxMainFrame : public wxFrame, public wxThreadHelper, private wxLog {
private:
public:
wxMainFrame();
~wxMainFrame(){
// it's better to do any thread cleanup in the OnClose()
// event handler, rather than in the destructor.
// This is because the event loop for a top-level window is not
// active anymore when its destructor is called and if the thread
// sends events when ending, they won't be processed unless
// you ended the thread from OnClose.
// See @ref overview_windowdeletion for more info.
}
void onCommsUpdate_t();
void OnClose(wxCloseEvent& evt);
wxTextCtrl * m_txtctrl;
protected:
virtual wxThread::ExitCode Entry();
//fields update from threads
double data_t;
wxCriticalSection m_dataCS; // protects field above - i don't really know how this thing scopes the CS
wxDECLARE_EVENT_TABLE();
};
SRC:
#include "wxMainFrame.h"
wxDEFINE_EVENT(EVT_COMMS_UPDATE, wxThreadEvent)
wxBEGIN_EVENT_TABLE(wxMainFrame, wxFrame)
EVT_THREAD(EVT_COMMS_UPDATE, wxMainFrame::onCommsUpdate_t)
EVT_CLOSE(wxMainFrame::OnClose)
wxEND_EVENT_TABLE()
wxMainFrame::wxMainFrame() : wxFrame(NULL, wxID_ANY, "wxWidgets Layout Demo") {
//Enable thread
if (CreateThread(wxTHREAD_JOINABLE) != wxTHREAD_NO_ERROR){
wxLogError("Could not create the worker thread!");
return;
}
// go!
if (GetThread()->Run() != wxTHREAD_NO_ERROR) {
wxLogError("Could not run the worker thread!");
return;
}
this->data_t = 0;
// Make a menubar
wxMenu *file_menu = new wxMenu;
file_menu->Append(LAYOUT_TEST_PROPORTIONS, "&Proportions demo...\tF1");
file_menu->Append(LAYOUT_TEST_SIZER, "Test wx&FlexSizer...\tF2");
file_menu->Append(LAYOUT_TEST_NB_SIZER, "Test ¬ebook sizers...\tF3");
wxMenuBar *menu_bar = new wxMenuBar;
menu_bar->Append(file_menu, "&File");
// Associate the menu bar with the frame
SetMenuBar(menu_bar);
// create the logging text control and a header showing the meaning of the
// different columns
wxTextCtrl *header = new wxTextCtrl(this, wxID_ANY, "",
wxDefaultPosition, wxDefaultSize,
wxTE_READONLY);
//DoLogLine(header, " Time", " Thread", "Message");
m_txtctrl = new wxTextCtrl(this, wxID_ANY, "",
wxDefaultPosition, wxDefaultSize,
wxTE_MULTILINE | wxTE_READONLY);
wxLog::SetActiveTarget(this);
// use fixed width font to align output in nice columns
wxFont font(wxFontInfo().Family(wxFONTFAMILY_TELETYPE));
header ->SetFont(font);
m_txtctrl ->SetFont(font);
m_txtctrl->SetFocus(); //Field to have focus on startu
// layout and show the frame
wxBoxSizer *sizer = new wxBoxSizer(wxVERTICAL);
sizer->Add(header, wxSizerFlags().Expand());
sizer->Add(m_txtctrl, wxSizerFlags(1).Expand());
SetSizer(sizer);
SetSize(600, 350);
}
wxThread::ExitCode wxMainFrame::Entry(){
// IMPORTANT: this function gets executed in the secondary thread context!
while (!GetThread()->TestDestroy()) {
// since this Entry() is implemented in MyFrame context we don't
// need any pointer to access the m_data, m_processedData, m_dataCS
// variables... very nice!
wxCriticalSectionLocker lock(m_dataCS);
this->data_t++;
// VERY IMPORTANT: do not call any GUI function inside this
// function; rather use wxQueueEvent():
wxQueueEvent(this, new wxThreadEvent(EVT_COMMS_UPDATE));
}
// TestDestroy() returned true (which means the main thread asked us
// to terminate as soon as possible) or we ended the long task...
return (wxThread::ExitCode)0;
}
void wxMainFrame::onCommsUpdate_t(){
//Do thread stuff
//wxCriticalSectionLocker lock(m_dataCS);
//std::cout << this->data_t;
}
void wxMainFrame::OnClose(wxCloseEvent& evt){
// important: before terminating, we _must_ wait for our joinable
// thread to end, if it's running; in fact it uses variables of this
// instance and posts events to *this event handler
if (GetThread() && // DoStartALongTask() may have not been called
GetThread()->IsRunning())
GetThread()->Wait();
Destroy();
}
Which results in this compilation error:
In file included from /usr/local/include/wx-3.1/wx/wx.h:24,
from includes/wxMainFrame.h:10,
from src/wxMainFrame.cpp:2:
/usr/local/include/wx-3.1/wx/event.h:4350:5: error: expected ‘,’ or ‘;’ before ‘ const’
4350 | const wxEventTable theClass::sm_eventTable = \
| ^~~~~
src/wxMainFrame.cpp:5:1: note: in expansion of macro ‘wxBEGIN_EVENT_TABLE’
5 | wxBEGIN_EVENT_TABLE(wxMainFrame, wxFrame)
As you can see this is a basic syntax error that I can see/fix if I go into /usr/local/include/wx-3.1/wx/event.h:4350
but this doesn't make sense, I can't be the first person to find a simple syntax error? Something must be wrong on my machine (ubuntu). This is the header that's added to my sys includes from make install
right? How would a syntax error appear if the demos/Samples built from the source all work fine. Just thinking out loud I suppose.
I built the lib from source using the suggestions from here.
I also saw this post on wxWidget's forum here. Who didn't seem to have the same syntax issue I'm having. But also, I didn't really understand how to use the suggested macro because the wiki says the macro is actually wrapped by wx__DECLARE_EVT2. If this is the solution perhaps a simple example would be helpful to show me how to use it properly.
I appreciate any input. Thanks!
PS I've tried posting this question on the forum but was unable to register (tried with multiple browsers) so here I am.
There are many issues here. First, the compilation error you mention is due to a missing semicolon, the line:
wxDEFINE_EVENT(EVT_COMMS_UPDATE, wxThreadEvent)
should be
wxDEFINE_EVENT(EVT_COMMS_UPDATE, wxThreadEvent);
Second, the onCommsUpdate_t
method needs to take a wxThreadEvent& parameter
void onCommsUpdate_t(wxThreadEvent&);
Obviously, the definition for the method needs to be updated too.
Third, I honestly don't how the EVT_THREAD
macro is supposed to work, but I know the line
EVT_THREAD(EVT_COMMS_UPDATE, wxMainFrame::onCommsUpdate_t)
doesn't work. The easiest way to handle a thread event is to use the Bind
method. Add a line like this to the frame constructor to handle the event
Bind(EVT_COMMS_UPDATE,&wxMainFrame::onCommsUpdate_t,this);
Fourth, as written, your wxMainFrame::Entry
method will be constantly spamming the event queue freezing up the application. In a real application, presumably the thread will do some work before queuing each event. Since in this example, no work is being done, you should add a line like
wxMilliSleep(500);
to the end of the while loop to make the thread sleep for a little bit before sending each event.
Fifth, you never actually signal your thread to shutdown and it never shuts down on its own. Consequently the line GetThread()->Wait();
will be an infinite loop. There are many ways to signal the thread, but the easiest might be to add a bool member named something like m_shutdown
to the frame class. Then
a. initialize m_shutdown
to false in the frame constructor.
b. In the wxMainFrame::OnClose
, use the critical section to set it to true before calling Wait:
if (GetThread() && // DoStartALongTask() may have not been called
GetThread()->IsRunning())
{
{
wxCriticalSectionLocker lock(m_dataCS);
m_shutdown = true;
}
GetThread()->Wait();
}
c. In the wxMainFrame::Entry
method, add a check for the value of m_shutdown
in the while loop (guarded by the critical section).
while (!GetThread()->TestDestroy()) {
// since this Entry() is implemented in MyFrame context we don't
// need any pointer to access the m_data, m_processedData, m_dataCS
// variables... very nice!
{
wxCriticalSectionLocker lock(m_dataCS);
this->data_t++;
if ( m_shutdown )
break;
}
...
Sixth, I don't know what the line
wxLog::SetActiveTarget(this);
is supposed to do, but it causes a crash at program exit. If you need this, you'll need to store the initial log target and restore it either in the frame destructor or in the close event handler.