Custom Events

From WxWiki
Jump to: navigation, search

Introduction

Starting with wx3.0, there have been enough changes to how custom events are created (different macros) and used (wxEvtHandler::Bind) that a separate page will be useful. You can find the page for older wx versions here.

Things you should understand before you start

Events and Event-types

These two completely different things often get confused.

An event is an object of class wxEvent, or more usually of one of its derivatives e.g. wxCommandEvent. A wxEventType is just a typedef for int. However the (understandable) tendency to use names like MyFooCommandEvent for wxEventTypes can cause considerable confusion.

Event Macros

To make the event system work, wx uses macros which you don't need to know much about. However there are other macros that do need a mention because you should use them, and they have changed since wx2.8: the macros for creating a new wxEventType.

The old macros for this were: DECLARE_EVENT_TYPE(MY_NEW_TYPE, wxID_ANY) and DEFINE_EVENT_TYPE(MY_NEW_TYPE) though it was almost as easy to use non-macro code. SInce wx3.0 events are type-safe and the new macros take this into account; so code that doesn't have to be backwards-compatible should use: wxDECLARE_EVENT(MY_NEW_TYPE, wxCommandEvent); and wxDEFINE_EVENT(MY_NEW_TYPE, wxCommandEvent); (Note the semicolons.) You now have to specify the event class that will use the new wxEventType; it will often be wxCommandEvent.

Dynamic or Static event binding

wx code has traditionally tended to use static event tables to connect events to handlers. However it has long been possible to do this dynamically, and since wx3.0 this has become easier and much safer with the addition of wxEvtHandler::Bind. Custom events can use either method.

It is strongly suggested that you no longer use wxEvtHandler::Connect (except when backward-compatibility with wx2.8 is essential) as it is so easy to get it wrong; and getting it wrong either fails silently or causes runtime crashes.

Dispatching your custom event

Once you've created an event instance using your custom class or wxEventType, you need to send or post it to its destination. You can do this using the same methods as in wx2.8: sending (synchronously) with wxEvtHandler::ProcessEvent or posting (asynchronously) with wxEvtHandler::AddPendingEvent or the convenience function wxPostEvent. However since wx3.0 there is also the asynchronous (and more thread-safe) wxEvtHandler::QueueEvent and its convenience function wxQueueEvent.

What sort of custom event do I need?

The term 'custom event' can be used to mean either creating your own subclass of wxEvent or wxCommandEvent, or just defining a new wxEventType to use with the standard event classes. Which of these you do depends on how much, and what type of, data you want to send in your event.

Most of the time you won't need a subclassed wxEvent, just a standard event with a new wxEventType to 'label' it. Even a plain wxEvent can transport useful data in its id and eventType fields. A wxCommandEvent additionally has fields for another int, a long int and a wxString. It can also take a void* in its clientData field. All these should be more than adequate in most situations, so needing to create your own custom class is rare. However if you do need to transport lots of data or have other special needs, subclassing is available and gives you the flexibility to do whatever is required.

Creating and using a new wxEventType

This is easy and (almost) foolproof.

Creation:

In a suitable cpp file add the line:

It doesn't have to be a wxCommandEvent, but that's the usual choice.

Posting:

You can use the new wxEventType either with or without giving the event a specific id.

wxCommandEvent event(MY_NEW_TYPE); // No specific id
 
// Add any data; sometimes the only information needed at the destination is the arrival of the event itself
event.SetString("This is the data");
 
// Then post the event
wxPostEvent(this, event); // to ourselves
wxPostEvent(pBar, event); // or to a different instance or class

If you need to send more specific information to the destination, add an id. This can easily be done using an enum.

// The values don't matter, as they're only used with this wxEventType
enum { foo_one = 1, foo_two, foo_three };
 
wxCommandEvent event_a(MY_NEW_TYPE, foo_one); 
event_a.SetString("data one"); wxPostEvent(this, event_a);
 
wxCommandEvent event_b(MY_NEW_TYPE, foo_two); 
event_b.SetString("data two"); wxPostEvent(this, event_b);
 
wxPostEvent(this, event_a); wxPostEvent(this, event_b);
// You can send the same event elsewhere as it's cloned in wxPostEvent
wxPostEvent(pBar, event_b);

Catching:

You can catch the posted event either with an event table or with wxEvtHandler::Bind. If you didn't specify the id (though this would work even if you did) the event table would be:

wxBEGIN_EVENT_TABLE(MyFoo, wxFoo))
  EVT_COMMAND(wxID_ANY, MY_NEW_TYPE, MyFoo::OnMyEvent)
  ...
wxEND_EVENT_TABLE()

The specific-id version would be:

wxBEGIN_EVENT_TABLE(MyFoo, wxFoo)
  EVT_COMMAND(foo_one, MY_NEW_TYPE, MyFoo::OnMyFirstEvent)
  EVT_COMMAND(foo_two, MY_NEW_TYPE, MyFoo::OnMySecondEvent)
  ...
wxEND_EVENT_TABLE()

The dynamic equivalents would be:

// If done inside a MyFoo function:
Bind(MY_NEW_TYPE, &MyFoo::OnMyEvent, this);
// or from elsewhere (pFoo is a pointer to a MyFoo instance):
pFoo->Bind(MY_NEW_TYPE, &MyFoo::OnMyEvent, pFoo);
 
// Similarly for specified ids
Bind(MY_NEW_TYPE, &MyFoo::OnMyEvent, this, foo_one);
...
pFoo->Bind(MY_NEW_TYPE, &MyFoo::OnMyEvent, pFoo, foo_two);

If this code is not in the same cpp file as the MY_NEW_TYPE definition, you'll need to declare it here first: wxDECLARE_EVENT(MY_NEW_TYPE, wxCommandEvent);

What could possibly go wrong?

You must define the wxEventType exactly once. If you add wxDEFINE_EVENT(MY_NEW_TYPE, wxCommandEvent); to two files, it will be defined twice with two different values. The same thing will happen if you write it only once, but you put it in a header file that is #included more than once. Why does that matter? Because you'll be posting an event with the type (e.g.) 12000, and trying to catch events with type 12001; which will silently fail.


Subclassing wxCommandEvent

This shows how to create a custom event class which can transport a wxRealPoint (who said there's no real point in custom events? ;) and the alternative ways to make it work.

Subclassing

You may do this in a cpp file, but often you'll use a header.

class MyFooEvent;
wxDECLARE_EVENT(MY_NEW_TYPE, MyFooEvent);
 
class MyFooEvent: public wxCommandEvent
{
public:
	MyFooEvent(wxEventType commandType = MY_NEW_TYPE, int id = 0)
        		:  wxCommandEvent(commandType, id) { }
 
	// You *must* copy here the data to be transported
	MyFooEvent(const MyFooEvent& event)
        		:  wxCommandEvent(event) { this->SetPoint(event.GetPoint()); }
 
	// Required for sending with wxPostEvent()
	wxEvent* Clone() const { return new MyFooEvent(*this); }
 
	wxRealPoint GetPoint() const { return m_RealPoint; }
	void SetPoint(const wxRealPoint& rp) { m_RealPoint = rp; }
 
private:
	wxRealPoint m_RealPoint;
};

Now the macros. If you aren't going to use an event table you can omit the ones marked 'optional'.

typedef void (wxEvtHandler::*MyFooEventFunction)(MyFooEvent &);
 
#define MyFooEventHandler(func) wxEVENT_HANDLER_CAST(MyFooEventFunction, func)                    
 
// Optional: define an event table entry
#define EVT_MYFOO(id, func) \
 	wx__DECLARE_EVT1(MY_NEW_TYPE, id, MyFooEventHandler(func))
 
// Very optionally, you can do a similar #define for EVT_MYFOO_RANGE.
#define EVT_MYFOO_RANGE(id1,id2, func) \
	wx__DECLARE_EVT2(MY_NEW_TYPE, id1, id2, MyFooEventHandler(func))

Posting:

// As before, define a new wxEventType
wxDEFINE_EVENT(MY_NEW_TYPE, MyFooEvent);
 
// Optional, but you'll often want to be able to assign specific IDs
enum { foo_one = 1, foo_two, foo_three };
...
// Create an instance of the event, optionally using a specific id
MyFooEvent event(MY_NEW_TYPE,  foo_one);
 
wxRealPoint rp(3.14159, 2.71828);
// Store the data in the event
event.SetPoint(rp);
 
// Post the event. pDest is a pointer to the destination
wxPostEvent(pDest, event);

Catching:

// Using Bind(). You'll often do this in the destination class constructor:
Bind(MY_NEW_TYPE, &MyFoo::OnMyEvent, this, foo_one); // Version for modern compilers
//Bind(MY_NEW_TYPE, MyFooEventHandler(MyFoo::OnMyEvent), this, foo_one); // Version for compilers new and old
 
// Using an event table
wxBEGIN_EVENT_TABLE(MyFoo, wxFoo)
      EVT_MYFOO(wxID_ANY, MyFoo::OnMyEvent)
// or EVT_MYFOO(foo_one, MyFoo::OnMyEvent)
// or EVT_MYFOO_RANGE(foo_one, foo_three, MyFoo::OnMyEvent)
wxEND_EVENT_TABLE()
 
// The handler:
void MyFoo::OnMyEvent(MyFooEvent& event)
{
    wxString msg = wxString::Format("pi = %f  e = %f", event.GetPoint().x, event.GetPoint().y);
    wxMessageBox(msg);
}

What could possibly go wrong?

In your subclass, you must override wxEvent::Clone and copy the data; otherwise the event will arrive, but it will be empty.

See also:

The 'Events' overview

The 'event' sample

Inter-thread communication