Boost logo

Boost :

From: Reece Dunn (msclrhd_at_[hidden])
Date: 2004-12-24 05:18:56


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


Boost list run by bdawes at acm.org, gregod at cs.rpi.edu, cpdaniel at pacbell.net, john at johnmaddock.co.uk