Can multiple WT application run on the same webpage? - c++

so recently I asked a question to see if Can multiple WT applications run on same port? and the answer was a yes, (+1 to Jorge Núñez for that awesome answer). However, now I am trying to take his solution a step further to see if multiple WT applications can be run on the same page, by embedding them in a kind of host WT application. What I have done is created a host WT application that has an accessor for its root() and has a WTabWidget. Then in the CreateHostApplication function I created tabs for the 2 test applications, which also have accessors for their root(), and added their root() to the tab that belongs to my host application, then after all the apps were added to their individual tabs, I returned the host.
The cool part is that the widgets from the test applications showed up in their tabs as I expected, what I didn't expect is that the connect() calls to connect buttons to functions failed. So the widgets are functional as far as clicking them and editing text in boxes goes, but they do nothing else as they are not connected to any custom functions.
After debugging this for a bit, am pretty sure the connect calls failed on the test apps because they are not actually hosted by the browser, which brings me here. I have not been able to find a solution to this problem, is there a way to get the connect calls functional with this setup?
The code below is the solution by Jorge Núñez, with the above mentioned modifications. I am using visual studio2010 for development. Thank you in advance for any help!
#include <Wt/WApplication>
#include <Wt/WBreak>
#include <Wt/WContainerWidget>
#include <Wt/WLineEdit>
#include <Wt/WPushButton>
#include <Wt/WText>
#include <Wt/WException>
#include <Wt/WLogger>
#include <Wt/WServer>
#include <Wt/WTabWidget>
using namespace Wt;
class Host : public Wt::WApplication
{
public:
Host(const Wt::WEnvironment& env);
WContainerWidget* GetRoot()
{
return root();
}
};
Host::Host(const Wt::WEnvironment& env) : Wt::WApplication(env)
{
}
class TestApp1 : public Wt::WApplication
{
public:
TestApp1(const Wt::WEnvironment& env, const std::string& title);
WContainerWidget* GetRoot()
{
return root();
}
private:
Wt::WLineEdit* _name_edit;
Wt::WText* _greeting;
void Greet();
};
TestApp1::TestApp1(const Wt::WEnvironment& env, const std::string& title) : Wt::WApplication(env)
{
setTitle(title);
root()->addWidget(new Wt::WText("Your name, please ? "));
_name_edit = new Wt::WLineEdit(root());
Wt::WPushButton* button = new Wt::WPushButton("Greet me.", root());
root()->addWidget(new Wt::WBreak());
_greeting = new Wt::WText(root());
button->clicked().connect(this, &TestApp1::Greet);
}
void TestApp1::Greet()
{
_greeting->setText("Hello there, " + _name_edit->text());
}
class TestApp2 : public Wt::WApplication
{
public:
TestApp2(const Wt::WEnvironment& env, const std::string& title);
WContainerWidget* GetRoot()
{
return root();
}
private: Wt::WLineEdit *_name_edit;
Wt::WText *_greeting;
void greet();
};
TestApp2::TestApp2(const Wt::WEnvironment& env, const std::string& title) : Wt::WApplication(env)
{
setTitle(title);
root()->addWidget(new Wt::WText("Your name, please ? "));
_name_edit = new Wt::WLineEdit(root());
Wt::WPushButton* button = new Wt::WPushButton("Say goodbye.", root());
root()->addWidget(new Wt::WBreak());
_greeting = new Wt::WText(root());
button->clicked().connect(this, &TestApp2::greet);
}
void TestApp2::greet()
{
_greeting->setText("Goodbye, " + _name_edit->text());
}
Wt::WTabWidget* tab_widget;
Wt::WApplication* CreateHostApplication(const Wt::WEnvironment& env)
{
Host* host = new Host(env);
WContainerWidget* root = host->GetRoot();
tab_widget = new WTabWidget(root);
//Create tab for the app
WContainerWidget* Tab_TestApp1 = new WContainerWidget();
//Get a pointer to the ACE tab
tab_widget->addTab(Tab_TestApp1, "Test Application 1", Wt::WTabWidget::LoadPolicy::PreLoading);
//Create app
TestApp1* test_app_1 = new TestApp1(env, "Test Application 1");
//Add app root to the tab
Tab_TestApp1->addWidget(test_app_1->GetRoot());
//Create tab for the app
WContainerWidget* Tab_TestApp2 = new WContainerWidget();
//Get a pointer to the ACE tab
tab_widget->addTab(Tab_TestApp2, "Test Application 2", Wt::WTabWidget::LoadPolicy::PreLoading);
//Create app
TestApp2* test_app_2 = new TestApp2(env, "Test Application 2");
//Add app root to the tab
Tab_TestApp2->addWidget(test_app_2->GetRoot());
return host;
}
Wt::WApplication* CreateTestApp1(const Wt::WEnvironment& env)
{
return new TestApp1(env, "Test Application 1");
}
Wt::WApplication* CreateTestApp2(const Wt::WEnvironment& env)
{
return new TestApp2(env, "Test Application 2");
}
int TestWRun(int argc, char* argv[],
Wt::ApplicationCreator host_application,
std::vector<Wt::ApplicationCreator> applications)
{
try
{
// use argv[0] as the application name to match a suitable entry
// in the Wt configuration file, and use the default configuration
// file (which defaults to /etc/wt/wt_config.xml unless the environment
// variable WT_CONFIG_XML is set)
Wt::WServer server(argv[0],"");
// WTHTTP_CONFIGURATION is e.g. "/etc/wt/wthttpd"
server.setServerConfiguration(argc, argv, WTHTTP_CONFIGURATION);
// add a single entry point, at the default location (as determined
// by the server configuration's deploy-path)
server.addEntryPoint(Wt::Application, host_application);
unsigned int num_apps = applications.size();
for(unsigned int cur_app = 0; cur_app < num_apps; ++cur_app)
{
server.addEntryPoint(Wt::Application, applications[cur_app], "/" + boost::lexical_cast<std::string>(cur_app));
}
if (server.start())
{
int sig = Wt::WServer::waitForShutdown(argv[0]);
std::cerr << "Shutdown (signal = " << sig << ")" << std::endl;
server.stop();
}
}
catch (Wt::WServer::Exception& e)
{
std::cerr << e.what() << "\n";
return 1;
}
catch (std::exception& e)
{
std::cerr << "exception: " << e.what() << "\n";
return 1;
}
}
int main(int argc, char** argv)
{
std::vector<Wt::ApplicationCreator> applications;
applications.push_back(&CreateTestApp1);
applications.push_back(&CreateTestApp2);
return TestWRun(argc, argv, &CreateHostApplication, applications);
}

While it's possible with Wt, your approach is not 100% correct. AFAIK you have two options:
Use Wt's widgetset mode. See how the chat widget is integrated on Wt's home page: these are two independent Wt applications, that are shown simultaneously on the same page, and that are both active. Wt's widgetset mode is best compared to the Google maps widget: you put a small placeholder (a div + some JS) on a webpage where you want the application to be rendered. See examples/wt-home, examples/feature/widgetset
There is no problem at all in Wt to reuse widgets from one application in another application. Put the part that you want to reuse all together in a WContainerWidget (or a WCompositeWidget), and integrate that in your tabwidgets. Actually, that resembles pretty much to what you're doing now, but instead of taking the root() of TestApp1, organise your code so that you'd only put a single widget in the root of TestApp1, and use the same widget in the tabs of the Host application. In createApplication you must not instantiate more than one WApplication object. Widgetgallery uses this approach to integrate parts of the charts example.

Related

Commit changes to SQLite database, that can be seen between QTabs

I have a simple application, where I can log in / out users. When user logs in, application shows appropriate tab on main window (employee/admin/customer). I have a QMainWindow with QTabWidget on it. In my QMainWindow I create a database (I implemented a special class for this):
class DataBase
{
public:
DataBase();
void initDatabase();
void closeDatabase();
private:
QSqlDatabase db;
};
DataBase::DataBase()
{
}
void DataBase::initDatabase()
{
QString filename = "database.sql";
QFile file(filename);
db = QSqlDatabase::addDatabase("QSQLITE");
db.setHostName("localhost");
db.setDatabaseName(filename);
// create users table
if(this->db.open())
{
QSqlQuery usersTableQuery;
QString usersTableQueryStr = "CREATE TABLE IF NOT EXISTS USERS (ID INTEGER PRIMARY KEY NOT NULL, "
"LOGIN TEXT,"
"PASSWORD TEXT,"
"FIRSTNAME TEXT,"
"LASTNAME TEXT,"
"EMAIL TEXT,"
"ACCOUNT_TYPE INTEGER"
");";
if(usersTableQuery.exec(usersTableQueryStr))
{
qDebug() << "Create USERS table OK";
}
else
{
qDebug() << usersTableQuery.lastError().text();
}
}
else
{
qDebug() << "DB is not opened!\n";
}
// create service table
if(this->db.open())
{
QSqlQuery serviceTableQuery;
QString serviceTableQueryStr = "CREATE TABLE IF NOT EXISTS SERVICE (ID INTEGER PRIMARY KEY NOT NULL, "
"NAME TEXT,"
"PRICE REAL"
");";
if(serviceTableQuery.exec(serviceTableQueryStr))
{
qDebug() << "Create SERVICE table OK";
}
else
{
qDebug() << serviceTableQuery.lastError().text();
}
}
else
{
qDebug() << "DB is not opened!\n";
}
}
void DataBase::closeDatabase()
{
db.close();
}
My tabs for employee, admin, client look like this one:
class AdminTab : public QWidget
{
Q_OBJECT
public:
explicit AdminTab(DataBase *db, QWidget *parent = 0);
//...
Everyone (employee,client,admin) can make changes in database (for instance, admin can insert services, users can check available services, etc). However, when admin adds a service (I make an insert operation on an open database), and logs out, when the client logs in, it can't see the changes made by the admin. When I start application again, and client logs in, it can see new added service.
Adding service looks like this:
bool DataBase::insertService(QString name, double price)
{
if(!db.isOpen())
{
qDebug() << query.lastError();
return false;
}
else
{
QSqlQuery query;
query.prepare("INSERT INTO SERVICE (NAME, PRICE) "
"VALUES (:NAME, :PRICE)");
query.bindValue(":NAME", name);
query.bindValue(":PRICE", price);
if(query.exec())
{
return true;
}
else
{
qDebug() << query.lastError();
}
}
return false;
}
I guess it's the problem that the database is all the time opened, but how can I make the changes to be available just after I insert/remove something in database? I open the database when I create QMainWindow and close it in its destructor.
I thought about opening/closing the database every time I need to use it, but I can't say if it's a good solution.
Even adding :
if(query.exec())
{
query.clear();
db.commit();
return true;
}
Does not help.
Client has: QVector<Service*> availableServices; and QComboBox *servicesComboBox;, checking for all the available services, when client logs in :
void ClientTab::updateAllServices()
{
availableServices.clear();
availableServices = db->selectAllServices();
servicesComboBox->clear();
for(int i=0; i<availableServices.size(); i++)
servicesComboBox->addItem(availableServices[i]->getServiceName(), QVariant::fromValue(availableServices[i]));
servicesComboBox->setCurrentIndex(-1);
}
Service class:
#ifndef SERVICE_H
#define SERVICE_H
#include <QString>
#include <QMetaType>
#include <QVariant>
class Service : public QObject
{
Q_OBJECT
public:
Service(int id, QString name, double price);
Service(){ id = -1; name = ""; price = 0;}
QString getServiceName() const;
void setServiceName(const QString &value);
double getServicePrice() const;
void setServicePrice(double value);
int getId() const;
void setId(int value);
private:
QString name;
double price;
int id;
};
Q_DECLARE_METATYPE(Service*)
#endif // SERVICE_H
And finally, selecting all services from the database (I use this method to populate combobox on ClientTab):
QVector<Service*> DataBase::selectAllServices()
{
QVector<Service*> services;
if(!db.isOpen())
{
return services;
}
else
{
QSqlQuery query;
if(query.exec("SELECT * FROM SERVICE;"))
{
while( query.next() )
{
int id = query.value(0).toInt();
QString name = query.value(1).toString();
double price = query.value(2).toDouble();
Service *s = new Service(id, name, price);
services.push_back(s);
}
}
else
{
qDebug() << "DataBase::selectAllServices " << query.lastError();
}
}
return services;
}
Could you doublecheck that
void ClientTab::updateAllServices()
is called every time as the client logs in (not only at application start)?
SQLite database has autocommit on by default, therefore you don't need to commit anything or use any transaction for this simple thing.
Can you see the new service added in sqlite command line with a select * from service? If so, then adding a service works well, and you need to check when do you call updateAllServices().
If I understand your problem correctly, it's about how to synchronize multiple views of a database. This is indeed a difficult task.
If only one of the views is visible at any given time (like it seems in your case to be with the different tabs), just reload the data from the database and repopulate the tabs. How would you do this? Add a signal contentChanged() to your main window and let all views reload the data when they see it. (Or clear their data and reload when the user switches to the specific tab.)
In case this is too slow or you can see the same content in multiple views at the same time: You should use the models provided by Qt, e.g. QListView + QAbstractListModel instead of QListWidget. If you use those, synchronization is for free, but you should not access your SQL database directly anymore and only change it through the model.

QWebEngineView Open In External Browser

I'm in the process of moving my code from QtWebKit to QtWebEngine. In general, the transition has been fairly smooth, however, I'm stuck on one particular issue. I use a QWebEngineView to display a Google Maps page. Some of the markers placed have have infowindows that pop up "Click Here for More Information" which opens the link in an external browser.
Using QtWebKit, this was fairly easy through the setLinkDelegation policy. However, it seems a little more complex here. I've tried to follow the example but somehow I need to redefine QWebEnginePage within QWebEngineView. Below is what I've come up with so far. Any idea how I can actually connect this all up?
Thanks
#ifndef MYQWEBENGINEVIEW_H
#define MYQWEBENGINEVIEW_H
#include <QWebEngineView>
#include <QDesktopServices>
class MyQWebEnginePage : public QWebEnginePage
{
Q_OBJECT
public:
MyQWebEnginePage(QObject* parent = 0) : QWebEnginePage(parent){}
bool acceptNavigationRequest(const QUrl & url, QWebEnginePage::NavigationType type, bool isMainFrame)
{
qDebug() << "acceptNavigationRequest("<<url << "," << type << "," << isMainFrame<<")";
if (type == QWebEnginePage::NavigationTypeLinkClicked)
{
QDesktopServices::openUrl(url);
return false;
}
return true;
}
};
class MyQWebEngineView : public QWebEngineView
{
Q_OBJECT
public:
MyQWebEngineView(QWidget* parent = 0);
MyQWebEnginePage* page() const;
};
#endif // MYQWEBENGINEVIEW_H
You don't need the second part. Try this:
QWebEngineView *view = new QWebEngineView();
MyQWebEnginePage *page = new MyQWebEnginePage();
view->setPage(page);

wxWidgets Accessing TopFrame of Another Process

I have an application which has the parent frame as the MainFrame and two child frames, ChildA and ChildB. This should be a document oriented GUI such that when user clicks a document, it should be able to open another instance of FrameA.
When I click the executable of GUI MainFrame, ChildA and ChildB loads normally. However when the executable is running and when I click a document I realized that Windows is opening it as another process such that MainFrame, ChildA and ChildB loads again which I dont want cause I only want to open another instance of ChildA to show the contents of the document.
My code is as follows:
bool MainFrameApp::OnInit()
{
m_AnotherInstanceRunning=false;
m_checker = new wxSingleInstanceChecker;
if(m_checker->IsAnotherRunning()) m_AnotherInstanceRunning=true;
//Some other code
if(!m_AnotherInstanceRunning)
{
frame= new MainFrame(0L);
std::cout<<"Frame:"<<frame; //Prints the address
auto m_ChildA=new SomeChildFrame(frame);
frame->Show();
m_FrameA->Show(true);
wxTheApp->SetTopWindow(frame);
}
if(m_AnotherInstanceRunning)
{
frame=dynamic_cast<MainFrame*>(wxTheApp->GetTopWindow());
std::cout<<"Frame:"<<frame; //prints 0
frame->OpenDocument(frame,strDocDirectory,strDocName); //Does not work
return false;
}
My question is, when I click on a document how can I open that document within an already running MainFrame.
EDIT 1:
This is how I achieved it after countless of hours...
Below is the code for MainFrame.h
const int SERVER_ID=wxNewId();
const int SERVERSOCKET_ID=wxNewId();
const int CLIENTSOCKET_ID=wxNewId();
class MainFrame;
class MainFrameApp : public wxApp
{
bool m_AnotherInstanceRunning;
wxSocketServer* m_server;
wxString m_DocDirectoryPath;
wxString m_DocFullName; //including extension
protected:
virtual void OnServerEvent(wxSocketEvent& event);
virtual void OnServerSocketEvent(wxSocketEvent& event);
virtual void OnClientSocketEvent(wxSocketEvent& event);
public:
MainFrame* m_MainFrame;
wxSingleInstanceChecker* m_checker;
};
static const wxCmdLineEntryDesc cmdLineDesc[] =
{
{wxCMD_LINE_PARAM, NULL, NULL, "", wxCMD_LINE_VAL_STRING, wxCMD_LINE_PARAM_MULTIPLE},
{wxCMD_LINE_NONE},
};
Below is the code for MainFrame.cpp
bool ScienceSuitApp::OnInit()
{
m_AnotherInstanceRunning=false;
m_checker = new wxSingleInstanceChecker;
if(m_checker->IsAnotherRunning()) m_AnotherInstanceRunning=true;
wxFileName exePath(wxStandardPaths::Get().GetExecutablePath());
glbStrExeDir=exePath.GetPath()+wxFileName::GetPathSeparator();
wxString strDocFullPath; //This is directory+name
if(argc>1)
{
wxCmdLineParser parser(cmdLineDesc, argc, argv);
parser.Parse();
strDocFullPath=argv[1];
wxFileName docPath(strDocFullPath);
m_DocDirectoryPath=docPath.GetPath()+wxFileName::GetPathSeparator();
m_DocFullName=docPath.GetFullName(); //we needed extension of document as well
}
if(!m_AnotherInstanceRunning)
{
//Some other code
//Here Process #1
m_MainFrame= new MainFrame(0L);
m_MainFrame->Show();
wxIPV4address addr;
addr.Service(3000);
m_server = new wxSocketServer(addr,wxSOCKET_WAITALL);
if (!m_server->Ok())
{
if (m_server->LastError() == wxSOCKET_INVPORT) wxMessageBox(wxT("Port in use!"));
m_server->Destroy();
}
m_server->SetEventHandler(*this, SERVER_ID);
m_server->SetNotify(wxSOCKET_CONNECTION_FLAG);
m_server->Notify(true);
Connect(SERVER_ID,wxID_ANY, wxEVT_SOCKET,wxSocketEventHandler(MainFrameApp::OnServerEvent));
Connect(SERVERSOCKET_ID,wxID_ANY,wxEVT_SOCKET,wxSocketEventHandler(MainFrameApp::OnServerSocketEvent));
}
if(m_AnotherInstanceRunning)
{
//Here is for Process #2
Connect(CLIENTSOCKET_ID,wxID_ANY,wxEVT_SOCKET,wxSocketEventHandler(MainFrameApp::OnClientSocketEvent));
wxIPV4address addr;
addr.Hostname(wxT("localhost"));
addr.Service(3000);
wxSocketClient* Socket = new wxSocketClient();
Socket->SetEventHandler(*this, CLIENTSOCKET_ID);
Socket->SetNotify(wxSOCKET_CONNECTION_FLAG | wxSOCKET_INPUT_FLAG | wxSOCKET_LOST_FLAG);
Socket->Notify(true);
if(!Socket->Connect(addr, true)) wxMessageBox("Failed to connect to server");
return true;
}
return true;
}
void MainFrameApp::OnServerEvent(wxSocketEvent& event) //Process #1
{
wxSocketBase* sock = m_server->Accept(false);
sock->SetEventHandler(*this, SERVERSOCKET_ID);
sock->SetNotify(wxSOCKET_INPUT_FLAG | wxSOCKET_LOST_FLAG);
sock->Notify(true);
}
void MainFrameApp::OnServerSocketEvent(wxSocketEvent& event) //Process #1
{
wxSocketBase *sock = event.GetSocket();
wxCharBuffer buf(256);
switch(event.GetSocketEvent())
{
case wxSOCKET_CONNECTION:
{
break;
}
case wxSOCKET_INPUT:
{
sock->Read(buf.data(), 255);
wxString pathstr=wxString::FromUTF8(buf);
sock->Destroy();
wxFileName docPath(pathstr);
wxString DocDirectoryPath=docPath.GetPath()+wxFileName::GetPathSeparator();
wxString DocFullName=docPath.GetFullName(); //we needed extension of document as well
m_MainFrame->OpenDocument(m_MainFrame,DocDirectoryPath,DocFullName);
break;
}
case wxSOCKET_LOST:
{
sock->Destroy();
break;
}
}
}
void MainFrameApp::OnClientSocketEvent(wxSocketEvent& event) //Process #2
{
wxString str=m_DocDirectoryPath+m_DocFullName;
wxSocketBase* sock = event.GetSocket();
switch(event.GetSocketEvent())
{
case wxSOCKET_CONNECTION:
{
sock->Write(str.mb_str(),str.length());
}
}
//Exit the client process (Process #2) staying in the background, otherwise and no messages are sent
//Also we dont want to spawn background processes
exit(0);
}
If you want to prevent more than one instance of your application running at the same time, you can use wxSingleInstanceChecker to check if another one is running and use the IPC classes to send the name of the document to open to the other instance if it is.

Windows Form Show vs ShowDialog

I have a small application that I am trying to create for windows. I am running into an issue with mixing a background thread designed to process some data. This background engine someone needs to both update the application gui (A windows Form) and get information from it.
Here is the basic application main.
int main() {
Engine engine;
Gui g;
engine.run(); // creates a new thread for engine logic
g.ShowDialog();
bool running = false;
while(1)
{
// Update gui with information from the engine
g.update(engine.GetState());
// transition to running
if(g.isRunning() && !running)
{
engine.play();
running = true;
}
// transition to stopped
else if(!g.isRunning() && running)
{
engine.stop();
running = false;
}
}
}
My main problem comes from the fact that Gui class is managed. See Class declaration below.
public ref class Gui : public System::Windows::Forms::Form
I am not really able to mix these two things, at first I had wanted to just throw the Engine into the Gui but that doesn't work since it is unmanaged.
You will note that the problem here is calling ShowDialog() as this makes the dialog modal and no code afterwards is executed. However, if I use Show() ... the Gui simply doesn't update or process any inputs.
SOLUTION:
I created a background worker in the Gui class so the engine is contained within the Gui but is ran on another thread.
void InitializeBackgoundWorker()
{
this->backgroundWorker1 = gcnew System::ComponentModel::BackgroundWorker;
backgroundWorker1->DoWork += gcnew DoWorkEventHandler( this, &Gui::backgroundWorker1_DoWork );
backgroundWorker1->RunWorkerAsync( );
}
delegate void UpdateCallback(int hp, int maxhp);
void UpdateGui(int hp, int maxhp)
{
this->playerHealthBar->Value = ((float)(hp)/(float)(maxhp) * 100.0f);
};
void backgroundWorker1_DoWork( Object^ sender, DoWorkEventArgs^ e )
{
aBotEngine engine;
while(true)
{
engine.runnable(NULL);
array<Object^>^args = gcnew array<Object^>(2);
args[0] = engine.getPlayerHp();
args[1] = engine.getPlayerMaxHp();
this->playerHealthBar->Invoke(gcnew UpdateCallback(this, &Gui::UpdateGui), args);
}
};
As far as I can tell this is the proper way to have background thread that updates your windows form. I'm sure its not the only way.
void InitializeBackgoundWorker()
{
this->backgroundWorker1 = gcnew System::ComponentModel::BackgroundWorker;
backgroundWorker1->DoWork += gcnew DoWorkEventHandler( this, &Gui::backgroundWorker1_DoWork );
backgroundWorker1->RunWorkerAsync( );
}
delegate void UpdateCallback(int hp, int maxhp);
void UpdateGui(int hp, int maxhp)
{
this->playerHealthBar->Value = ((float)(hp)/(float)(maxhp) * 100.0f);
};
void backgroundWorker1_DoWork( Object^ sender, DoWorkEventArgs^ e )
{
aBotEngine engine;
while(true)
{
engine.runnable(NULL);
array<Object^>^args = gcnew array<Object^>(2);
args[0] = engine.getPlayerHp();
args[1] = engine.getPlayerMaxHp();
this->playerHealthBar->Invoke(gcnew UpdateCallback(this, &Gui::UpdateGui), args);
}
};

Multithreading in wxWidgets GUI apps?

I'm having problem (basically, I'm confused.) while trying to create a worker thread for my wxWidgets GUI application that WILL MODIFY one of the GUI property itself. (In this case, wxTextCtrl::AppendText).
So far, I have 2 source files and 2 header files for the wx program itself (excluding my own libs, MySQL lib, etc), say MainDlg.cpp which contains a derived class of wxFrame called 'MainDlg' and MainForm.cpp which contains a derived class of wxApp called 'MainForm'.
MainForm.cpp
#include "MainHeader.h" // contains multiple header files
IMPLEMENT_APP(MainForm)
bool MainForm::OnInit()
{
MainDlg *Server = new MainDlg(wxT("App Server 1.0"), wxDEFAULT_FRAME_STYLE - wxRESIZE_BORDER - wxMAXIMIZE_BOX);
Editor->Show();
return true;
}
MainDlg.cpp:
#include "MainHeader.h"
BEGIN_EVENT_TABLE(MainDlg, wxFrame)
EVT_BUTTON(6, MainDlg::StartServer)
EVT_BUTTON(7, MainDlg::StopServer)
END_EVENT_TABLE()
CNETServerConnection *cnServCon;
std::string ServerIP, DBHost, DBUser, DBName, DBPass;
int UserCapacity, DBPort, ServerPort;
MYSQL *sqlhnd;
MainDlg::MainDlg(const wxString &title, long style) : wxFrame(NULL, wxID_ANY, title, wxDefaultPosition, wxSize(301, 230), style)
{
cnServCon = new CNETServerConnection(100);
this->InitializeComponent();
}
void MainDlg::InitializeComponent()
{
this->SetTitle(wxT("App Server 1.0"));
this->SetSize(396, 260);
this->SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_3DFACE));
this->Centre();
statBox = new wxTextCtrl(this, 4, wxT("Welcome to AppServer 1.0\n\n"), wxPoint(10, 10), wxSize(371, 141), wxTE_MULTILINE | wxTE_READONLY);
//.................................
}
void MainDlg::StartServer(wxCommandEvent &event)
{
this->startBtn->Enable(false);
this->AppendStatus(wxT("\nLoading server configuration... "));
//.................................
this->AppendStatus(wxT("OK\n\nServer ready!\n\n"));
// When the server is ready, I need to run a thread
// that will update the statBox (through AppendStatus func or wxTextCtrl::AppendText directly)
// regularly without interrupting the GUI itself.
// Because the thread will contain a while-loop
// to make the program keep receiving message from clients.
this->startBtn->Hide();
this->stopBtn->Show();
this->cmdBtn->Enable();
this->cmdBox->Enable();
}
void MainDlg::StopServer(wxCommandEvent &event)
{
//...................................
}
void MainDlg::AppendStatus(const wxString &message)
{
statBox->AppendText(message);
}
// Well, here is the function I'd like to run in a new thread
void MainDlg::ListenForMessages()
{
int MsgSender = 0;
while(1)
{
if(!cnServCon->GetNewMessage())
continue;
if(MsgSender = cnServCon->GetJoiningUser())
this->AppendStatus(wxT("Someone connected to the server."));
}
}
I also found an usage example of wxThread from Simple example of threading in C++:
class MessageThread : public wxThread
{
private:
MessageThread(const MessageThread &copy);
public:
MessageThread() : wxThread(wxTHREAD_JOINABLE)
{
}
void *Entry(void)
{
// My works goes here
return;
}
};
wxThread *CreateThread()
{
wxThread *_hThread = new MessageThread();
_hThread->Create();
_hThread->Run();
return _hThread;
}
But I don't know how to associate it with my program and make it able to modify my GUI property (statBox).
Any kind of help would be appreciated! :)
Thanks.
The easiest is to create an event type id, and use a wxCommandEvent using the type id, set its string member ("evt.SetText"), and send the event to one of your windows (using AddPendingEvent). In the handler of that event, you can then call AppendText on your control using the text you sent (evt.GetText), because you are in the GUI thread by then.
// in header
DECLARE_EVENT_TYPE(wxEVT_MY_EVENT, -1)
// in cpp file
DEFINE_EVENT_TYPE(wxEVT_MY_EVENT)
// the event macro used in the event table. id is the window id you set when creating
// the `wxCommandEvent`. Either use -1 or the id of some control, for example.
EVT_COMMAND(window-id, event-id, handler-function)
Here is an overview how it works: Custom Events.