Awesome
Thinking Wt 1: general
This article shows one way to think about program architecture when using the Wt library.
Overview
In the first two paragraph I will explain the rationale behind the architecture proposed. The following paragraphs describes the steps in implementing this architecture in a top-down way, followed by a conclusion.
Why these guidelines?
Wt is 'a widget-centric API' [1] for, primarily, dynamic web applications. I see many resemblances in its architecture with the Qt library, suitable for, among others, desktop applications. Due to their different running environments, Wt (web) and Qt (desktop) are mutually exclusive. Because I enjoy porting applications to as much platforms as possible, I have developed some applications that use both (but not simultaneously) a Wt and a Qt front-end. To be able to do this easily, I follow the guidelines presented in this article. Additionally, I like to reuse my own widgets and dialogs in multiple applications, which is only possible with a proper architecture.
Note that I follow the same guidelines for Qt front-ends.
Architecture
The architecture, from biggest to smallest, is: main, Wt::WApplication, dialog, widget:
- Every C++ program has a single main function. The purpose of this main function is to create a Wt::WApplication. Optionally, main can parse the command-line arguments
- The purpose of the Wt::WApplication is to manage dialogs. Optionally, Wt::WApplication can respond to the Wt::WEnvironment parameters given at construction
- A dialog is a collection of at least one widget. The purpose of a dialog is to respond to its widgets or creating other dialogs. A menu is an example of a dialog that creates other dialogs. A TicTacToe dialog would display a TicTacToe widget, but additionally shows the score, responding to a player winning and allowing the user to close it
- A widget is a single visual element. The purpose of a widget is to repond to interaction and emitting signals when needed. For example, a TicTacToe widget responds to the clicking of the user and emits a signal when the game is finished. The signal might (or might not) be used by the dialog it is in
Implementing main
In this example, main creates a single Wt::WApplication, and does not respond to command-line arguments.
The most basic main function would only call WRun with a createApplication function that only returns a newly created Wt::WApplication:
#include <Wt/WApplication>
#include <Wt/WEnvironment>
Wt::WApplication *createApplication(
const Wt::WEnvironment& env)
{
return new WtApplication(env);
}
int main(int argc, char **argv)
{
return WRun(argc, argv, &createApplication);
}
This way of creating a Wt::WApplication is identical to [2][3].
Implementing the Wt::WApplication
The purpose of the Wt::WApplication is to create a dialog. In this example, WtApplication manages a single to pointer to a single dialog, called WtDialog. WtApplication does not respond to the Wt::WEnvironment parameters given at construction, but sets the WtDialog as its widget. Additionaly, it sets the browser title to 'My title'.
#include <Wt/WApplication>
#include <Wt/WEnvironment>
struct WtApplication : public Wt::WApplication
{
WtApplication(const Wt::WEnvironment& env)
: Wt::WApplication(env),
m_dialog(new WtDialog)
{
this->setTitle("My title");
root()->addWidget(m_dialog);
}
private:
WtDialog * const m_dialog;
};
Because the pointer m_dialog is set to be managed by Wt in the 'addWidget' method, it should not be deleted (doing so results in a double deletion warning.
Implementing the dialog
Because a dialog is a collection of at least one widget, WtDialog is a derived class from Wt::WContainerWidget. WtDialog manages two widgets, but does not respond to their signals.
#include <Wt/WContainerWidget>
struct WtDialog : public Wt::WContainerWidget
{
WtDialog()
: m_widget1(new WtWidget),
m_widget2(new WtWidget)
{
this->addWidget(m_widget1);
this->addWidget(m_widget2);
}
private:
WtWidget * const m_widget1;
WtWidget * const m_widget2;
};
Because the pointers m_widget1 and m_wiget2 are set to be managed by Wt in the 'addWidget' method, these should not be deleted (doing so results in a double deletion warning.
Implementing the widget
A widget is a single visual element. In this example, WtWidget is a button (and thus a derived class of Wt::WPushButton), that displays how often it is clicked.
#include <cstdlib>
#include <sstream>
#include <stdexcept>
#include <string>
#include <Wt/WString>
#include <Wt/WPushButton>
Wt::WString IntToWString(const int i)
{
std::ostringstream s;
if (!(s << i)) throw std::logic_error("IntToWString failed");
return Wt::WString(s.str());
}
struct WtWidget : public Wt::WPushButton
{
WtWidget()
: m_clicks(0)
{
setText(IntToWString(m_clicks));
this->clicked().connect(this,&WtWidget::OnClick);
}
private:
void OnClick()
{
++m_clicks;
setText(IntToWString(m_clicks));
}
int m_clicks;
};
WtWidget responds to its own 'clicked'-signal only.
Running the Wt application
Add the following line to your Qt project file (to prevent link errors like undefined reference to 'Wt::WRun(int, char**, Wt::WApplication* (*)(Wt::WEnvironment const&))'):
LIBS += -lwt -lwthttp
Additionally, add the following line to your Qt project file, as Wt uses the Boost.Signals library, that needs to be linked to as well:
LIBS += -lboost_signals
Add the following arguments to the Run Settings (to prevent the misc error stat: No such file or directory. Document root ("") not valid.
--docroot . --http-address 0.0.0.0 --http-port 8080
Start the program and your favorite webbrowser. Take the webbrowser to the following address:
http://127.0.0.1:8080/
Conclusion
In this article I have shown one of many Wt program architectures you can use, for a very basic application. In my humble opinion, this architecture makes sense, but I am open to discussion on this subject.
My next article, Thinking Wt 2: TicTacToe widget describes how I implement the Wt widget of a Tic-tac-toe game.
External links
References
Acknowledgements
Thanks Tor Arne Fallingen for notifying me that I omitted linking to Boost.Signals.