Difference between revisions of "Custom Events in wx2.8 and earlier"

From WxWiki
Jump to navigation Jump to search
(cleaned up and reorganized content)
Line 190: Line 190:
 
</source>
 
</source>
  
== Why are custom events so confusing? ==
+
== Why are custom events so confusing?==
  
 
Two significant reasons are the optional use of macros; and the EVT_CUSTOM macro. I hope this section will make things clearer.
 
Two significant reasons are the optional use of macros; and the EVT_CUSTOM macro. I hope this section will make things clearer.

Revision as of 07:57, 22 April 2008

This article exists in the hope of clearing up any confusion with defining your own custom events for use with the wxWidgets event handlers. The following information is provided to supplement the Event Handling Overview, please read this overview first. Also, it is a good idea to look at the events sample, which has a working custom event.

In addition to this article, you may also find the following articles helpful:

  • Markus Neifer has written a tutorial for use with wxWidgets 2.3, but most of it should still be relevant.
  • HopeSeekr of xMule has composed a tutorial.


Quick User Notes

  • EVT_FOO(-1, SomeClass::SomeFunc) is a purely passive declaration. It says, "If a FOO happens to come my way, let me know about it." It doesn't tell any particular FOO-generating class to notify this class about FOO events. But without this, it will not respond to FOO events at all, even if explicitly sent to an object of this class.
  • wxButton events seem to work automatically because the EVT_BUTTON is generated in the wxButton itself, and since it's a command event it propogates to its parents automatically. Thus, no call to SetEventHandler() or similar is necessary.
  • wxSocket events do not exist in a chain of parents, so SetEventHandler() is necessary. This establishes the connection between an event and a particular instance of an object.
  • Note that there are other strategies for the event dispatching as well, except the first 2 already mentioned above and which I'm going to repeat for completeness:
    • Send the event to oneself (possibly relying on it to be propagated to the parent).
    • Send the event to another event handler explicitly associated with us.
    • Send the event to all top level windows (possibly relying on them to propagate the event *downwards* to their children recursively; example: EVT_IDLE).
    • Send the event to wxTheApp only.
  • In sub-classing wxEvent, remember to associate your event (wxFooEvent) to your event type (wxEVT_FOO) in the constructor:
wxFooEvent( int id = 0 )
: wxEvent (id, wxEVT_FOO)
{
	...
}
  • To get a unique wxEVT_FOO, use:
wxEVT_FOO = wxNewEventType();

wxPostEvent vs. wxEvtHandler::ProcessEvent

Both can be used when the event handler object is known at compile time.

wxEvtHandler::ProcessEvent(wxEvent &event)

ProcessEvent will call the event handler directly and the event will be processed serially like a function call.

wxPostEvent(wxEvtHandler *evtHandler, wxEvent &event)

wxPostEvent will put the event in the event queue of the wxEvtHandler object but the event will not be processed immediately. Note that the event queue can be flushed by calling wxApp::Yield or wxYieldIfNeeded.

Creating a Custom Event (Method 1)

Example of custom event creation managing a click (by Marco Cavallini)

const wxEventType newEVT_MYCLASS_CLICK = wxNewEventType();

#define EVT_MYCLASS_CLICK(id, fn)                                 \
	DECLARE_EVENT_TABLE_ENTRY( newEVT_MYCLASS_CLICK, id, -1,  \
	(wxObjectEventFunction) (wxEventFunction)                 \
	(wxCommandEventFunction) & fn, (wxObject*) NULL ),

In the class you have to manage the event as you like and send your custom one to the event table:

void Myclass::OnMouse( wxMouseEvent & event )
{
	if ( event.LeftIsDown() )
	{
		wxCommandEvent event( newEVT_MYCLASS_CLICK, GetId() );
		event.SetEventObject(this);
		ProcessCommand(event);
	}
}

Creating a Custom Event (Method 2)

This was tested under wxWidgets 2.4.2 (GTK) and seems to be less complicated than the other methods here. It was derived from the event.cpp example, and uses a wxCommandEvent to pass data.

First, the declaration (needed to scope the event) and then the definition:

BEGIN_DECLARE_EVENT_TYPES()
	DECLARE_LOCAL_EVENT_TYPE( myEVT_SOMETHINGHAPPENED, wxNewEventType() )
END_DECLARE_EVENT_TYPES()

DEFINE_LOCAL_EVENT_TYPE( myEVT_SOMETHINGHAPPENED )

The event type isn't used by the macro. 7777 appears to be an ad-hoc standard: any value works but unique is "better". My spurious wxNewEventType() argument is almost certainly incorrect because it's in the declaration not the definition. In my code the declaration is only hit once, so that's okay.

Then in your event table (in this case, based on a wxControl)...

BEGIN_EVENT_TABLE( myFunkyObject, wxControl )
	EVT_CUSTOM( myEVT_SOMETHINGHAPPENED, wxID_ANY, myFunkyObject::OnSomethingHappened )
END_EVENT_TABLE()

myFunkyObject::OnSomethingHappened( wxCommandEvent &event )
{
	...
}

I'm not sure what the id field in EVT_CUSTOM is for (it's the starting id number) but -1 (wxID_ANY) seems to work fine.

Then, to make something happen with your event from another object, do this:

myDifferentObject::makesomethinghappen(int what)
{
	wxCommandEvent event( myEVT_SOMETHINGHAPPENED );
	event.SetInt(what);
	wxPostEvent( ptr_to_a_myFunkyObject_chain, event );
}

(D.F. Smith, Aug 2004)

Creating a Custom Event (Method 3)

You need to declare your event type first:

DECLARE_EVENT_TYPE(wxCUSTOM_EVENT, 7777)
DEFINE_EVENT_TYPE(wxCUSTOM_EVENT)

The second argument to DECLARE_EVENT_TYPE is only needed in wxWidgets < 2.4.0, and it's your event id there which must be unique. As of 2.4.0, event ID's are dynamically allocated at runtime so theres no conflicts.

Next, you'll want an event table macro for your event. This one can be pretty funky:

#define EVT_CUSTOM_EVENT(fn)                                             \
	DECLARE_EVENT_TABLE_ENTRY( wxCUSTOM_EVENT, wxID_ANY, wxID_ANY,   \
	(wxObjectEventFunction)(wxEventFunction)&fn, (wxObject*) NULL ),

This is a declaration that doesn't want an ID, and it's for wxEvent-derived events. You'll need to add a cast to wxCommandEvent for command events, and add arguments for events that have IDs.

TODO: provide an example

Finally, implement your event itself. The things to remember are:

  • Set the m_eventType param with your new event type in the constructor.
  • Implement a virtual Clone() method:
virtual wxEvent *Clone() const { return new wxCustomEvent(*this); }
  • Implement a copy constructor.
  • Be sure to use the DECLARE_DYNAMIC_CLASS and IMPLEMENT_DYNAMIC_CLASS macros.

A quick custom event example:

DECLARE_EVENT_TYPE(wxCUSTOM_EVENT, 7777)

#define EVT_CUSTOM_EVENT(fn)                                             \
	DECLARE_EVENT_TABLE_ENTRY( wxCUSTOM_EVENT, wxID_ANY, wxID_ANY,   \
	(wxObjectEventFunction)(wxEventFunction)&fn, (wxObject*) NULL ),

class wxCustomEvent : public wxEvent
{
	wxCustomEvent();
	wxCustomEvent( const wxCustomEvent &event );

	virtual wxEvent *Clone() const
		{ return new wxCustomEvent(*this); };

	DECLARE_DYNAMIC_CLASS(wxCustomEvent)
};
DEFINE_EVENT_TYPE( wxCUSTOM_EVENT );
IMPLEMENT_DYNAMIC_CLASS( wxCustomEvent, wxEvent )

wxCustomEvent::wxCustomEvent() : m_eventType(wxCUSTOM_EVENT) { }

wxCustomEvent::wxCustomEvent( const wxCustomEvent &event )
{
	// not really needed in this sample, but it's boring to have it empty
	this->m_eventType = event.m_eventType;
}

Using your new class:

DECLARE_EVENT_TABLE( myForm, wxForm )
	EVT_CUSTOM_EVENT( myForm::HandleCustomEvent )
END_EVENT_TABLE()

Why are custom events so confusing?

Two significant reasons are the optional use of macros; and the EVT_CUSTOM macro. I hope this section will make things clearer.

- by David Hart (29.11.06) wxGTK 2.6.3

Explanation of Macros

Part of the problem is that some examples use macros, and some don't. So there seem to be several different ways to do something, when in fact they are identical. Here is a list of the important equivalents.

  • DECLARE_EVENT_TYPE( MyFooCommandEvent, wxID_ANY )
is the same as
extern expdecl const wxEventType MyFooCommandEvent;
  • DEFINE_EVENT_TYPE( MyFooCommandEvent )
is the same as
const wxEventType MyFooCommandEvent = wxNewEventType();
  • BEGIN_DECLARE_EVENT_TYPES() and END_DECLARE_EVENT_TYPES()
used to do something. Starting from wxWidgets 2.4 they are empty!

It's not a macro, but since wxWidgets 2.4

  • wxNewEventType()
is used instead of doing things like
const newEVT_MYCLASS_FIRST = wxEVT_FIRST + 5400;

There is also EVT_CUSTOM (and EVT_CUSTOM_RANGE). More about this below, but for the record

  • EVT_CUSTOM(eventtype, id, func)
becomes wx__DECLARE_EVT1(eventtype, id, wxEventHandler(func))
which becomes
DECLARE_EVENT_TABLE_ENTRY(eventtype, id, wxID_ANY, func, NULL),

Creating a Custom Event (Method 4)

This shows how to create a custom event class, which carries a trivial amount of data, and the alternative ways to make it work.

// Could have been DECLARE_EVENT_TYPE( MyFooCommandEvent, -1 )
// Not needed if you only use the event-type in one .cpp file
extern expdecl const wxEventType MyFooCommandEvent;

// An exciting custom event that transports a whole wxString!
class MyFooEvent: public wxCommandEvent
{
public:
	MyFooEvent( wxEventType commandType = MyFooCommandEvent, int id = 0 )
	:  wxCommandEvent(commandType, id) { }

	// You *must* copy here the data to be transported
	MyFooEvent( const MyFooEvent &event )
	:  wxCommandEvent(event) { this->SetText( event.GetText() ); }

	// Required for sending with wxPostEvent()
	wxEvent* Clone() const { return new MyFooEvent(*this); }

	wxString GetText() const { return m_Text; }
	void SetText( const wxString& text ) { m_Text = text; }

private:
	wxString m_Text;
};

typedef void (wxEvtHandler::*MyFooEventFunction)(MyFooEvent &);

// This #define simplifies the one below, and makes the syntax less
// ugly if you want to use Connect() instead of an event table
#define MyFooEventHandler(func)                                           \
	(wxObjectEventFunction)(wxEventFunction)(wxCommandEventFunction)  \
	wxStaticCastEvent(MyFooEventFunction, &func)

// Define the event table entry. Yes, it really *does* end in a comma
#define EVT_MYFOO(id, fn)                                            \
	DECLARE_EVENT_TABLE_ENTRY( MyFooCommandEvent, id, wxID_ANY,  \
	(wxObjectEventFunction)(wxEventFunction)                     \
	(wxCommandEventFunction) wxStaticCastEvent(                  \
	MyFooEventFunction, &fn ), (wxObject*) NULL ),

// Optionally, you can do a similar #define for EVT_MYFOO_RANGE
#define EVT_MYFOO_RANGE(id1,id2, fn)                                 \
	DECLARE_EVENT_TABLE_ENTRY( MyFooCommandEvent, id1, id2,      \
	MyFooEventHandler(fn), (wxObject*) NULL ),

// If you want to use the custom event to send more than one sort
// of data, or to more than one place, make it easier by providing
// named IDs in an enumeration.
enum { Foo_DoFirstThing = 1, Foo_DoSecondThing, Foo_DoThirdThing };
// Could have been DEFINE_EVENT_TYPE( MyFooCommandEvent )
const wxEventType MyFooCommandEvent = wxNewEventType();

// Usage:

// To Send the event

...
MyFooEvent event( MyFooCommandEvent, Foo_DoFirstThing );
wxString bar( wxT("This is a Foo_DoFirstThing event") );
// Add the exciting data. In real life you can put anything you like
// into the class: ints, structs, binary data...
event.SetText( bar );
wxPostEvent( panel, event );
...

// To receive the event either use an event table like so:

BEGIN_EVENT_TABLE( MyDestination, wxDestination )
      EVT_MYFOO( wxID_ANY, MyDestination::DoSomething )
// or EVT_MYFOO_RANGE( Foo_DoFirstThing, Foo_DoThirdThing, MyDestination::DoSomething )
// or EVT_MYFOO( Foo_DoFirstThing, MyDestination::DoFirstThing)
// or EVT_MYFOO( Foo_DoSecondThing, MyDestination::DoSecondThing)
// or EVT_MYFOO( Foo_DoThirdThing, MyDestination::DoThirdThing)
END_EVENT_TABLE()

// Or use Connect(), you'd probably do this in the MyDestination constructor
Connect( wxID_ANY, MyFooCommandEvent,
	MyFooEventHandler(MyDestination::DoSomething), NULL, this );

// To handle the event:

void MyDestination::DoSomething( MyFooEvent &event )
{
	switch( event.GetId() )
	{
	case Foo_DoFirstThing:
		wxLogDebug( event.GetText() ); break;
	case Foo_DoSecondThing:
		/* Do something different */ break;
	//  ...
	}
}

The MyFooEvent class could have been made dynamic, but for me it works without. YMMV of course.

The EVT_CUSTOM Macro Method

A different approach is to use the EVT_CUSTOM macro. This has the distinct advantage that you don't need the other, complicated macro stuff. Just make your new event type (and declare it too if necessary):

const wxEventType MyFooCommandEvent = wxNewEventType();

Then create your MyFooEvent class. All you need to do from there is use EVT_CUSTOM directly in the event table:

BEGIN_EVENT_TABLE( MyDestination,wxDestination )
	EVT_CUSTOM( MyFooCommandEvent, wxID_ANY, MyDestination::DoSomething )
END_EVENT_TABLE()

Then send and receive as before.

Much simpler, so why not do it all the time? Well, I don't think you can do this with Connect(). More importantly, the macro expects MyDestination::DoSomething to take a wxEvent& parameter, not a wxFooEvent&, so you'd have to change the function to:

void MyDestination::DoSomething( wxEvent& event )

This means that whenever you want to access the data in the event, you would need to cast. So the switch statement would need to be:

case Foo_DoFirstThing: wxLogDebug( ((MyFooEvent&)event).GetText() ); break;