Page 1 of 1

handling control events (incl. mouse) in parent window

Posted: Sun Feb 05, 2006 5:32 pm
by finn
aloha,

i wanna share a reusable, minimal effort solution for handling any control events in event handler functions of the control's parent window (including mouse events). i will show the specific solution and then explain how to use it in general.

being a newbie to wxWidgets, playing around with it for two weeks, i wanted a solution for :

- a more graphical UI design displaying images instead of regular controls
- all event handling done in member functions of the controls' parent window class. this avoids creating and maintaining a growing number of derived custom control classes with non-reusable application-specific event handlers.


the only solution i found for displaying images without any default behaviour was wxStaticBitmap, which does not offer an event propagating to the parent window after clicking on the image. so the question was, how to capture mouse click events for a wxStaticBitmap control in the control's parent window. the wxWidgets documentation says in "Event handling overview - How events are processed" :

... if ... the event is set to ... propagate (in the library only wxCommandEvent based events are set to propagate), ProcessEvent is recursively applied to the parent window's event handler. ... To put it a different way, events set to propagate (See: wxEvent::ShouldPropagate) (most likely derived either directly or indirectly from wxCommandEvent) will travel up the containment hierarchy from child to parent ...

this means, there is no way to capture a wxMouseEvent of any child control in the parent window because the only propagating event class is wxCommandEvent. but what, if a class derived from wxStaticBitmap captures the desired mouse events and passes them to the parent window through calling the parent window's ProcessEvent() ?

the following is extracted from the implementation of the derived class (don't forget to derive the class from public wxStaticBitmap and DECLARE_EVENT_TABLE() in the class' header file) :

Code: Select all

    IMPLEMENT_CLASS( wxAloCommandBitmap, wxStaticBitmap )

    // event table for overriding standard event handler.
    BEGIN_EVENT_TABLE( wxAloCommandBitmap, wxStaticBitmap )
        EVT_LEFT_DOWN( wxAloCommandBitmap::OnLeftClick )
    END_EVENT_TABLE()

    // event handler.
    void wxAloCommandBitmap::OnLeftClick( wxMouseEvent& event ) {
        event.Skip();
    
        // send event to the parent window's event handlers.
        wxWindow* pwinParent = this->GetParent();
        if( pwinParent ) { pwinParent->ProcessEvent( event ); }	
    }
YES :o) ! it works. there is only one problem left : event table macros in the parent window's event table usually have two parameters. the first parameter is the window id identifying the child control which did send the event. the second parameter is the event handling member function. the wxWidgets documentation about "wxMouseEvent - event table macros" shows that no event table macro for capturing wxMouseEvent offers the parameter for the window id.

in other words, the event table will only see the event but not where it came from. for all events of this type (here: EVT_LEFT_DOWN) the same event handling member function will be called. this problem is easy to solve : wxEvent.GetId() returns the window id of the sender of an event. we can use that to handle the event depending from which window or control it was sent.

the following is extracted from the implementation of the parent window's class (don't forget to define the control's window id and DECLARE_EVENT_TABLE() in the class' header file) :

Code: Select all

    BEGIN_EVENT_TABLE( AppFrame, wxFrame )
        EVT_LEFT_DOWN( AppFrame::OnLeftDown )
    END_EVENT_TABLE()

    void AppFrame::CreateControls() {
        . . .
        mbmpFolders = new wxAloCommandBitmap( itemFrame1, ID_CMDSUBFOLDERS, wxBitmap( ButtonSubfolders_xpm ), wxDefaultPosition, wxSize(42, 42), wxNO_BORDER );
        msizTop->Add( mbmpFolders, 0, wxALIGN_LEFT, 0 );
        . . .
    }    

    void AppFrame::OnLeftDown( wxMouseEvent& event ) {
        switch( event.GetId() ) {
        case ID_CMDSUBFOLDERS : 
            wxMessageBox( "ID_CMDSUBFOLDERS" ); 
            break;
        default :
            break;
        }

        event.Skip();
    }
how to use this in general :

- control class : derive from any wxWidget's control class we like to catch events.
- control class : add an entry and an event handler member function for each event of the control we want to handle. pass on the event by calling the control's parent window's ProcessEvent().
- parent window : add an entry and an event handler member function for each event we want to handle.
- parent window : when creating the control, make sure to pass the window id of the parent window to the control's constructor.
- parent window : depending on the type of event it might be necessary to add a switch case to the event handling function to explore, which control did send the event.

that's it. hope it is useful.

Posted: Wed Feb 08, 2006 8:30 pm
by Edwin
Hello finn,

Thanks for the clear explanation of your solution.
I was playing around with the same kind of problem.
When using a wxGLCanvas I wanted the key and mouse events also to propagate to a parent controller.
My solution was to capture the events in my OpenGLPanel-class (derived from wxGLCanvas) and than do the following trick to force propagation.

Code: Select all

BEGIN_EVENT_TABLE (OpenGLPanel, wxGLCanvas)
    EVT_KEY_DOWN (OpenGLPanel::ForceKeyPropagation)
    EVT_KEY_UP (OpenGLPanel::ForceKeyPropagation)
                    :
END_EVENT_TABLE ()

void OpenGLPanel::ForceKeyPropagation (wxKeyEvent &event)
{   // and here we send the event down through the parents
    event.ResumePropagation (999); // i don't really like the 999
    event.Skip (); // continue the event             
}
This works nicely too, but I don't like the '999' which is a countdown for the internal propagation-counter. (btw '-1' instead of '999' didn't work as it raises an assert)
Your solution of 'pwinParent->ProcessEvent( event )' seems kinder, and less of a hack! :)
Now I'm trying to put all this into a ForcedPropagationEventHandler (derived from wxEventHandler) which can be added to any control via PushEventHandler.
But I'm not really happy with this construction.
My ultimate (?) solution would be something in the line of the 'Model-View-Controller' design pattern (and this would be in the 'Controller-View' part)
I'm now reading a book about design patterns and think it's the 'Holy Grail' :wink:
I was also reading something about 'signals and slots' in this forum, maybe that worth investigating too...
Anyway thanks for your code!

Bye,
Edwin

Posted: Wed Feb 08, 2006 10:10 pm
by finn
Hello Edwin,

beautiful solution ! more streamlined than mine. if you don't mind, i'll use it in my code.

for avoiding the 999 "hack" : the documentation for wxEvent::m_propagationLevel mentions a constant wxEVENT_PROPAGATE_MAX. did you try that ?

Posted: Thu Feb 09, 2006 1:04 pm
by Edwin
Hi finn,

I actually refered to the manual a couple of times, looking up the Propagation-functions but I seem to have totally missed the information at wxEvent::m_propagationLevel!? :oops:
Now I feel much better about the this hack.
Thank's for pointing it out, and may the code be with you!

cu,
Edwin

Posted: Thu Feb 09, 2006 11:38 pm
by finn
aloha Edwin,

you mentioned writing a wxEventhandler derived class to add that to any control and forcing the event propagation from that control. again a great idea. i needed to check that out and doing so, i found another solution :

every wxWindow is derived from wxEvtHandler, meaning every wxWindow can be the event handler for any other window including the windows of it's child controls.

to stay with the static bitmap example, the solution would be :

Code: Select all

BEGIN_EVENT_TABLE( AppFrame, wxFrame )
    EVT_LEFT_DOWN( AppFrame::OnLeftDown )
END_EVENT_TABLE()


bool AppFrame::CreateControls()
{
    . . .
    m_bmpFiles = new wxStaticBitmap( this, ID_CMDFILES, . . . );
    . . .
    // the frame handles the child controls' events.
    m_bmpFiles->SetEventHandler( this );
    . . .
}

void AppFrame::OnLeftDown( wxMouseEvent& event )
{
    // depending on the clicked control's window id. 
    switch( event.GetId() ) {
        . . .
        case ID_CMDFILES : {
            . . .
            break; 
        }
        . . .
    }

    event.Skip();
}
at first, i tried PushEventHandler( this ) instead of SetEventHandler( this ), which did not work. the application starts and immediately terminates. probably a crash.

as far as i understand, using SetEventHandler has a drawback : it sets the only event handler for a window, which then has to handle all the events for this window. to me that's fine, it's what i wanted here. but maybe it is not always the best solution.

Posted: Fri Feb 10, 2006 8:40 am
by Edwin
G'day finn,

I think the fact that every wxWindow is also derived from wxEvtHandler, has more to do with the ease of using this object and sending events to it within the widgets library and from other code.
And that it's not supposed to be 'tempered' with by reassigning it to another control :)
My guess of what is going on when you tried to push it, is some kind of recursive event calling!?
But doesn't it also reroute systemevents (create, sizing,...) for the object, and doesn't that get messed up in the parent?

Anyway, I worked out the simple PropagationHandler. I think it is a robust way to do the trick.
I even put in flags for controlling what to propagate, and replaced the macro-eventtable with the dynamic way of eventhandling.
The only thing I don't like is the full list of events which need to be added.
Is there a way to identify all key/mouse events?

Code: Select all

class PropagationHandler : public wxEvtHandler
{
public:
    enum PropFlags 
    {   // warning: these are meant as bitflags not incremental integer values
        Key_Flag        = 0x0001, // propagate all key events
        Mouse_Flag      = 0x0002, // propagate all mouse events
        SomeOther_Flag  = 0x0004, // propagate all other events
        All_Flags       = 0xFFFF  // propagate everything
    };
public:
    PropagationHandler (PropFlags propwhat = All_Flags)
    {
        if (propwhat & Key_Flag)
        {   // add key events to propagate
            Connect (wxEVT_KEY_DOWN, wxObjectEventFunction (&PropagationHandler::PropagateEvent));
            Connect (wxEVT_KEY_UP, wxObjectEventFunction (&PropagationHandler::PropagateEvent));
            // add char events to propagate (is this usefull?)
            Connect (wxEVT_CHAR, wxObjectEventFunction (&PropagationHandler::PropagateEvent));
        }
        if (propwhat & Mouse_Flag)
        {   // add mouse events to propagate
            Connect (wxEVT_ENTER_WINDOW, wxObjectEventFunction (&PropagationHandler::PropagateEvent));
            Connect (wxEVT_LEAVE_WINDOW, wxObjectEventFunction (&PropagationHandler::PropagateEvent)); 
            Connect (wxEVT_LEFT_DOWN, wxObjectEventFunction (&PropagationHandler::PropagateEvent)); 
            Connect (wxEVT_LEFT_UP, wxObjectEventFunction (&PropagationHandler::PropagateEvent)); 
            Connect (wxEVT_LEFT_DCLICK, wxObjectEventFunction (&PropagationHandler::PropagateEvent)); 
            Connect (wxEVT_MIDDLE_DOWN, wxObjectEventFunction (&PropagationHandler::PropagateEvent)); 
            Connect (wxEVT_MIDDLE_UP, wxObjectEventFunction (&PropagationHandler::PropagateEvent)); 
            Connect (wxEVT_MIDDLE_DCLICK, wxObjectEventFunction (&PropagationHandler::PropagateEvent)); 
            Connect (wxEVT_RIGHT_DOWN, wxObjectEventFunction (&PropagationHandler::PropagateEvent)); 
            Connect (wxEVT_RIGHT_UP, wxObjectEventFunction (&PropagationHandler::PropagateEvent)); 
            Connect (wxEVT_RIGHT_DCLICK, wxObjectEventFunction (&PropagationHandler::PropagateEvent)); 
            Connect (wxEVT_MOTION, wxObjectEventFunction (&PropagationHandler::PropagateEvent)); 
            Connect (wxEVT_MOUSEWHEEL, wxObjectEventFunction         (&PropagationHandler::PropagateEvent)); 
        }
        if (propwhat & SomeOther_Flag)
        {   // is there something else?
        }
    }
private:
    void PropagateEvent (wxEvent &event)
    {   // set events propagationlevel to run down through the parents
        event.ResumePropagation (wxEVENT_PROPAGATE_MAX);
        event.Skip (); // continue the event             
    }
};

// This is the object that wants to propagate his events
OpenGLPanel::OpenGLPanel(wxWindow *parent, float eyeoffset, wxWindowID id, const wxPoint &position, const wxSize& size, long style) :
    wxGLCanvas (parent, id, position, size, style|wxWANTS_CHARS)
{
    // inject the propagation handler into this object
    PushEventHandler (new PropagationHandler (PropagationHandler::Key_Flag));
}
Later,
Edwin