Beech:Using controls
Introduction
In the previous session we looked at the basic architecture of wxWidgets and built the most simple of applications. It didn't do much but it did demonstrate the basic functionality of a typical Windows program.
The next step is to add some more functionality so that we develop something remotely useful but most importantly, learn more about the wxWidgets framework.
In this session we add a text control, a menu and a statusbar to BasicFrame. The first step is adding the text control.
Adding a Text Control
Adding a text control to the empty frame
The application with a text control is shown here. The text control provides all the functionality of a text editor such as cursor movement, text insert, selection and delete, cut, copy, paste, etc. It can, with some fairly straightforward programming, provide for file open, save and save as, undo and redo operations, and different fonts.
The simplest style of wxTextCtrl is just a single line text control which you might use in a dialog box to gather data from a user of your program. Changing the text control to something that will handle many lines is simply a matter of changing the style. Likewise, changing the text control to something that will handle large amounts of data is also achieved by changing the style.
Here is the source code for our BasicFrame with a text control.
#ifndef BASIC_H
#define BASIC_H
class BasicApplication : public wxApp
{
public:
virtual bool OnInit();
};
class BasicFrame : public wxFrame
{
public:
BasicFrame( const wxChar *title, int xpos, int ypos, int width, int height );
~BasicFrame();
wxTextCtrl *theText;
};
#endif
The header file is almost unchanged except for the line "wxTextCtrl *theText;". Here we declare that there will be a thing called theText which will be a pointer to a wxTextCtrl.
#include <wx/wx.h>
#include "basic.h"
IMPLEMENT_APP(BasicApplication)
bool BasicApplication::OnInit()
{
BasicFrame *frame = new BasicFrame(
_("wxWidgets Basic Steps - Step 1: A Simple Application"),
50, 50, 200, 200);
frame->Show(true);
SetTopWindow(frame);
return true;
}
BasicFrame::BasicFrame( const wxChar *title, int xpos, int ypos, int width, int height)
: wxFrame( (wxFrame *) NULL, -1, title, wxPoint(xpos, ypos), wxSize(width, height) )
{
theText = (wxTextCtrl *) NULL;
theText = new wxTextCtrl( this, -1,
wxString("This is a text control\n\n"
"The text control supports "
"basic text editing operations\n"
"along with copy, cut, paste, "
"delete, select all and undo.\n\n"
"Right click on the control "
"to see the pop-up menu.\n"
),
wxDefaultPosition,
wxDefaultSize,
wxTE_MULTILINE
);
}
BasicFrame::~BasicFrame()
{
}
In the constructor you can see that we create the text control with some default arguments and that there is a line "theText = (wxTextCtrl *) NULL;" which in this case is quite unnecessary but I have introduced just a reminder about pointer safety. As the complexity of your programs increases you will quite often declare pointers to objects that don't yet exist. Sometimes you fall into the trap of using one of these pointers before you have actually created the instance of the class you want to point at. The results of using an uninitialized pointer can range from mildly irritating to sickeningly frustrating. You could lose just seconds of your precious time or even hours. As a matter of course you should always initialize any pointer you have declared in an interface file. Other people will say "It doesn't matter, I intend to init the thing straight away!". I say - ignore them and avoid disturbing the even tenor of your life with bad pointers. I do this out of a sense of brotherly concern for your well-being. The call to the text control constructor "theText = new wxTextCtrl( ... )" contains a parameter "this" which is a reference to the parent window. In this case "this" points to the parent frame that is being, or just has been, constructed.
User note: I compiled this from within Eclise 3.5.2 + CDT 1.2.2 using the MinGW port of GCC 4.1.2 and wxWidgets 2.8.3, on Windows XP w/SP3. While the application runs as expected, on exit it causes a Windows GPF. I was able to fix it by adding the macro "WXUSINGDLL" to the compiler options. After that, there were no further problems.
The Text Control Class - wxTextCtrl
wxTextCtrl(
wxWindow* parent,
wxWindowID id,
const wxString & value = "",
const wxPoint & pos,
const wxSize & size = wxDefaultSize,
long style = 0,
const wxValidator & validator,
const wxString & name = "text"
)
If you compare the constructor shown here with the example you will see that I have used a number of defaults, just as I did with the BasicFrame in the previous session.
The wxTextCtrl constructor exhibits the same kind of pattern as many of the wxWidgets controls and this is a great strength of the wxWidgets framework. Many of the new things you learn about the framework, for example the pattern of constructor declarations, can be used as a guide elsewhere in the framework. You've seen the wxFrame constructor, and can see that the wxTextCtrl constructor is very similar in both the order of the parameters it uses and the types of the parameters.
The wxTextCtrl introduces a new parameter that applies only for text: the wxValidator class. I won't describe the details of this class at the moment, it's quite complex. It's enough to say that we can use the validator to validate data which passes between program data structures and controls. You can use a validator to limit input to text only, or numeric for example.
The remaining parameters for the wxTextCtrl constructor are the same as for the wxFrame:
- wxWindow* parent - a pointer to the parent window, BUT it can't be NULL, whereas the frame could,
- wxWindowID id - an ID for the control,
- const wxString & value = "" - an initial value (see the example),
- const wxPoint & pos - the x and y location of the top-left corner,
- const wxSize & size = wxDefaultSize - the width and height dimensions,
- long style = 0 - the style (see below),
- const wxString & name = "text" - a name for the control.
wxTextCtrl Styles
- wxTE_PROCESS_ENTER - The control will generate the message wxEVENT_TYPE_TEXT_ENTER_COMMAND (otherwise pressing is either processed internally by the control or used for navigation between dialog controls).
- wxTE_PROCESS_TAB - The control will receieve EVT_CHAR messages for TAB pressed - normally, TAB is used for passing to the next control in a dialog instead. For the control created with this style, you can still use Ctrl-Enter to pass to the next control from the keyboard.
- wxTE_MULTILINE - The text control allows multiple lines.
- wxTE_PASSWORD - The text will be echoed as asterisks.
- wxTE_READONLY - The text will not be user-editable.
- wxHSCROLL - A horizontal scrollbar will be created. No effect under GTK+.
We can also add:
- wxTE_RICH - which will turn a plain text control, which has a limit of around 32K of data, into a richtext text control which can handle larger amounts of data. This is for Windows 95/98 only and is ignored on other platforms (for WinNT there is no limit for the plain mult-line text control).
A text control style, like a frame style, is the OR of a one or more styles, however the default style is 0, indicating that you will get a single-line text control no matter how big the width and height.
You should experiment with the wxTextCtrl styles just to get some idea of what you can and can't combine, for instance could you have a style of wxTE_MULTILINE | wxTE_PASSWORD?
To build the program:
Create the source files in a single directory
- basic.h
- basic.cpp
- basic_resources.rc
- Makefile
Run make in the directory where the files are stored
- make
- Run the program
- basic
Adding a Menu Bar
Although the frame and text control give us quite a bit of program function we still have a few steps to take before we have anything remotely like a "real" application. The next thing to add is a menu bar so that we can prepare for our user to issue different commands to the program.
#ifndef BASIC_H
#define BASIC_H
enum {
BASIC_EXIT = 1,
BASIC_OPEN = 100,
BASIC_ABOUT = 200
};
class BasicApplication : public wxApp
{
...
};
class BasicFrame : public wxFrame
{
public:
BasicFrame( ... );
~BasicFrame();
wxTextCtrl *theText;
wxMenuBar *menuBar;
wxMenu *fileMenu;
};
#endif
The header file is substantially the same except for the additions:
enum {
BASIC_EXIT = 1,
BASIC_OPEN = 100,
BASIC_ABOUT = 200
};
Note that I have cut some parts (shown with the ellipse ...) since they are unchanged. The enum declares some values which we will use to associate with a menu. The following lines:
wxMenuBar *menuBar;
wxMenu *fileMenu;
declare that the BasicFrame class has two new data members. One is a menubar, the other a menu. The menu bar of course is the piece that sits at the top of the frame. The menu is a drop down menu.
#include <wx/wx.h>
#include "basic.h"
IMPLEMENT_APP(BasicApplication)
bool BasicApplication::OnInit()
{
...
return true;
}
BasicFrame::BasicFrame( ... )
{
theText = (wxTextCtrl *) NULL;
menuBar = (wxMenuBar *) NULL;
fileMenu = (wxMenu *) NULL;
theText = new wxTextCtrl( ... );
fileMenu = new wxMenu;
fileMenu->Append( BASIC_OPEN, _("&Open File") );
fileMenu->Append( BASIC_ABOUT, _("&About") );
fileMenu->AppendSeparator();
fileMenu->Append( BASIC_EXIT, _("E&xit") );
menuBar = new wxMenuBar;
menuBar->Append( fileMenu, _("&File") );
SetMenuBar(menuBar);
}
BasicFrame::~BasicFrame()
...
The additions to the implementation file are shown here.
We play safety first again:
menuBar = (wxMenuBar *) NULL;
fileMenu = (wxMenu *) NULL;
We create an instance of wxMenu:
fileMenu = new wxMenu;
We use the Append() and AppendSeparator() methods to add items to the new file menu:
fileMenu->Append( BASIC_OPEN, _("&Open File") );
fileMenu->Append( BASIC_ABOUT, _("&About") );
fileMenu->AppendSeparator();
fileMenu->Append( BASIC_EXIT, _("E&xit") );
Finally we create an instance of wxMenuBar, append the file menu to it and call BasicFrame's SetMenuBar() method to add the menu bar to the frame:
menuBar = new wxMenuBar;
menuBar->Append( fileMenu, _("&File") );
SetMenuBar(menuBar);
Note how the ampersand ("&") is used in strings like "E&xit" and "&File". This indicates which character in a menubar or menu will be the menu hotkey.
You might think our menubar doesn't do much at the moment apart from display itself when we click on it with mouse or press Alt-F. It will soon though when we look at events, meanwhile we need to add a statusbar.
To build the program:
Create the source files in a single directory
- basic.h
- basic.cpp
- basic_resources.rc
- Makefile
Run make in the directory where the files are stored
- make
- Run the program
- basic
Adding a Status Bar
Adding a status bar couldn't be simpler. Add the line "CreateStatusBar(3);":
SetMenuBar(menuBar);
CreateStatusBar(3);
This will add a status bar containing 3 fields to BasicFrame. We write a string value to a field with the SetStatusBarText(string, integer) method of wxFrame. Integer is 0, 1 or 2 in the case of a 3 field status bar.
If you make a small change to the previous example, ie add a third parameter to the Append() method:
fileMenu->Append( BASIC_OPEN, _("&Open File"), _("Open an existing file") );
fileMenu->Append( BASIC_ABOUT, _("&About"), _("Who wrote this!") );
fileMenu->AppendSeparator();
fileMenu->Append( BASIC_EXIT, _("E&xit"), _("Stop wasting time.") );
you will see the third parameter displayed in status bar field 0.
Classes Used
We only used two new classes wxMenuBar and wxMenu, and we use the CreateStatusBar() method of wxFrame which took care of the creating, positioning and so on of a status bar.
I will save looking at the classes wxMenuBar and wxMenu until later, the next thing we need to do is to see how we make our program respond to menu events.
Summary
In this session we enhanced our very basic application by adding some typical windows features. The interesting thing was how little effort it took. This is a sign of a well-designed framework.
We also saw how simple it is to modify the style of the text control and gained a hint of the flexibility of the wxMenu class.