Custom Events Tutorial

From WxWiki
Revision as of 12:35, 21 January 2011 by Greenbreen (Talk | contribs) (Fixed error caught by compiler; now compiles and functions correctly on my system under wxWidgets 2.8.10)

(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Jump to: navigation, search

Copyright © 2000, 2001 by Markus Neifer

Permission is granted to copy, distribute and/or modify this document under the terms of the GNU Free Documentation License, Version 1.1 or any later version published by the Free Software Foundation.

In this article we'll have a look at the wxWidgets "Hello, world" sample and we extend it to use user-defined events. It assumes that you have read (and understood) the "Hello, world" sample. The code snippets could be used for "cut 'n' paste". That means that if you put all code snippets (in the order they appear here) in one file you have a working sample. If you want a more realistic sample you could create a header and a source file for each class. Have a look at the "// Begin of" and "// End of" comments to see what files should be created and uncomment additional include statements.

Create a new event class

To do the real work we need one class for the new event, of course. It must inherit from wxWidgets' wxEvent class. Objects of this class will represent the event.

// Begin of MyEvent.h
 
#include <wx/wx.h>
 
class MyEvent : public wxEvent
{
public:
	MyEvent( wxWindow* win = (wxWindow*) NULL );
	wxEvent* Clone() const {return new MyEvent(*this);}
 
	DECLARE_DYNAMIC_CLASS( MyEvent )
};

You may wonder what this mysterious DECLARE_DYNAMIC_CLASS() macro and the Clone() method are all about. These are used by the wxWidgets run-time type information system. The macro is necessary for classes when one wishes to check the object type at run-time. The Clone() method is inherited from the wxEvent base class and works like a 'virtual' assignment operator. We won't worry about all this here. If you scratch your head why we cast NULL to wxWindow* here then you might be surprised to hear that some compilers define NULL to be 0L preventing assignment to pointers. This cast makes your code slightly more portable.

Provide event table information

As we'll see later a so called 'event table' is created to connect event handler methods to the wxWidgets event system. Several steps are necessary to make this process as easy as possible to users of your event class.

Create a typedef for the event handler method pointer

If you've ever used method pointers (or function pointers in C) you know that it's very helpful to create a typedef for the pointer. That helps to clarify statements a lot. We'll follow this simple rule and create a typedef for a pointer to methods of class wxEvtHandler with void return type and one argument of type MyEvent&. We come back to this later and you'll see why class wxEvtHandler comes into play.

typedef void (wxEvtHandler::*myEventFunction)(MyEvent&);

Create a new event constant

To identify your event and make it distinguishable from other events you have to create a new event constant. If you have a look into the file %wxwin%/include/wx/event.h you'll see all wxWidgets event constants. Thanks to the new event type handling for wxWidgets 2.4 (which is already used in 2.3), this is less error prone now. Just declare your event type using the appropriate macros. These macros provide backward compatiblity with the old event type handling based on enum constants. The second macro parameter for DECLARE_EVENT_TYPE() (1 in our case) is only used, if you compile this code in compatibility mode.

BEGIN_DECLARE_EVENT_TYPES()
	DECLARE_EVENT_TYPE( myEVT_MYEVENT, 1 )
END_DECLARE_EVENT_TYPES()

A quote from file %wxwin%/include/wx/event.h about event classes vs. event types: "An event class represents a C++ class defining a range of similar event types; examples are canvas events, panel item command events. An event type is a unique identifier for a particular system event, such as a button press or a listbox deselection."

Declare event table entry code

Every event handler needs its event table entry. This entry uses the typedef defined above to connect the appropriate event handler method to the wxWidgets event system. Please note the comma at the end of the last line.

#define EVT_MYEVENT(func)                              \
	DECLARE_EVENT_TABLE_ENTRY( myEVT_MYEVENT,      \\
		-1,                                    \\
		-1,                                    \\
		(wxObjectEventFunction)                \\
		(myEventFunction) & func,              \\
		(wxObject *) NULL ),
// End of MyEvent.h

Implement your event class

Implement the constructor

First we have to call the IMPLEMENT_DYNAMIC_CLASS() macro which is the companion of the DECLARE_DYNAMIC_CLASS() macro we've discussed in section 'Create a new event class'. If you're interested what this dynamic stuff is all about i recommend reading the 'Run time class information overview' in the wxWidgets online help (see topic overviews). After that a call to the DEFINE_EVENT_TYPE() macro creates an object for the variable we've declared in section 'Declare event type'. The constructor sets the appropriate event type using that variable within the SetEventType() method. Additionally it registers the object that has generated the event using the SetEventObject() method. These two methods are defined in the wxEvent class from which we derive.

// Begin of MyEvent.cpp
// #include "MyEvent.h"
 
IMPLEMENT_DYNAMIC_CLASS( MyEvent, wxEvent )
DEFINE_EVENT_TYPE( myEVT_MYEVENT )
 
MyEvent::MyEvent( wxWindow* win )
{
	SetEventType( myEVT_MYEVENT );
	SetEventObject( win );
}

Create a new frame class

This sample uses only a simple application with one window. We need a constructor and three handler methods to handle application exit, the generation of user-defined events and the receivement of user-defined events. The DECLARE_EVENT_TABLE() macro adds members and methods used by the wxWidgets event system to our frame class. To identify the different menu events we need some enum constants.

// Begin of MyFrame.h
// #include "MyEvent.h"
 
class MyFrame: public wxFrame
{
public:
	MyFrame( const wxString & title, const wxPoint & pos, const wxSize & size);
 
	void OnQuit( wxCommandEvent & event );
	void OnFireEvent( wxCommandEvent & event );
	void OnMyEvent( MyEvent & event );
 
	DECLARE_EVENT_TABLE()
};
 
enum
{
	ID_Quit = 1,
	ID_FireEvent,
};
 
// End of MyFrame.h

Implement your frame class

Create the event table

The event table connects the event handler methods to the wxWidgets event system. We use the entry code we've defined above for this purpose. As you can see it's quite simple to build the event table because we've already done most of the necessary work above. And now it's clear why we need a method pointer to a wxEvtHandler class in section 'Create a typedef for the event handler method pointer': wxFrame (which owns the handler methods) is derived from wxWindow which is derived from wxEvtHandler. Every class that wishes to provide event handler methods must derive from wxEvtHandler.

// Begin of MyFrame.cpp
// #include "MyFrame.h"
 
BEGIN_EVENT_TABLE( MyFrame, wxFrame )
	EVT_MENU( ID_Quit, MyFrame::OnQuit )
	EVT_MENU( ID_FireEvent, MyFrame::OnFireEvent )
	EVT_MYEVENT( MyFrame::OnMyEvent )
END_EVENT_TABLE()

Implement the constructor

The constructor initializes the base class and creates a simple menu to make the application usable.

MyFrame::MyFrame( const wxString & title, const wxPoint & pos, const wxSize & size)
: wxFrame( (wxFrame*) NULL, -1, title, pos, size )
{
	wxMenu* menuFile = new wxMenu();
 
	menuFile->Append( ID_FireEvent, _("&Fire Event") );
	menuFile->AppendSeparator();
	menuFile->Append( ID_Quit, _("E&xit") );
 
	wxMenuBar* menuBar = new wxMenuBar();
	menuBar->Append( menuFile, _("&File") );
 
	SetMenuBar( menuBar );
}

Implement the quit event handler

The exit application handler is quite simple. It just closes the top frame which exits the application.

void MyFrame::OnQuit( wxCommandEvent& WXUNUSED( event ))
{
	Close( true );
}

Implement the fire event handler

This method generates the user-defined event. It creates one object of our event class and calls the ProcessEvent() method of wxEventHandler which is one base class of our MyFrame class. The WXUNUSED() macro is not really necessary here but there are compilers that complain if you declare 'empty' parameters (writing just the type). As we don't need the event parameter inside our method, this macro prevents you from some warning messages while providing compatibility with many compilers. One of the many goodies wxWidgets provides for multi-platform, multi-compiler development.

void MyFrame::OnFireEvent( wxCommandEvent& WXUNUSED( event ))
{
	MyEvent my_event( this );
 
	if ( ProcessEvent( my_event ) == false )
		wxLogWarning( _("Could not process event!") );
}

Implement the event handler for the user-defined event

This method is called when an event of our event class is processed in the wxWidgets event system. It shows a message box to indicate the receivement of the event.

void MyFrame::OnMyEvent( MyEvent& WXUNUSED( event ))
{
	wxMessageBox( _("Looks like it works!"), _("MyEvent reached"),
		wxOK | wxICON_INFORMATION, this );
}
 
// End of MyFrame.cpp

Create and implement a new application class

Finally we need an application class that shows the main frame and delivers the application entry point using the IMPLEMENT_APP() macro.

// Begin of MyApp.h
 
class MyApp: public wxApp
{
	virtual bool OnInit();
};
 
// End of MyApp.h
 
// Begin of MyApp.cpp
// #include "MyFrame.h"
// #include "MyApp.h"
 
IMPLEMENT_APP( MyApp )
 
bool MyApp::OnInit()
{
	MyFrame* frame = new MyFrame( _("Hello Event World"),
		wxPoint(50, 50), wxSize(450, 340));
	frame->Show( true );
	SetTopWindow( frame );
 
	return true;
}
 
// End of MyApp.cpp

Build and test your application

Now you should be able to build the application and run some tests. When the application comes up select "Fire Event" from the "File" menu. A message box should indicate that it works. If the message box doesn't come up please re-read this tutorial and your code, check your build environment with other applications. If it still doesn't work you may ask the wxWidgets mailing list or the author of this tutorial for help.

But remember: Hunting for errors and fixing them is always a good experience to really understand what's going on. Use your favorite debugger to have a look inside this interesting application and see how all this works. Another good idea is to run just the preprocessor with this source and see what all these macros expand to.