Beech:Using events

From WxWiki
Jump to: navigation, search

Introduction

Many years ago I worked on two different kinds of operating system. One was a time-sharing system and the operating system scheduler gave each user a slice of time in which to use the computer resources. The other was an event-driven system, it was an industrial process-controller and the process could not afford to wait for users to each get a share of resources. The computer system was designed to rapidly respond to external events like an imminent nuclear core melt-down. In some aspects GUI systems combine the event-driven model with the time-shared model.

An event is some interesting occurrence that has happened outside the program. It might have been initiated by the user selecting a menu item, or pressing a hotkey. It might have been initiated by something in the computer environment but outside the program, a timer running down, some data available at a communications port, a system shutdown message and so on. Whatever the origins of the event it needs to be dealt with.

As I write this I have 10 applications loaded on my system, a Windows 98 system. Apart from this editor the applications don't appear to be doing much, occasionally the hard disk light will flash. However if I click on an application on the task bar that application will appear and be ready to work. The task bar receives the mouse click event, pops the event and related information into a queue managed by the operating system and somehow the appropriate application gets a message to say it's been called.

A common programming technique for event-handling is to use call-back functions. Here is an example of the use of call-back functions in FLTK (another cross-platform framework):

Fl_Menu_Item menuitems[] = {
	{ "&File", 0,0,0, FL_SUBMENU },
	{ "&New", FL_ALT + 'n', (Fl_Callback *) new_cb }, 
	...    
}

void new_cb(void)
{
	if ( changed && !check_save() ) return;

	filename[0] = '\0';
	input->value("");
	set_changed(0);
}

The program fragment shows a menu item which has a function associated with it new_cb. The function is type-cast as a pointer to Fl_Callback. When the application receives a message from the operating system indicating that the New menu item was selected then the application can call new_cb().The Win32 API also has a similar mechanism. A window that needs to respond to events has a function that is declared as a CALLBACK type function.

wxWidgets and Microsoft's MFC framework (Microsoft Foundation Classes) both use a different technique, at least from the programmer's point of view. It's quite possible that underneath the call-back mechanism is still used. It's called the "event table" and it simplifies the business of programming for events.

The wxWidgets Event Table

BEGIN_EVENT_TABLE (BasicFrame, wxFrame)
	EVT_MENU ( BASIC_EXIT,  BasicFrame::OnExit)
	EVT_MENU ( BASIC_ABOUT, BasicFrame::OnAbout)
	EVT_MENU ( BASIC_OPEN,  BasicFrame::OnOpenFile)
END_EVENT_TABLE()

As you can see here the wxWidgets event table is simplicity in itself. It is a collection of macros, the first heralds the beginning of the event table, the next three link a constant of some kind with a class method, and the last heralds the end of the event table. The event table maps events, identified by constants like BASIC_OPEN, to functions, methods like BasicFrame::OnOpenFile.

We could have many event tables in our program so we need to make clear which class will process the events for any given event table. The BEGIN_EVENT_TABLE macro declares that this event table belongs to BasicFrame which is derived from the wxFrame class. We also need to to make clear that the class will be using an even table and do this with the DECLARE_EVENT_TABLE() macro which appears in the class prototype in the interface file. And lastly we have to provide the member functions which will process the events. We should look at a practical example of all of this:

#ifndef BASIC_H
#define BASIC_H

#include <wx/wx.h>

static const wxChar *TITLE = _T("Basic - Step 3: Responding to Events");

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;
	wxMenuBar  *menuBar;
	wxMenu     *fileMenu;

	void OnOpenFile (wxCommandEvent & event);
	void OnAbout    (wxCommandEvent & event);
	void OnExit     (wxCommandEvent & event);

	DECLARE_EVENT_TABLE()
};

enum {
	BASIC_EXIT  =   1,
	BASIC_OPEN  = 100,
	BASIC_ABOUT = 200
};

#endif

Here is the header file, you will see a number of new things here and not all of them are related to events.The first thing is the this odd piece of work:

static const wxChar *TITLE = _T("Basic - Step 3: Responding to Events");

You can guess that we are declaring a pointer to a char, that it is a constant value and that it is static. In this particular example there is not much point to declaring the variable to be static. The reason for this is that we typically use static to ensure the value is retained between invocations of a function (internal static) or hide variables from other files (external static). Since we aren't using any other files in this example the static memory class is redundant. Nonetheless it is good practice to declare external or global variables, ie variables declared outside a function, as being static. The reason is that we might use this file elsewhere and want to head off the possibility of name clashes. See [icpp_scope.html#external External variables - static and automatic] to review the material on static variables. The macro _T is a pseudonym for another macro wxTRANSLATE which has something to do with locale information, but what I don't yet know! But now to the event table matter: You can see to the left we have three member functions, each is a void function and each has a wxCommandEvent argument. These are effectively our callback functions. There is also a macro DECLARE_EVENT_TABLE() which alerts the compiler to the fact that this class will have an event table. Last there is an enumerated range which we use to give some meaningful identifiers to some numeric values.

#include "basic.h"

IMPLEMENT_APP(BasicApplication)

bool BasicApplication::OnInit()
{
	...
}

BasicFrame::BasicFrame(...)
{
	...

	fileMenu = new wxMenu;
	fileMenu->Append(BASIC_OPEN,  _T("&Open file"));
	fileMenu->Append(BASIC_ABOUT, _T("&About"));
	fileMenu->AppendSeparator();
	fileMenu->Append(BASIC_EXIT,  _T("E&xit"));

	menuBar = new wxMenuBar;
	menuBar->Append(fileMenu, _T("&File"));
	SetMenuBar(menuBar);
	CreateStatusBar(3);  
}

BasicFrame::~BasicFrame() { ... }

BEGIN_EVENT_TABLE (BasicFrame, wxFrame)
	EVT_MENU ( BASIC_EXIT,  BasicFrame::OnExit)
	EVT_MENU ( BASIC_ABOUT, BasicFrame::OnAbout)
	EVT_MENU ( BASIC_OPEN,  BasicFrame::OnOpenFile)
END_EVENT_TABLE()

void BasicFrame::OnOpenFile( wxCommandEvent & event )
{
	theText->LoadFile(_("data.txt"));
}


void BasicFrame::OnAbout( wxCommandEvent & event )
{
	wxString t = TITLE;
	t.append( _T("\nDB 2001") );

	wxMessageDialog aboutDialog( this, t, _T("About Basic"), wxOK | wxCANCEL );
	aboutDialog.ShowModal();
}

void BasicFrame::OnExit( wxCommandEvent & event )
{
	Close(TRUE);
}

USER WARNING: I tried to compile this Using latest SVN Code::Blocks with custom created project, but ended up with error "The Application could not innitialize properly()". My fix for this was to put in a define in the project settings: "WXUSINGDLL"

To the left is the implementation file. Nothing has changed here except that we have added:

  • An event table to map events to functions
  • The member functions to handle the eventsOne way in which wxWidgets helps the programmer to distinguish between the different kinds of events is in the provision of different kinds of macros. The table contains three EVT_MENU() macros which deal specifically with menu events. Amongst the other macros are:
  • EVT_BUTTON
  • EVT_CHECKBOX
  • EVT_RADIOBUTTON
  • EVT_SLIDER
  • EVT_SCROLLBAR and many, many others. In later sessions we learn how to use other events, for now we just concentrate on the basics. The OnOpenFile method demonstrates how useful wxTextCtrl is. It has a method that will directly load the contents of a file into the control. When we click the "Open file" item of the File menu this triggers an event, the operating system catches it and notifies our application which then calls the appropriate function - load a file into theText.The OnAbout method uses a new class wxMessageDialog. I am going to look at this class and the wxCommandEvent class later in this session.

Summary

Something to try...

wxWidgets has an event macro which helps handle window sizing:

EVT_SIZE ( BasicFrame::OnSize )

Note that no event ID is mapped to it since it is assumed it applies to the current window. Add the size event to the example, create the OnSize event handler and in the handler write some code to get the window size and display it in the window title bar. Hint: You might want to use a wxString and the wxString::Printf() method.

Although this is an important session I have kept it short and limited the scope so that you can get a good basic insight into wxWidgets events. Handling events can be quite a complex business and even though the wxWidgets framework makes it much easier for the programmer there is still a reasonable degree of complexity.

In the next session we look at using some of the common dialogs.