Writing a dialog based wxWidgets application |
Sometimes even a minimal application is... too much!
Suppose you need a really simple application. No menu, no toolbar, no status bar. Just few buttons, a pair of text boxes and that's it. Too simple to be useful? Well, it could be a currency converter or a tiny desktop clock...
This kind of applications have to be designed to take as little desktop space as possible. And since they are so simple they don't even need a menu, a toolbar or a status bar. For example, in the case of a desktop clock it does not even need a single button (suppose the correct time is taken directly from the operating system so no Set button is necessary.) You run it and it shows itself on the desktop. You can drag it around and leave where you like most. Then, when tired of having it there, you just press the system close button (in Windows 95 is the small 'x' in the upper right corner) and it's gone.
In this article we will see how to write such a dialog based application. At the end you will find the links to download two sample dialog based applications.
A dialog based application simply is a wxWidgets application that has a dialog box as its main (and usually only) window. Thus, like in any other wxWidgets based application, the first thing to do is to derive a class from wxApp and then implement its OnInit method. If it were a standard application (i.e. with a frame as its main window), you would then derive a class from wxFrame, initialize it, and then set it as the main application window with a (non mandatory) call to SetTopWindow. If this were the case, the code would look like this (from the minimal sample in the standard wxWidgets distribution):
// Define a new frame type: this is going to be our main frame
class MyFrame : public wxFrame
{
...
private:
...
// any class wishing to process wxWidgets events must use this macro
DECLARE_EVENT_TABLE()
};
...
bool MyApp::OnInit()
{
// Create the main application window
MyFrame *frame = new MyFrame("Minimal wxWidgets App",
wxPoint(50, 50), wxSize(450, 340));
// Show it and tell the application that it's our main window
frame->Show(TRUE);
SetTopWindow(frame);
// success: wxApp::OnRun() will be called which will enter the main message
// loop and the application will run. If we returned FALSE here, the
// application would exit immediately.
return TRUE;
}
But in a dialog based application the main window is a dialog box and thus it's derived from wxDialog rather than from wxFrame. Here is a similar code excerpt but for a dialog based application (code is taken from the dlgapp sample accompanying this article):
// Define a new dialog type: this is going to be our main window
class MyDialog : public wxDialog
{
...
private:
...
// any class wishing to process wxWidgets events must use this macro
DECLARE_EVENT_TABLE()
};
...
// `Main program' equivalent: the program execution "starts" here
bool MyApp::OnInit()
{
// Create the main application window (a dialog in this case)
MyDialog *dialog = new MyDialog("Dialog based App",
wxPoint(50, 50), wxSize(200, 200));
// Show it and tell the application that it's our main window
dialog->Show(TRUE);
SetTopWindow(dialog);
// success: wxApp::OnRun() will be called which will enter the main message
// loop and the application will run. If we returned FALSE here, the
// application would exit immediately.
return TRUE;
}
As you can see the two pieces of codes are very similar except for the class the main window is derived from (marked in red.)
In both cases the main window class is the one that handles the events and thus has to contain the DECLARE_EVENT_TABLE() instruction in its declaration. The events are then managed in the usual way, associating each one with the corresponding handling method. The particularity is that in a dialog based application you will deal mostly with command handling events, that is with events generated by the controls that populate the dialog.
Here is an example of event handling table for a dialog based application (from dlgapp sample):
BEGIN_EVENT_TABLE(MyDialog, wxDialog)
EVT_BUTTON(Dlgapp_CelToFar, MyDialog::OnCelToFar)
EVT_BUTTON(Dlgapp_FarToCel, MyDialog::OnFarToCel)
EVT_BUTTON(Dlgapp_About, MyDialog::OnAbout)
EVT_BUTTON(Dlgapp_Close, MyDialog::OnQuit)
// We have to implement this to force closing
// the dialog when the 'x' system button is pressed
EVT_CLOSE( MyDialog::OnQuit)
END_EVENT_TABLE()
As you can see all the managed events are related to buttons that populate the dialog, but there could also be, for example, events responding to text change in a text box etc. There is also an EVT_CLOSE event entry that normally wouldn't be present because the wxWidgets framework handles the closing event by itself. More about this particularity will come later.
By the way, up to now our main dialog window doesn't contain any control at all. There are two ways of populating the dialog with controls. The simpler (but boring) one is creating and positioning the various controls inside the dialog constructor. The second way is loading the entire dialog from an external resource (wxResource or native Windows resource.) The second approach requires a little attention because of the way the resources are implemented in wxWidgets to make them portable.
The next coding example shows how to do populate the dialog from inside its constructor. Just remember that it's convenient to store a pointer for each control you're going to access later while you don't need a pointer for the remaining controls:
// dialog constructor
MyDialog::MyDialog(const wxString& title, const wxPoint& pos, const wxSize& size)
: wxDialog((wxDialog *)NULL, -1, title, pos, size)
{
// create and position controls in the dialog
// create static box to enclose text and conversion buttons
wxStaticBox *statBox = new wxStaticBox(this, Dlgapp_StaticBox,
wxString("Convert"),
wxPoint(5,0), wxSize(130, 100));
// static text celsius
wxStaticText *celText = new wxStaticText(this, Dlgapp_CelText,
wxString("Celsius:"), wxPoint(10,15));
// text control (declared in MyDialog, pointer stored for later access)
celDegree = new wxTextCtrl(this, Dlgapp_CelDegree, wxString(""),
wxPoint(10,30), wxSize(50,20));
// static text fahrenheit
wxStaticText *fahrText = new wxStaticText(this, Dlgapp_FahrText,
wxString("Fahrenheit:"), wxPoint(10,55));
// text control (declared in MyDialog, pointer stored for later access)
fahrDegree = new wxTextCtrl(this, Dlgapp_FahrDegree, wxString(""),
wxPoint(10,70), wxSize(50,20));
// four buttons, we don't need to reference them later
wxButton *btnCelToFahr = new wxButton( this, Dlgapp_CelToFahr,
"C -> F", wxPoint(80,30), wxSize(50,20));
wxButton *btnFahrToCel = new wxButton( this, Dlgapp_FahrToCel,
"F -> C", wxPoint(80,70), wxSize(50,20));
wxButton *btnAbout = new wxButton( this, Dlgapp_About,
"About", wxPoint(10,110), wxSize(50,20));
wxButton *btnClose = new wxButton( this, Dlgapp_Close,
"Close", wxPoint(80,110), wxSize(50,20));
// no default button
}
celDegree and fahrDegree have been declared as members of MyDialog so that we can use them to access the related control inside the event handling code. Otherwise we can assign a name to the controls we need to access later and then retrieve a pointer to them using the wxFindWindowByName function.
The next piece of code shows how to load the dialog from an external WXR file. You need to pay attention to the compiler and the operating system you are working with since they could behave differently than expected. The following code (belonging the the calc sample accompanying this article) has been adapted from the resource sample coming with the standard wxWidgets distribution. You should look at this sample and at the wxWidgets documentation to find out more about handling WXR files and resources.
// `Main program' equivalent: the program execution "starts" here
bool TheApp::OnInit()
{
#if defined(__WXMSW__)
// Load the .wxr 'file' from a .rc resource, under Windows.
dialog1 = wxLoadUserResource("dialog1", "WXRDATA");
// All resources in the file (only one in this case) get parsed
// by this call.
wxResourceParseString(dialog1);
#else
// Simply parse the data pointed to by the variable dialog1.
// If there were several resources, there would be several
// variables, and this would need to be called several times.
wxResourceParseData(dialog1);
#endif
// Create dialog
MainDialog *dialog = new MainDialog();
if (dialog->LoadFromResource(NULL, "dialog1"))
{
dialog->txtDisplay = (wxStaticText *)wxFindWindowByName("lblDisp", dialog);
if (dialog->txtDisplay)
{
dialog->txtDisplay->SetLabel("0.");
}
}
else
{
wxMessageBox("Error loading resources!", "ERROR!",
wxOK | wxICON_EXCLAMATION, NULL);
return FALSE;
}
// Show it and tell the application that it's our main window
dialog->Show(TRUE);
SetTopWindow(dialog);
// success: wxApp::OnRun() will be called which will enter the main message
// loop and the application will run. If we returned FALSE here, the
// application would exit immediately.
return TRUE;
}
In the code above the main dialog is first created using the default constructor MainDialog() (equivalent to wxDialog()) and then populated with LoadFromResource. Once the dialog has been loaded, the controls we are interested in can be addressed using wxFindWindowByName to retrieve a pointer to them. Then the main dialog is shown like any other window using the Show method. By the way, the dialog resources had been created using DialogEd in the standard wxWindows distribution.
Normally you don't have to care about closing the main application window since wxWidgets will deal automatically with it, i.e. ending the application when all of its windows have been closed. But dialog windows behave differently from frame windows. When an application main window is closed, if it's a frame window it's automatically destroyed. The application object, recognizing it doesn't have a window any more shuts-down the application and everything goes fine. But if the main application window is a dialog, closing the window with the 'x' system button of with Close() just causes the dialog to be hidden. Thus the application still has a window (the dialog) even if it's hidden and so doesn't shut-down. It simply sits there with its only window hidden and incapable of getting any user input. To put it simply, it's stuck. It's still running but we cannot interact with it because its only window is hidden.
To prevent this unpleasant situation we have to make sure that, when closed, the main dialog is destroyed rather than hidden. In this way the automatic application shut-down will work correctly. To do so we need to call the Destroy() method rather than Close() when handling the quit or exit command. We also need to handle the EVT_CLOSE event, generated by clicking the small 'x' in the upper right corner, in the same way so to ensure the application is properly terminated if exited in this way (look at the Events handling paragraph.) Here is the code that handles the quit command:
void MyDialog::OnQuit(wxCommandEvent& WXUNUSED(event))
{
// --> Don't use Close with a wxDialog,
// use Destroy instead.
Destroy();
}
Under Windows 95 the dialog based application we have outlined has a little defect. The problem is that it's main window (the dialog) doesn't have an associated icon and thus no icon is displayed in the title bar near to the application name nor it appears in the task bar. We cannot define an icon the same way we do for a wxFrame derived main window because wxDialog doesn't have a SetIcon method. So the only thing we can do is to provide, under Windows 95, a similar method for the our wxDialog derived class.
Fortunately wxWidgets comes complete with source code so it's easy to go into Windows' specific wxFrame code to see how SetIcon is implemented. Here is the wxFrame SetIcon code adapted to our wxDialog derived class:
void MyDialog::SetIcon(const wxIcon& icon)
{
m_icon = icon;
#ifdef __WIN95__
if ( m_icon.Ok() )
SendMessage((HWND) GetHWND(), WM_SETICON,
(WPARAM)TRUE, (LPARAM)(HICON) m_icon.GetHICON());
#endif
}
It works by sending a Windows native WM_SETICON message to the dialog and passing the icon to display. This code is Windows 95 specific since previous versions didn't have such a message. m_icon is defined in MyDialog as wxIcon. The icon can be loaded in MyDialog's constructor using the code:
SetIcon(wxICON(mondrian));
that will actually show the icon under Windows 95 only.
Here is the complete source code of two dialog based applications. The code has been tested under Windows 95 with Visual C++ 5.0 compiler but it's supposed to work with other operating systems/compiler. Any kind of feed-back will be really appreciated.
Download:
Note: instead of the following links, samples should be downloaded together here. The archive contains the samples plus this article.
dlgapp A simple dialog based application. Converts Celsius degree to Fahrenheit and viceversa.
calc A dialog based application that implements a small desktop calculator having just the four basic operations.