More flexible event callbacks

If you have a cool piece of software to share, but you are not hosting it officially yet, please dump it in here. If you have code snippets that are useful, please donate!
Post Reply
pete_b
Knows some wx things
Knows some wx things
Posts: 41
Joined: Fri Jan 05, 2007 9:52 am

More flexible event callbacks

Post by pete_b » Thu Nov 15, 2007 5:17 pm

I've noticed that I tend to have a lot of onSomething(event)methods in my main frame class for handling menus etc that simply delegate to another object. I could change the object to become an event handler but that means it can only be used in wxWidgets code and also enforces a class hierachy that I may not really want.

It would be nice if events could be delegated to classes that do not inherit from wxEvtHandler. It would also be nice to be able to omit the event argument if desired. It would also be nice if we could connect events to free functions and function objects.

I've been playing around with ways to make this possible...

Whether or not this is really of any use will depend upon what the following comment in event.h really means!:

Code: Select all

    // m_callbackUserData is for internal usage only
I can't see m_callbackUserData being used anywhere really? The code appears to work.

It would have been possible to do this more effectively if wxDynamicEventTableEntry had a virtual destructor and maybe an event dispatching virtual function. Clearly this would reduce performance a little in the normal case, but it would make my generic method faster!

This should really be put into separate files, with the EventProxy::instance being put into a single cpp file to prevent multiple instances. Also should really have private copy/assign on the proxy. And a few more comments!

Code: Select all

class EventDelegate : public wxObject
{
public:
	virtual ~EventDelegate() = 0;
	virtual void processEvent(wxEvent& e) = 0;
};
inline EventDelegate::~EventDelegate() {}

template <typename E, typename F>
class EventDelegateImpl1 : public EventDelegate
{
public:
	EventDelegateImpl1(F f) : f_(f) {}
	virtual void processEvent(wxEvent& e) {
		f_(static_cast<E&>(e));
	}
private:
	F f_;
};

// Eats the event when invoked.
template <typename F>
class EventDelegateImpl0eat : public EventDelegate
{
public:
	EventDelegateImpl0eat(F f) : f_(f) {}
	virtual void processEvent(wxEvent& e) {
		f_();
	}
private:
	F f_;
};

// Skips the event when invoked.
template <typename F>
class EventDelegateImpl0skip : public EventDelegate
{
public:
	EventDelegateImpl0skip(F f) : f_(f) {}
	virtual void processEvent(wxEvent& e) {
		e.Skip();
		f_();
	}
private:
	F f_;
};


class EventProxy : public wxEvtHandler
{
public:
	static EventProxy& instance();
	void onEvent(wxEvent& e) {
		static_cast<EventDelegate*>(e.m_callbackUserData)->processEvent(e);
	}
};

EventProxy& 
EventProxy::instance()
{
	static EventProxy i;
	return i;
}

class EventConnection
{
public:
	EventConnection(
		wxEvtHandler* src,
		wxEventType evt_id, 
		wxObject* user, 
		int win_id, 
		int win_id_last
	) 
	: src_(src)
	, evt_id_(evt_id)
	, user_(user)
	, win_id_(win_id)
	, win_id_last_(win_id_last)
	{}

	void disconnect() const
	{
		src_->Disconnect(
			win_id_, 
			win_id_last_, 
			evt_id_, 
			wxObjectEventFunction(&EventProxy::onEvent), 
			user_, 
			&EventProxy::instance()
		);
	}

private:
	wxEvtHandler* src_;
	wxEventType evt_id_; 
	wxObject* user_;
	int win_id_;
	int win_id_last_;
};

template <typename F>
EventConnection ConnectEvent0eat(
	wxEvtHandler* src,
	wxEventType evt_id, 
	F functor, 
	int win_id = wxID_ANY, 
	int win_id_last = wxID_ANY
) {
	std::auto_ptr<wxObject> p(new EventDelegateImpl0eat<F>(functor));
	src->Connect(win_id, win_id_last, evt_id, 
		wxObjectEventFunction(&EventProxy::onEvent), p.get(), &EventProxy::instance());
	wxObject* user = p.release();
	return EventConnection(src, evt_id, user, win_id, win_id_last);
}

template <typename F>
EventConnection ConnectEvent0skip(
	wxEvtHandler* src,
	wxEventType evt_id, 
	F functor, 
	int win_id = wxID_ANY, 
	int win_id_last = wxID_ANY
) {
	std::auto_ptr<wxObject> p(new EventDelegateImpl0skip<F>(functor));
	src->Connect(win_id, win_id_last, evt_id, 
		wxObjectEventFunction(&EventProxy::onEvent), p.get(), &EventProxy::instance());
	wxObject* user = p.release();
	return EventConnection(src, evt_id, user, win_id, win_id_last);
		
}


template <typename E, typename F>
EventConnection ConnectEvent1(
	wxEvtHandler* src,
	wxEventType evt_id, 
	F functor, 
	int win_id = wxID_ANY, 
	int win_id_last = wxID_ANY
) {
	std::auto_ptr<wxObject> p(new EventDelegateImpl1<E, F>(functor));
	src->Connect(win_id, win_id_last, evt_id, 
		wxObjectEventFunction(&EventProxy::onEvent), p.get(), &EventProxy::instance());
	wxObject* user = p.release();
	return EventConnection(src, evt_id, user, win_id, win_id_last);
}

To use the above is fairly simple.
You can also use FastDelegate (google it) to connect to any object method you desire.

Code: Select all


void AStaticFunction()
{
	wxLogMessage(wxT("AStaticFunction()"));
}

void AStaticFunction2(wxCommandEvent& e)
{
	e.Skip();
	wxLogMessage(wxT("AStaticFunction2(event)"));
}

struct AFunctor {
	AFunctor() {}
	void operator()() const {
		wxLogMessage(wxT("AFunctor()"));
	}
};

// In some initialization code:

wxMenuItem* item = ...
ConnectEvent0skip(this, wxEVT_COMMAND_MENU_SELECTED, AFunctor(), item->GetId());
ConnectEvent0skip(this, wxEVT_COMMAND_MENU_SELECTED, &AStaticFunction, item->GetId());
ConnectEvent1<wxCommandEvent>(this, wxEVT_COMMAND_MENU_SELECTED, &AStaticFunction2, item->GetId());

You can also store the return value of the connect functions in EventConnection objects to allow later disconnection if required.
You would need to explicilty disconnect if you destroy the object being delegated to.

Auria
Site Admin
Site Admin
Posts: 6695
Joined: Thu Sep 28, 2006 12:23 am
Contact:

Post by Auria » Thu Nov 15, 2007 7:03 pm

Can't your already redirect events to another object using Connect?

pete_b
Knows some wx things
Knows some wx things
Posts: 41
Joined: Fri Jan 05, 2007 9:52 am

Post by pete_b » Thu Nov 15, 2007 8:23 pm

Auria wrote:Can't your already redirect events to another object using Connect?
Yes you can, but the other object has to inherit from wxEvtHandler.
You cannot use wxEvtHandler::Connect to connect to:
* Free functions
* Function objects
* Member functions of classes that do not inherit from wxEvtHandler

The code above actually uses Connect to accomplish all of the above by having a singleton class that derives wxEvtHandler to accept the events and dispatches them using the user wxObject. (The wxObject is cast to a EventDelegate whose dynamic type erases the type of the callable entity that you are delegating to).

S.Volkenandt
Knows some wx things
Knows some wx things
Posts: 26
Joined: Tue Nov 14, 2006 1:51 pm
Location: Duesseldorf, Germany

Post by S.Volkenandt » Thu Mar 13, 2008 12:18 pm

Additionally, the syntax is more natural modern C++, i.e. &Class::Method instead of wxSomeEventHandler(Class::Method).

Nice approach, I have been close to trying something similar myself. I will try this out and possibly post some more remarks later.

EDIT: I would plead for a more modern event system in wxWidgets 3.0 anyway ;)

S.Volkenandt
Knows some wx things
Knows some wx things
Posts: 26
Joined: Tue Nov 14, 2006 1:51 pm
Location: Duesseldorf, Germany

Flexible Event Callbacks revised

Post by S.Volkenandt » Fri Mar 14, 2008 10:40 am

As I stated, I have evaluated the mechanism and I wanted to reduce the dependencies between wxEvtHandler and the actual handler functions even more. For example, if you handle wxEVT_COMMAND_MENU_SELECTED, there's really no need to specify that the argument is of type wxCommandEvent& in the Connect-call, if you somehow group the wxEventType and corresponding wx*Event class.

Inspired by pete_b's solution, here is what I've come up with. However, I've omitted the possibility to have event handlers that take zero parameters.

SYNOPSIS

Code: Select all

    template< class Event > void wxConnect( unspecified* source, unspecified handler, int id = wxID_ANY, int lastId = wxID_ANY );
    template< class Event > void wxConnect( wxEvtHandler* source, unspecified handler, unspecified* sink, int id = wxID_ANY, int lastId = wxID_ANY );
    template< class Event > void wxConnect( wxEvtHandler* source, unspecified handler, int id = wxID_ANY, int lastId = wxID_ANY );
DESCRIPTION
All event handlers must be free functions, methods or functors taking only one argument, whose type is the appropriate event class for the event determined by the template argument.

The first form connects a method of the source's type to the specified event, being emitted by source.
The second form connects a method of the sink's type to the specified event, being emitted by source.
The third form connects any function or functor to the specified event, being emitted by source.

EXAMPLE
Connect a method to an event of the own instance by ID (Emitter and Receiver both are "this"):

Code: Select all

wxConnect< wxEVENT_UPDATE_UI >( this, &MainWindow::OnUpdateUI, ID_MENU_SAVE );
Connect a method to another widget's event by instance (Emitter is another widget, Receiver is "this":

Code: Select all

wxConnect< wxEVENT_CONTEXT_MENU >( m_listCtrl1, &MainWindow::OnListCtrl1ContextMenu, this );
Connect a freestanding function (or functor) to an event of the own instance (Emitter is "this", receiver might not have an instance at all):

Code: Select all

void HandleContextMenu( wxUpdateUIEvent& args );
wxConnect< wxEVENT_CONTEXT_MENU >( this, &::HandleContextMenu);
Connect a function taking more parameters than appropriate by binding the unwanted ones (using the c++ standards extensions TR1 from 2006):

Code: Select all

wxConnect< wxEVENT_CONTEXT_MENU >( m_listCtrl1, bind( &MainWindow::OnContextMenu, this, m_listCtrl1, _1 ) );
wxConnect< wxEVENT_CONTEXT_MENU >( m_listCtrl2, std::tr1::bind( &MainWindow::OnContextMenu, this, m_listCtrl2, _1 ) );
The latter example calls a method MainWindow::OnContextMenu( wxListCtrl*, wxContextMenuEvent& ) with the this-parameter bound to "this" and the first parameter being either m_listCtrl1 or m_listCtrl2, depending on which one sent the signal.

Here's the code:

Code: Select all

#include <wx/cpp.h>
#include <wx/defs.h>
#include <wx/event.h>

// helper macro to define a structure defining the event argument type and the
// appropriate wxEventType
#define DECLARE_FLEXEVENT_STRUCT( event, args ) \
	struct wxCONCAT( wxEVENT_, event ) \
	{ static wxEventType EventType() { return wxCONCAT( wxEVT_, event ); } \
	  typedef args EventArgs; \
	}

// the actual event structures (yet incomplete)
DECLARE_FLEXEVENT_STRUCT( ACTIVATE              , wxActivateEvent    );
DECLARE_FLEXEVENT_STRUCT( CONTEXT_MENU          , wxContextMenuEvent );
DECLARE_FLEXEVENT_STRUCT( UPDATE_UI             , wxUpdateUIEvent    );
DECLARE_FLEXEVENT_STRUCT( COMMAND_BUTTON_CLICKED, wxCommandEvent     );
DECLARE_FLEXEVENT_STRUCT( COMMAND_MENU_SELECTED , wxCommandEvent     );
DECLARE_FLEXEVENT_STRUCT( COMMAND_TEXT_UPDATED  , wxCommandEvent     );

// trait class that groups the behaviour of one event
template< typename Event >
struct wxEventTraits
{
	static wxEventType EventType() { return Event::EventType(); }
	typedef typename Event::EventArgs EventArgs;
};

// polymorphic type that can process wxWidgets events
struct wxEventDelegate : public wxObject
{
	virtual ~wxEventDelegate() {}
	virtual void ProcessEvent( wxEvent& e ) = 0;
};

// derivation for pointer-to-member types
template< typename Event, typename Sink >
struct wxEventDelegateMember : public wxEventDelegate
{
	typedef wxEventTraits< Event > Traits;
	typedef typename Traits::EventArgs Args;
	typedef void (Sink::*Handler)( Args& );

	wxEventDelegateMember( Sink* sink, Handler handler )
		: m_sink( sink )
		, m_handler( handler ) {}

	virtual void ProcessEvent( wxEvent& e )
	{
		(m_sink->*m_handler)( static_cast< Args& >( e ) );
	}

private:
	Sink* m_sink;
	Handler m_handler;
};

// derivation for arbitrary functors
template< typename Event, typename Handler >
struct wxEventDelegateFunctor : public wxEventDelegate
{
	typedef wxEventTraits< Event > Traits;
	typedef typename Traits::EventArgs Args;

	wxEventDelegateFunctor( Handler handler )
		: m_handler( handler ) {}

	virtual void ProcessEvent( wxEvent& e )
	{
		m_handler( static_cast< Args& >( e ) );
	}

private:
	Handler m_handler;
};

struct wxEventProxy : public wxEvtHandler
{
	static wxEventProxy& Get()
	{
		static wxEventProxy instance;
		return instance;
	}

	void OnEvent( wxEvent& e ) 
	{ 
		wxEventDelegate& delegate = static_cast< wxEventDelegate& >( *e.m_callbackUserData );
		delegate.ProcessEvent( e ); 
	}
};

// form that connects a method of source to an event of source
template< typename Event, typename Source >
void wxConnect( Source* source, void (Source::*handler)( typename wxEventTraits< Event >::EventArgs& ), int firstId = wxID_ANY, int lastId = wxID_ANY )
{
	typedef wxEventTraits< Event > Traits;
	wxEventDelegate* delegate( new wxEventDelegateMember< Event, Source >( source, handler ) );
	source->Connect( firstId, lastId, Traits::EventType(), wxObjectEventFunction( &wxEventProxy::OnEvent ), delegate, &wxEventProxy::Get() );
}

// form that connects a method of sink to an event of source
template< typename Event, typename Sink >
void wxConnect( wxEvtHandler* source, void (Sink::*handler)( typename wxEventTraits< Event >::EventArgs& ), Sink* sink, int firstId = wxID_ANY, int lastId = wxID_ANY )
{
	typedef wxEventTraits< Event > Traits;
	wxEventDelegate* delegate( new wxEventDelegateMember< Event, Sink >( sink, handler ) );
	source->Connect( firstId, lastId, Traits::EventType(), wxObjectEventFunction( &wxEventProxy::OnEvent ), delegate, &wxEventProxy::Get() );
}

// form that connects an arbitrary functor to an event of source
template< typename Event, typename Handler >
void wxConnect( wxEvtHandler* source, Handler handler, int firstId = wxID_ANY, int lastId = wxID_ANY )
{
	typedef wxEventTraits< Event > Traits;
	wxEventDelegate* delegate( new wxEventDelegateFunctor< Event, Handler >( handler ) );
	source->Connect( firstId, lastId, Traits::EventType(), wxObjectEventFunction( &wxEventProxy::OnEvent ), delegate, &wxEventProxy::Get() );
}

pete_b
Knows some wx things
Knows some wx things
Posts: 41
Joined: Fri Jan 05, 2007 9:52 am

Post by pete_b » Mon Mar 17, 2008 10:16 am

Nice one! Not sure I like the macros too much though.

You could of course get rid of the need for either when using member functions, with something along the lines of:

Code: Select all

template <typename U, typename R, typename T, typename E, typename F> 
EventConnection ConnectEvent( 
        wxEvtHandler* src, 
        wxEventType evt_id, 
        U* object,
        R (T::*mfn)(E&), 
        int win_id = wxID_ANY, 
        int win_id_last = wxID_ANY 
) { 
        ...
} 

ConnectEvent(subwin, wxEVT_MOTION, this, &MyWin::HandleMotion);
Would also need an overload to handle const member functions too. And maybe volatile mfn's but thats probably going too far.

For functors, they could follow the std::unary_function guidelines to get the event type.


I've actually been working on another event system that allows you to do the following:

Code: Select all


class MyEvent;

// Defining signals for a class is really simple.  
// All you need to do is define a nested or global structure 
// that derives from class Signal with the function signature
// (will also have SignalX types for older compilers)
// 
// Once you've done this, the rest is handled dynamically.
// So, you'll get much simpler code than when using 
// boost::signals (you don't need a typedef, a member 
// variable AND a function to connect / get the signal).
// You also won't pay for the memory used by signals 
// that are never connected.
// However signal invocation will be slower than 
// with boost::signals due to the lookup based upon signal 
// type.
//
// Hopefully I might be able to make it a bit quicker 
// to compile than boost signals.
//
class Test : public SignalSource
{
public:
   // Signals must be a unique type.
   class SignalFoo : public Signal<void(int, double)> {};
   class SignalMyEvent : public Signal<void(MyEvent)> {};

   void emitFoo(int p1, double p2) const {
       emit_sig<SignalFoo>(p1, p2);
   }

   void emitMyEvent(MyEvent const& e) const {
       emit_sig<SignalMyEvent>(e);
   }

};

class MyEvent : public Event
{
public:
   MyEvent(int value) : value_(value) {}
      int getValue() const { return value_; }

private:
   int value_;
};


void f(int i, double d) {
   std::cout << i << ", " << d << std::endl;
} 

void evt_1(MyEvent& e) {
   std::cout << "evt_1: " << e.getValue() << std::endl;
}

void evt_2(Event& e) {
   std::cout << "evt_2" << std::endl;
   e.consume();
}

void evt_3(MyEvent& e) {
   std::cout << "evt_3: " << e.getValue() << std::endl;
   e.skip();
}

...
   Test t;
   t.connect<Test::SignalFoo>(&f);
   t.emitFoo(1, 2.8);

   t.connect_evt<Test::SignalMyEvent>(&evt_1);
   t.connect_evt<Test::SignalMyEvent>(&evt_2);
   t.connect_evt<Test::SignalMyEvent>(&evt_3);
   MyEvent e(500);
   t.emitMyEvent(e); 
I've still got quite a bit to do on the library (like preventing dangling pointers and offering a threading policy) and I expect I'll be changing the name to something other than 'Signals', since boost already uses it. Not sure I could use the name 'Event' either. Maybe something like the 'Notify' library. I might try and submit it to boost if I have time to really hone the design and do all the documentation etc.

Jorg
Moderator
Moderator
Posts: 3971
Joined: Fri Aug 27, 2004 9:38 pm
Location: Delft, Netherlands
Contact:

Post by Jorg » Mon Mar 17, 2008 11:21 am

This connecting might be a nice thing, but the main reason why wxWidgets didn't adopt them is their unpredictability of order of sending.

For example, an event of a button called EVT_BUTTON will be first sent to the internal event handler of the wxButton. In this case if you derived from the button, you can handle the event yourself. Then because it is a command event, it is sent to the parent. Probably a panel, then to the next parent until it reaches a handler that does handle the button (that's why event.Skip() is invented). Also multiple handlers can handle the same event this way, in an hierarchical matter.

For signals.... If you connect a signal to the button class, and a panel for example that contains the button also connects to the same button class, who is going to enforce the proper order of the button click event? For example if the internal button handler first needs to be called to reflect the internal state of the class that a higher handler (in the panel) needs to check for, things get unpredictable.

Although the solution given looks nice, you must also be aware of what happens under the hood ;-)

- Jorgen
Forensic Software Engineer
Netherlands Forensic Insitute
http://english.forensischinstituut.nl/
-------------------------------------
Jorg's WasteBucket
http://www.xs4all.nl/~jorgb/wb

pete_b
Knows some wx things
Knows some wx things
Posts: 41
Joined: Fri Jan 05, 2007 9:52 am

Post by pete_b » Mon Mar 17, 2008 1:40 pm

This is something I have been considering for my event/signals library. In particular the 'TryParent' bit (which should not be difficult). I'm not really considering its use in conjunction with wxWidgets in particular, its more for a general puropose library.

Currently I always push new connections to the front of the list of handlers. This is exactly the same as the way dynamic event handlers are handled in wxWidgets (there is only one list of handlers). True, you have the static event table which is effectively a list of handlers connected most-derived-first. As such, static event handlers might as well be considered as pre-connected 'fixed' dynamic handlers that you cannot disconnect.

I don't really see your argument about deriving from wxButton vs using Connect? Dynamic events are always called before the static event table. But so is the derived static event table. If you handle the button click in the derived class it will be called before the base class (wxButton) anyway. This is the same problem in unless I have missed something for forwarding on an event to the next handler in its list and then doing something afterward?

e.g.

Code: Select all

void MyHandler(wxEvent& e) 
{
    SuperClassHandler(e);

    // do some more processing here
}
Clearly, you can do this anyway if the super class defines its event handler as public/protected. You're fairly stuffed for options if the handler is private in the parent class though. And if you are handling the event externally you're fairly stuffed if its protected (but you can use some blunt down-casting tricks to get round this).

In fact, it would be quite a nice feature to have a finer degree of control over event handler ordering...

Jorg
Moderator
Moderator
Posts: 3971
Joined: Fri Aug 27, 2004 9:38 pm
Location: Delft, Netherlands
Contact:

Post by Jorg » Mon Mar 17, 2008 3:12 pm

I don't really see your argument about deriving from wxButton vs using Connect? Dynamic events are always called before the static event table. But so is the derived static event table. If you handle the button click in the derived class it will be called before the base class (wxButton) anyway. This is the same problem in unless I have missed something for forwarding on an event to the next handler in its list and then doing something afterward?
Well what I can imagine is that signals can be dynamically connected and disconnected, thus the order can change. Also the button might connect later to the handler then the parent will, which messes up the order.

Consider the following trivial example:

I create a CounterButton (derived from wxButton). The counterbutton connects to it's own handler using Connect() and everytime it gets a press event it counts with one.

The parent also connects to the click event, and displays the count value. For normal Connect and wxEventHandler derived classes this is ok.

It doesn't matter how often you connect or disconnect, the order always stays the same.

Now if you solve this with signal handlers, it really depends on how the order is. if one disconnects and another connects again, the order can get screwed up.

I am aware that if you use wxButton you might as well use the normal connect method available, but in similar or differen conditions when one listener depends on the object in question being updated, and that object updates itself with the same signal mechanism, it really depends which one is first ;-)

I just pointed this out, it was no critisism on your work, I used a similar one, sigslot with much pleasure.

- Jorgen
Forensic Software Engineer
Netherlands Forensic Insitute
http://english.forensischinstituut.nl/
-------------------------------------
Jorg's WasteBucket
http://www.xs4all.nl/~jorgb/wb

pete_b
Knows some wx things
Knows some wx things
Posts: 41
Joined: Fri Jan 05, 2007 9:52 am

Post by pete_b » Mon Mar 17, 2008 4:39 pm

Don't wory - I'm not taking it as negative criticism! I see what you mean in terms of parent chaining. Its clearly beneficial for windowing libraries to support this (and it will be possible with my signal library - it is much more like wxEvent handling):

Code: Select all

   

// Event signature definition is now a global type.
class SignalMyEvent : public Signal<void(MyEvent)> {};

// Derivation from SignalSource is not mandatory but 
// generally convenient.  It is also possible to aggregate
// SignalSource or the Signals class.
//
class Parent : public SignalSource
{
public:     
};

class Test : public SignalSource
{
public:
   Test (SignalSource* parent) 
   : SignalSource(parent)
   {}

   void emitMyEvent(MyEvent const& e) const {
       emit_sig<SignalMyEvent>(e);
   }
};

...
void evt_1(Event& e) {
   std::cout << "evt_1" << std::endl;
   e.skip();
}

void evt_2(MyEvent& e) {
   std::cout << "evt_2: " << e.getValue() << std::endl;
   e.skip();
}

   Parent p;
   Test t(&p);
   t.connect_evt<SignalMyEvent>(&evt_1);
   p.connect_evt<SignalMyEvent>(&evt_2);


Post Reply