If it absolutely has to have a GUI, and it has to run on different platforms, you have to have a look at this freely available framework.
Introduction
A GUI (Graphical User Interface) is perhaps the most important feature in applications that require interaction with end users. Virtually all modern operating systems, including recent versions of Linux, provide users with a graphical environment that allows them to launch applications and manage resources. Users are accustomed to these standardized facilities, and expect no less from applications.
In this article I present an introductory tutorial on wxWindows, a free C++ based toolkit for the development of multi-platform GUI applications. The targeted platforms include Linux, MS Windows, MAC, and Sun. wxWindows also offers a class library for the development of non-GUI applications (console or embedded). In this article, however, I will concentrate on the use of wxWindows to develop GUI applications for Linux. You can find more about other benefits and facilities for other platforms at wxWindows web site (www.wxwindows.org).
The Basics of Using wxWindows
One of the main benefits of wxWindows is its ease of use. It provides an extremely intuitive approach to developing GUI applications, yet you can use it to develop very complex applications, with a full-featured user interface. In this section I provide an overview of how an application is constructed using wxWindows.
Among the most important classes in the wxWindows library are wxApp, wxWindow, and wxFrame. The wxApp class represents the application itself. In general, you derive from this class to create your custom application class. The customization is done by overriding certain methods notably the OnInit method. OnInit is executed when the application starts; you place any required initialization code in this function, including code to instantiate and display the applications main window.
Class wxWindow represents graphical components, such as regular windows, dialog boxes, controls, etc. Class wxFrame is derived from wxWindow, and it is normally used to represent the applications main frame (the main window that contains all the other windows). As with wxApp, programmers dont instantiate class wxFrame directly, but use it as the base class to create a customized frame class.
The following code fragment shows an example of defining an application class and main frame class. In the application class, the overriden OnInit method creates and shows an instance of the main frame:
class SampleApp : public wxApp { private: virtual bool OnInit(); }; class MainFrame : public wxFrame { public: MainFrame (const wxString & title, const wxPoint & position, const wxSize & size) : wxFrame (NULL, -1, title, pos, size) {} }; MainFrame * frmMain; // could also be a private data // member of class SampleApp bool SampleApp::OnInit() { frmMain = new MainFrame ("Sample Application", wxPoint(20,20), wxSize(200,150)); frmMain->Show (true); SetTopWindow (frmMain); return true; }In this example, the MainFrame class created is not particularly useful, since it didnt require or receive any customization. I chose to do it this way to illustrate the typical approach used with wxWindows. In practice, the main frame class always requires some customization.
The parameters to wxFrames constructor are described as follows. The first parameter is a pointer to the parent, or the container window. Given that this is a top-level window, its parent is set to NULL. The second parameter is the ID, which is not required in this case. A value of -1 indicates a default, or not used setting for this ID.
In the case of wxFrame, the parameter title corresponds to the frames titlebar text. Notice the use of class wxString. This class is relatively similar to the Standard C++ librarys string class, at least when used in simple situations. You can find out more about class wxString in the wxWindows documentation.
The other two parameters are position and size. These are conveniently specified using classes wxPoint and wxSize; wxPoints constructor takes parameters x and y (in screen coordinates), and wxSizes constructor takes parameters width and height, in pixels.
Notice that the frame object is dynamically allocated. wxWindows requires you to use dynamic memory allocation for all of the windows (and as you will see, for most GUI objects of the wxWindows library).
By adding the appropriate include files and an entry point, I could turn this fragment into a minimally working application. The include directives should be as shown below:
#include <wx/wxprec.h> #ifdef __BORLANDC__ #pragma hdrstop #endif #ifndef WX_PRECOMP #include <wx/wx.h> #endifIncluding only <wx/wx.h> would suffice under Linux, but it is strongly advised that you always use the above, since this provides increased portability, and takes advantage of specific features of compilers on other platforms.
The applications entry point is provided by the IMPLEMENT_APP macro. This macro requires only the application classs name, and it hides the definition of main, the instantiation of the application object, and all possible initializations required. In this example you would place, at file scope, the following line:
IMPLEMENT_APP (SampleApp)This sample code shows an absolutely minimal application, in that the application does nothing useful it merely displays a window with Sample Application on its title bar. To make something more useful, you need to first understand the notion of events, which enable the application to respond to actions from the user.
Event-driven Programming
GUI applications have a funny way of working: they are usually doing nothing. (They call it idle to make us believe that they are actually working.) The functionality of GUI applications is based on the notion of events. Events are conditions usually external to the application (often associated with actions from the user, such as mouse clicks, keystrokes, etc.) that are reported to the application so that it does something in response.
Thus, instead of imposing a predefined sequence of actions on the user, GUI applications typically present a set of graphical input and output devices, and let the user choose the sequence of data entry and commands; the application simply waits for the appropriate commands, and responds to them.
As an example, try to picture how a login authorization process would work. The event-driven, GUI approach would be as follows: display a dialog box containing two text boxes with labels next to them, an OK button, and a Cancel button. The user can type the name first, or the password first, and they can come back one or several times to re-enter or edit whatever information they previously entered.
If you accept the idea that the keystrokes, text editing, etc. are handled internally by the text input devices (not too hard to accept), then you can see that the application just has to display the dialog box and enter an idle loop, waiting for the user to click on OK or Cancel. Only in these cases, the application should wake up and respond. In general, after responding to the users action, the application returns to idle.
This example is not a formal or complete description of the event-driven programming paradigm, but I hope it gives you an idea of how the approach works, in case you were not familiar with it.
One of the key aspects of designing a GUI application is identifying what events the application should respond to, and writing the fragments of code that respond appropriately. These fragments of code are called event handlers.
In wxWindows, event handlers are implemented as methods of the window class related to the event. Events are associated with particular GUI elements in the application; for instance, in the previous example, it is not enough for the application to know that the user clicked the mouse; it has to know if the click was on the OK button, or if it was on the Cancel button. These are two different events, requiring two different responses. As the examples in the next sections will show, the application uses event tables to establish the links between the events and the corresponding event handlers. Event tables provide a practical and intuitive way to bind the various events to their corresponding event handlers.
Building Up the GUI
Although GUI applications can appear in a wide variety of formats, there are many common elements that appear in most applications. Among these common elements are menus and controls perhaps the most common elements to allow the application to interact with the end users, and dialog boxes, which provide a mechanism to organize the applications user interface. In this section I show how to add these common elements to wxWindows applications.
Menus
Providing menus in wxWindows applications is very straightforward. You typically create the menus in the constructor of the frame class, and write the event handlers for each menu selection.
In Listing 1, sample_wx.cpp, the code within the frame constructor, MainFrame::MainFrame, creates a menu tree with a structure as shown in Figure 1.
The first four statements in the constructor instantiate the menu objects. You need one wxMenuBar object for the top-level menu, and one wxMenu object for each pop-up menu required. You populate wxMenu objects using Append. Each menu item is identified by a unique numeric constant, which allows you to attach the event handlers to each menu selection. You can also use the function AppendSeparator to place a separator line at the corresponding position.
The second parameter to Append is a string that specifies the caption for the corresponding menu item. An & indicates that the character following is the accelerator key for that menu item, and a tab character indicates that what follows is a code for the shortcut key. You can use a third parameter to indicate that the added menu item has a sub-menu associated.
The code in the constructor shows only how to create the menus; you still need to write the event handlers and bind them to the various menu selections using an event table.
Creating an event table is a two-step procedure: declaring it (done in the frame classs definition), and defining it (done at file scope). To carry out the first step, you simply place the DECLARE_EVENT_TABLE macro in the frames class definition, typically in the private section:
class MainFrame : public wxFrame { // ... private: DECLARE_EVENT_TABLE() };Then, at some point in the file, define the event table using the BEGIN_EVENT_TABLE and END_EVENT_TABLE macros. BEGIN_EVENT_TABLE receives two parameters: the name of the class for which it is being defined (in this case, MainFrame), and the name of the base class (in this case, wxFrame). The base class parameter is necessary, since some events associated with the frame may have an event handler in the base class. The event table could look like the following:
BEGIN_EVENT_TABLE (MainFrame, wxFrame) EVT_MENU (ID_message1, MainFrame::OnFileMessage1) EVT_MENU (ID_message2, MainFrame::OnFileMessage2) EVT_MENU (ID_exit, MainFrame::OnFileExit) EVT_MENU (ID_about, MainFrame::OnHelpAbout) END_EVENT_TABLE ()Each EVT_MENU entry binds a menu event to an event handler. The first argument to the macro specifies a menu object having a particular ID; the second argument specifies the event handler, which is a member function of the applications frame class (in this case, MainFrame).
Event handlers in wxWindows are methods that receive a reference to a wxEvent object as a parameter. Handlers for different types of events receive specialized parameters (i.e., objects of classes derived from wxEvent). In this example, the event handlers should have a signature similar to the following:
void OnFileMessage1 (wxCommandEvent & event);The parameter provides information relevant to the event. In this particular case, such information is not useful, so the handler ignores the parameter. (Therefore, you may omit the parameter name from the parameter list.) An example of when this parameter may be particularly useful is in the handling of certain mouse events. In that case the parameter provides information such as the coordinates of the mouse pointer, the state of the mouse buttons, and the state of the Alt, Ctl, and Shift keys at the time the event was produced.
Listing 1 (sample_wx.cpp) shows the complete version of this minimal Example. (The full version in the online listings includes a note on how to install wxWindows and compile the application. See www.cuj.com/code.) I use wxMessageBox to display dialog boxes with simple messages as the response to the mouse events. Also notice the call to the Close method (inherited from wxFrame) in the OnFileExit event handler. This closes the main frame, terminating the application.
Controls
Controls are GUI elements that provide interaction between the user and the application. They may be used to get input from the user (as in the case of text boxes, push buttons, check boxes, etc.), or to display information (as in the case of static text boxes, static bitmaps, etc.).
Class wxControl represents the common functionality of controls, and is at the top of their class hierarchy. You can use wxControl-derived classes to represent specific types of controls, as required by your application.
In the following example I use classes wxButton, wxStaticText, and wxTextCtrl to implement a temperature conversion utility. The application shows a text box with a caption, and two buttons, one to convert from Celsius to Fahrenheit, and one from Fahrenheit to Celsius. The text box is represented by the wxTextCtrl class, the caption by the wxStaticText class, and the buttons by wxButton.
The constructors for the various types of controls are very similar for most regular controls. In particular, the first five parameters are the same for many of the frequently used controls. The first parameter is a pointer to the controls parent window (the window that owns or contains the control). The second parameter is the ID that uniquely identifies the control. This ID is useful if you need to handle events associated with that control. Even if you dont need to uniquely associate events with controls, it is in general a good idea to always provide a unique numeric ID for every control. The third parameter is the caption. For a wxButton, it specifies the caption displayed inside the button; for a wxStaticText, it is the text that the control displays; for a wxTextCtrl it indicates the initial contents of the text box, etc. The remaining two parameters are position and size.
You usually declare the controls as private data members of the window class that contains them, and instantiate them in the constructor. As with the frame and the menus, you must always allocate them dynamically.
The following fragment of code shows the modifications to the sample_wx program to create and display the controls:
class MainFrame : public wxFrame { public: MainFrame (const wxString & title, const wxPoint & position, const wxSize & size); private: wxStaticText * label; wxTextCtrl * input; // input // from user wxStaticText * output;// result wxButton * FtoC; wxButton * CtoF; DECLARE_EVENT_TABLE() }; MainFrame::MainFrame (const wxString & title, const wxPoint & position, const wxSize & size) : wxFrame (NULL, -1, title, position, size) { label = new wxStaticText (this, -1, "Temperature:", wxPoint(20,20), wxDefaultSize); input = new wxTextCtrl (this, -1, "", wxPoint(150,20), wxSize(80,20)); output = new wxStaticText (this, -1, "Result: ", wxPoint(20,50), wxSize(150,20)); FtoC = new wxButton (this, ID_FtoC, "F to C", wxPoint(250,20), wxDefaultSize); CtoF = new wxButton (this, ID_FtoC, "C to F", wxPoint(250,50), wxDefaultSize); }The sizes and positions of the controls assume that the main frame is 350 by 90. I used wxDefaultSize to indicate that those controls should assume their default size, according to the contents (caption) specified.
You typically use the this pointer as the first parameter in the constructors, indicating that the MainFrame object is the parent window. The two buttons are the only controls attached to events, so they do have ID values that uniquely identify them.
Listing 2 (controls.cpp) shows the complete code for this second sample application. It contains one important difference with respect to the sample shown above. In the sample above, I created the controls directly on the frame for the sake of simplicity. However, it is not a good idea to place controls in a wxFrame-derived object. Class wxFrame was not designed to act as a container for controls; for instance, it does not provide tab navigation through the controls. Listing 2 shows the recommended approach, which consists placing a container window (e.g., a wxPanel) inside your frame, and using it as the container for the controls.
In both event handlers, the information from the text box is read with wxTextCtrls GetValue method, which returns a wxString. This wxString is converted to its numerical equivalent, and used to generate the output string, which is sent to the output wxStaticText using its SetLabel method.
wxString::c_str converts the string to a C-style string that the Standard C atof function can take as a parameter. I could have also used wxString::ToDouble, which has the advantage that it notifies client code if the conversion was successful. (Check the online documentation for more details.)
If you want to clear the input text box after doing the conversion, you could use wxTextCtrls SetValue method, which receives a wxString that specifies the new value for the contents of the text box:
input->SetValue (""); // clear input // boxCustom Dialog Boxes
Class wxDialog provides a convenient framework for creating and using custom dialog boxes. As with wxFrame, you should use wxDialog as the base class to create your own custom dialog classes. wxDialog provides the basic functionality of dialog boxes, including the Show and ShowModal methods, and automatic closing and returning of the appropriate button code when the user clicks on OK or Cancel, etc.
In the following example, I create a dialog box that asks the user for name and password. The first step is to create the class to represent this custom dialog box. This procedure is relatively similar to the procedure for creating custom frames. The class definition is shown in Listing 3.
The public member functions allow client code to get the information entered by the user (name and password). I could have made the controls public, but this would break the encapsulation. I always prefer keeping the data members corresponding to the controls in the private section, and provide public methods to access the controls contents if required.
Instead of using event handlers attached to the OK and Cancel buttons, you can just use pre-defined ID values when creating them. These values (wxID_OK and wxID_CANCEL) identify them and give them the appropriate behavior, which includes closing the dialog when the user clicks on either button, and returning a code identifying the button to the client code.
The dialog can be used as follows:
MainFrame::OnLogin (wxCommandEvent &) { PasswordDlg pwdBox (this); if (pwdBox.ShowModal() == wxID_OK) { // use pwdBox.get_name() and pwdBox.get_password() // for whatever purposes as required } }Notice that here it is okay to declare a local dialog object, as opposed to dynamically allocating it. You may still choose dynamic allocation to be consistent with the use of other objects. The tricky detail is that if you allocate the dialog dynamically, then you must call its Destroy method when youre done, instead of using the C++ delete operator.
Listing 4 (pwd_box.cpp) shows the implementation details of this custom dialog sample. The rest of the sample application can be downloaded from CUJs web site (www.cuj.com/code/). This sample shows the approach recommended by wxWindows for organizing your code: split your projects into modules, with one pair of .h/.cpp files for the application class, and one pair for every dialog or frame class that you create.
I handle the keystroke events for both text boxes, to keep the OK button disabled as long as one of the two text boxes is empty. Both events require the same response (check the contents of both text boxes and enable the OK button accordingly); thus, I provide only one event handler, and attach both events to the same handler.
The password text box is given the appropriate behavior using the extra parameters in wxTextCtrls constructor (in particular, the style parameter). Setting the style to wxTE_PASSWORD sets the control to show asterisks as the user types, while internally storing the actual text entered by the user.
There are other, more convenient techniques available for the positioning and sizing of the controls in custom dialog boxes (notably the use of sizers). I omit a detailed discussion here on the use of sizers. You can find more information in the documentation and samples available when you download and install wxWindows, or from the wxWindows website.
Common Dialogs
wxWindows provides a handful of ready-to-use custom dialogs for common data entry tasks, such as browsing for a filename or multiple filenames, fonts, printer, etc. The fragment of code below shows an example using the file selection dialog. You can check the samples shipped with wxWindows (also available from their web site) to find out more about this and the other common dialogs.
MainFrame::OnFileOpen (wxCommandEvent &) { wxFileDialog dialog (this, "Open file", "", "", "*.*"); if (dialog.ShowModal() == wxID_OK) { // Use dialog.GetPath() as required // dialog.GetFilename() and dialog.GetDirectory() // may also be used. } }Memory Management Issues
As I already mentioned, you must always use dynamic memory allocation for objects that represent GUI elements (controls and windows). One detail that I didnt mention is that you dont have to explicitly use delete to deallocate them.
Dialogs and frames keep track of the controls they own. When you call wxDialog::Destroy, or wxFrame::Close, etc., these methods handle self-destruction. Upon destruction, these objects handle destruction of the controls they contain, and any other window objects that are owned by them. (They use delete internally, which is why you must always use new to allocate such controls and window objects.)
In some cases, such as with dialogs, it is okay to use allocation on the stack. My advice is that you check the samples to see how allocation is handled for specific classes that youre not familiar with.
The Future of wxWindows
I hope that this introduction triggers your interest in this convenient and powerful toolkit. A lot of effort is being done to improve and increase wxWindows capabilities. Among the features that are relevant to Linux developers are: support for CORBA; better support for graphics, including OpenGL; and HTML and XML support. The support for databases is also improving; more drivers are being embedded to provide support for a wider range of database engines and servers. Unicode support, and thus better internationalization support, is also on its way.
The creation of the newsgroup comp.soft-sys.wxwindows [1] provides new users with the possibility of discussing and getting support from the wxWindows community. This could compensate for the relative lack of documentation, which is arguably one of wxWindows main weak points. Of course, this relative lack of documentation is also compensated by the ease of learning and using wxWindows.
Commercial supplements also promise to put wxWindows on a solid standing for the future. These supplements will include technical support in the form of timely solutions or alternatives to development problems. (Commercial support is currently available for wxPython, a close cousin of wxWindows.)
Another currently available commercial supplement is wxDesigner [2]. It provides a graphical IDE that simplifies the design of portable, sizer-based custom dialogs.
Conclusion
wxWindows robustness is definitely an important benefit, but its being so easy to learn and use is also one of the most important features of this toolkit. I would hope that after reading this article you are convinced. As additional support to my claim that wxWindows is easy to learn and use, I provide a case study. If you are interested, please see the sidebar.
Cross-platform support is another extremely important feature of wxWindows. Even though the emphasis of this article was on using wxWindows to develop GUI applications on Linux, it is worth mentioning that all of the samples included in this article compile and run without any modification whatsoever under MS Windows, and surely on other platforms as well (I actually tested them only on a RedHat Linux 6.2, using g++ 2.95.2, and on Windows 2000, using Borland C++ 5.5.1).
I have presented an introductory discussion on the main features, but you can find out about the many additional features and tools from the online documentation [3] and samples. These are shipped with wxWindows and are made available when you install it. They are also available from wxWindows web site, following the links to the documentation and samples.
Acknowledgements
I would like to thank the authors of the wxWindows toolkit for kindly reviewing the article draft and making valuable suggestions.
References
[1] wxWindows newsgroup: comp.soft-sys.wxwindows.
[2] wxDesigner. http://www.roebling.de.
[3] wxWindows online documentation. http://www.wxwindows.org.
Carlos Moreno has a Bachelors Degree in Electronics Engineering and a Master Engineering diploma in Computer and Electrical Engineering. He has 12 years of experience in the development of low-level applications and currently works as an instructor/trainer in C, C++, Object-Oriented Programming, and Visual Basic, and as an independent consultant and software developer. His main interests are digital signal processing, audio and image processing applications, communications, data encryption and compression, and (of course) computer games development. He can be reached at moreno@mochima.com or on the Web at http://www.mochima.com.