
24 Dec
2004
24 Dec
'04
10:18 a.m.
Christopher D. Russell wrote: > Some random thoughts on GUI libraries: > > Message filtering, dispatch: > > Somewhere buried in Joel de Guzman's Spirit or Phoenix docs I recall mention > of functional programming techniques being useful for working with graphical > user interface library organization. This sounds interesting. I am not sure how it would look, though. > This comment stuck in my head as I > generally dislike the organization of message dispatch and filtering code in > libraries like the MFC and the WTL. I have never liked their approach either. I like the Win32GUI approach and have adapted it into my Boost.GUI library. Windows has 4 types of message flow: the normal processing of messages (e.g. WM_MOVE); processing WM_COMMAND notifications (e.g. menus or button presses); processing WM_NOTIFY notifications (e.g. virtual list data requests); events sent by controls to parents (and possibly reflected back to the original sender). Win32GUI uses a std::map< UINT, handler > to process events and has one for the normal flow and one each for the notification flows; I am not sure how it handles reflection/reflected events. The problem with this is that it has 3 maps for event handling with the notification handlers being Windows-specific. The solution I have devised is to have a single std::map< event_id, handler > that processes all events sent to onevent( event * ). The event_id type is OS/Platform-specific, so for GTK+ it could be std::string. In Windows, this is struct event_id { unsigned int id; unsigned int code; }; where id is the event type (WM_PAINT, etc.) and code is normally 0, but for WM_COMMAND and WM_NOTIFY messages, it is 0 (for generic handlers) and is then set to the notification code (e.g. BN_PRESSED for button presses). The onevent function for Windows will then process reflected messages. The handler is currently defined as boost::signal< bool ( event * ev ) >, such that ev is the OS-specific event structure and the return value indicates is the message has been processed (and processing should be stopped). Since the signal has a non-member signature, you need a make_handler( Object * ob, bool ( Object::* h )( event * ev )) helper that constructs a function object that will call the member function. This is far more powerful than binding specifically to a component member function as it allows: * an application class to respond to close events; * the radio_group class to process a group of radio buttons to keep mutual exclusivity, handling onpressed() events; * layout managers to respond to resize events without you having to add the handlers manually, or write specific code to interact with the layout manager (NOTE: this is currently in code on my machine, and I will post it after Christmas). There is a defect with my code at present: returning true from a handler will stop all event processing. This affects filtering handlers such as radio_group and cascading handlers like layout managers and will result in undefined behaviour. I am thinking of binding a handler-type to the event handler: handler | filter | cascading which dictates how the events are processed. The event processor allows you to attach a handler to a component using: signal_type handler_for( event_id ); for example: frame.handler_for( event::onclose ) .connect( &terminate_gui ); Buttons define a signal_type onpressed() method that is an alias for the OS-specific: handler_for( button-pressed-event ) which in Windows is: handler_for( event_id( WM_COMMAND, BN_PRESSED )) I am also considering returning a proxy class to a signal object to allow for: new_.onpressed() .attach( handler_type::handler, this, &mainfrm::onnew ); cancel.onpressed() .attach( handler_type::handler, &terminate_gui ); > In essence, this is just another way to encode complex state machines. But I > think it would be cool to encode the message filtering and dispatch logic in > EBNF! This sounds cool. Could you give an example? It should be possible to build the state machine logic on top of the event processing outlined above. For example: button quit; quit // handle button presses << state::pressed[ &terminate_gui ] // convert pressing the spacebar into pressing the button << state::keypress( ' ' ).fire( state::pressed ) ; Is this what you are aiming at? I am not sure on the implementability of the above, but it will most likely be built on top of the event processing/binding that is in place by my library. This would be done my constructing the appropriate function objects and the appropriate calls to handler_for. The state machine should allow for successive slots, e.g.: frame // save the state of the application (e.g. position) << state::close[ state::bind( frame, &mainfrm::onsavestate )] // terminate the application << state::close[ &terminate_gui ] ; > Message routing: > > signals / slots? I am using signals/slots for event handling (see above for details). Regards, Reece