fix wxWindow::PushEventHandler and related wxWindow functions for the stack management; currently they don't work well when passing event handlers which are part of an event handler chain (see wx-dev thread 'wxWindow event handler stack'); implement wxEvtHandler Unlink() and IsUnlinked() functions and document them; revise docs of all involved functions of both wxEvtHandler and wxWindow, adding images for better explanations

git-svn-id: https://svn.wxwidgets.org/svn/wx/wxWidgets/trunk@58291 c3d73ce0-8a6f-49c7-b76d-6d57e0e08775
This commit is contained in:
Francesco Montorsi 2009-01-22 11:53:09 +00:00
parent 47009083ce
commit 7f853dd046
16 changed files with 303 additions and 119 deletions

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

View File

@ -184,7 +184,7 @@ in the previous section. However the similarities end here and both the syntax
and the possibilities of handling events in this way are rather different.
Let us start by looking at the syntax: the first obvious difference is that you
need not use @c DECLARE_EVENT_TABLE() nor @c BEGIN_EVENT_TABLE and the
need not use DECLARE_EVENT_TABLE() nor BEGIN_EVENT_TABLE() and the
associated macros. Instead, in any place in your code, but usually in
the code of the class defining the handler itself (and definitely not in the
global scope as with the event tables), call its Connect() method like this:
@ -331,10 +331,20 @@ doesn't count as having handled the event and the search continues):
<li value="5">
The event is passed to the next event handler, if any, in the event handler
chain, i.e., the steps (1) to (4) are done for it. This chain can be formed
using wxEvtHandler::SetNextHandler() or wxWindow::PushEventHandler() but
usually there is no next event handler and chaining event handlers using
these functions is much less useful now that Connect() exists so this step
will almost never do anything.
using wxEvtHandler::SetNextHandler():
@image html overview_eventhandling_chain.png
(referring to the image, if @c A->ProcessEvent is called and it doesn't handle
the event, @c B->ProcessEvent will be called and so on...).
In the case of wxWindow you can build a stack (implemented using wxEvtHandler
double-linked list) using wxWindow::PushEventHandler():
@image html overview_eventhandling_winstack.png
(referring to the image, if @c W->ProcessEvent is called, it immediately calls
@c A->ProcessEvent; if nor @c A nor @c B handle the event, then the wxWindow
itself is used - i.e. the dynamically connected event handlers and static
event table entries of wxWindow are looked as the last possibility, after
all pushed event handlers were tested).
Note however that usually there are no wxEvtHandler chains nor wxWindows stacks
so this step will usually do anything.
</li>
<li value="6">
@ -349,7 +359,7 @@ doesn't count as having handled the event and the search continues):
<li value="7">
Finally, i.e., if the event is still not processed, the wxApp object itself
gets a last chance to process it.
(which derives from wxEvtHandler) gets a last chance to process it.
</li>
</ol>

View File

@ -283,7 +283,7 @@ HR {
}
.memproto, .memdoc {
border: 1px solid #84b0c7;
border: 1px solid #84b0c7;
}
.memproto {
@ -400,6 +400,10 @@ H2 > A.anchor {
color: black;
}
IMG {
margin: 20px;
}
IMG.logo {
float: right;
margin: 20px;

View File

@ -2668,14 +2668,27 @@ public:
wxEvtHandler();
virtual ~wxEvtHandler();
// Event handler chain
// -------------------
wxEvtHandler *GetNextHandler() const { return m_nextHandler; }
wxEvtHandler *GetPreviousHandler() const { return m_previousHandler; }
void SetNextHandler(wxEvtHandler *handler) { m_nextHandler = handler; }
void SetPreviousHandler(wxEvtHandler *handler) { m_previousHandler = handler; }
virtual void SetNextHandler(wxEvtHandler *handler) { m_nextHandler = handler; }
virtual void SetPreviousHandler(wxEvtHandler *handler) { m_previousHandler = handler; }
void SetEvtHandlerEnabled(bool enabled) { m_enabled = enabled; }
bool GetEvtHandlerEnabled() const { return m_enabled; }
void Unlink();
bool IsUnlinked() const;
// Event queuing and processing
// ----------------------------
// Process an event right now: this can only be called from the main
// thread, use QueueEvent() for scheduling the events for
// processing from other threads.
@ -2686,6 +2699,7 @@ public:
// when called from C code (e.g. in GTK+ callback) when the exception
// wouldn't correctly propagate to wxEventLoop.
bool SafelyProcessEvent(wxEvent& event);
// NOTE: uses ProcessEvent()
// Schedule the given event to be processed later. It takes ownership of
// the event pointer, i.e. it will be deleted later. This is safe to call
@ -2708,11 +2722,17 @@ public:
}
void ProcessPendingEvents();
// NOTE: uses ProcessEvent()
#if wxUSE_THREADS
bool ProcessThreadEvent(const wxEvent& event);
// NOTE: uses AddPendingEvent()
#endif
// Connecting and disconnecting
// ----------------------------
// Dynamic association of a member function handler with the event handler,
// winid and event type
void Connect(int winid,

View File

@ -792,7 +792,7 @@ public:
// replace the event handler (allows to completely subclass the
// window)
void SetEventHandler( wxEvtHandler *handler ) { m_eventHandler = handler; }
void SetEventHandler( wxEvtHandler *handler );
// push/pop event handler: allows to chain a custom event handler to
// alreasy existing ones
@ -806,13 +806,17 @@ public:
// be there)
bool RemoveEventHandler(wxEvtHandler *handler);
// Process an event by calling GetEventHandler()->ProcessEvent() and
// handling any exceptions thrown by event handlers. It's mostly useful
// when processing wx events when called from C code (e.g. in GTK+
// callback) when the exception wouldn't correctly propagate to
// wxEventLoop.
// Process an event by calling GetEventHandler()->ProcessEvent() and
// handling any exceptions thrown by event handlers. It's mostly useful
// when processing wx events when called from C code (e.g. in GTK+
// callback) when the exception wouldn't correctly propagate to
// wxEventLoop.
bool HandleWindowEvent(wxEvent& event) const;
// disable wxEvtHandler double-linked list mechanism:
virtual void SetNextHandler(wxEvtHandler *handler);
virtual void SetPreviousHandler(wxEvtHandler *handler);
// validators
// ----------

View File

@ -258,7 +258,7 @@ public:
wxWindow is (and therefore all window classes are) derived from this class.
When events are received, wxEvtHandler invokes the method listed in the
event table using itself as the object. When using multiple inheritance
event table using itself as the object. When using multiple inheritance
<b>it is imperative that the wxEvtHandler(-derived) class is the first
class inherited</b> such that the @c this pointer for the overall object
will be identical to the @c this pointer of the wxEvtHandler portion.
@ -279,8 +279,8 @@ public:
/**
Destructor.
If the handler is part of a chain, the destructor will unlink itself and
restore the previous and next handlers so that they point to each other.
If the handler is part of a chain, the destructor will unlink itself
(see Unlink()).
*/
virtual ~wxEvtHandler();
@ -382,17 +382,23 @@ public:
The normal order of event table searching is as follows:
-# If the object is disabled (via a call to wxEvtHandler::SetEvtHandlerEnabled)
the function skips to step (6).
the function skips to step (6).
-# If the object is a wxWindow, ProcessEvent() is recursively called on the
window's wxValidator. If this returns @true, the function exits.
window's wxValidator. If this returns @true, the function exits.
-# SearchEventTable() is called for this event handler. If this fails, the base
class table is tried, and so on until no more tables exist or an appropriate
function was found, in which case the function exits.
class table is tried, and so on until no more tables exist or an appropriate
function was found, in which case the function exits.
-# The search is applied down the entire chain of event handlers (usually the
chain has a length of one). If this succeeds, the function exits.
chain has a length of one). This chain can be formed using wxEvtHandler::SetNextHandler():
@image html overview_eventhandling_chain.png
(referring to the image, if @c A->ProcessEvent is called and it doesn't handle
the event, @c B->ProcessEvent will be called and so on...).
Note that in the case of wxWindow you can build a stack of event handlers
(see wxWindow::PushEventHandler() for more info).
If any of the handlers of the chain return @true, the function exits.
-# If the object is a wxWindow and the event is a wxCommandEvent, ProcessEvent()
is recursively applied to the parent window's event handler.
If this returns true, the function exits.
is recursively applied to the parent window's event handler.
If this returns @true, the function exits.
-# Finally, ProcessEvent() is called on the wxApp object.
@param event
@ -620,7 +626,10 @@ public:
/**
@name Event handler chain
@name Event handler chaining
wxEvtHandler can be arranged in a double-linked list of handlers
which is automatically iterated by ProcessEvent() if needed.
*/
//@{
@ -664,21 +673,60 @@ public:
/**
Sets the pointer to the next handler.
@param handler
Event handler to be set as the next handler.
@remarks
See ProcessEvent() for more info about how the chains of event handlers
are internally used.
Also remember that wxEvtHandler uses double-linked lists and thus if you
use this function, you should also call SetPreviousHandler() on the
argument passed to this function:
@code
handlerA->SetNextHandler(handlerB);
handlerB->SetPreviousHandler(handlerA);
@endcode
@see GetNextHandler(), SetPreviousHandler(), GetPreviousHandler(),
wxWindow::PushEventHandler, wxWindow::PopEventHandler
@param handler
The event handler to be set as the next handler.
Cannot be @NULL.
@see @ref overview_eventhandling_processing
*/
void SetNextHandler(wxEvtHandler* handler);
virtual void SetNextHandler(wxEvtHandler* handler);
/**
Sets the pointer to the previous handler.
All remarks about SetNextHandler() apply to this function as well.
@param handler
Event handler to be set as the previous handler.
The event handler to be set as the previous handler.
Cannot be @NULL.
@see @ref overview_eventhandling_processing
*/
void SetPreviousHandler(wxEvtHandler* handler);
virtual void SetPreviousHandler(wxEvtHandler* handler);
/**
Unlinks this event handler from the chain it's part of (if any);
then links the "previous" event handler to the "next" one
(so that the chain won't be interrupted).
E.g. if before calling Unlink() you have the following chain:
@image html evthandler_unlink_before.png
then after calling @c B->Unlink() you'll have:
@image html evthandler_unlink_after.png
@since 2.9.0
*/
void Unlink();
/**
Returns @true if the next and the previous handler pointers of this
event handler instance are @NULL.
@since 2.9.0
@see SetPreviousHandler(), SetNextHandler()
*/
bool IsUnlinked() const;
//@}
};

View File

@ -1656,6 +1656,9 @@ public:
/**
@name Event-handling functions
wxWindow allows you to build a (sort of) stack of event handlers which
can be used to override the window's own event handling.
*/
//@{
@ -1669,9 +1672,8 @@ public:
wxEvtHandler* GetEventHandler() const;
/**
This function will generate the appropriate call to
Navigate() if the key event is one normally used for
keyboard navigation and return @true in this case.
This function will generate the appropriate call to Navigate() if the key
event is one normally used for keyboard navigation and return @true in this case.
@return Returns @true if the key pressed was for navigation and was
handled, @false otherwise.
@ -1691,44 +1693,62 @@ public:
/**
Removes and returns the top-most event handler on the event handler stack.
@param deleteHandler
If this is @true, the handler will be deleted after it is removed.
The default value is @false.
E.g. in the case of:
@image html overview_eventhandling_winstack.png
when calling @c W->PopEventHandler(), the event handler @c A will be
removed and @c B will be the first handler of the stack.
@see SetEventHandler(), GetEventHandler(),
PushEventHandler(), wxEvtHandler::ProcessEvent, wxEvtHandler
Note that it's an error to call this function when no event handlers
were pushed on this window (i.e. when the window itself is its only
event handler).
@param deleteHandler
If this is @true, the handler will be deleted after it is removed
(and the returned value will be @NULL).
@see @ref overview_eventhandling_processing
*/
wxEvtHandler* PopEventHandler(bool deleteHandler = false);
/**
Pushes this event handler onto the event stack for the window.
An event handler is an object that is capable of processing the events sent
to a window. By default, the window is its own event handler, but an application
may wish to substitute another, for example to allow central implementation
of event-handling for a variety of different window classes.
wxWindow::PushEventHandler allows an application to set up a @e stack
of event handlers, where an event not handled by one event handler is
handed to the next one in the chain.
E.g. if you have two event handlers @c A and @c B and a wxWindow instance
@c W and you call:
@code
W->PushEventHandler(A);
W->PushEventHandler(B);
@endcode
you will end up with the following situation:
@image html overview_eventhandling_winstack.png
Note that you can use wxWindow::PopEventHandler to remove the event handler.
@param handler
Specifies the handler to be pushed.
It must not be part of a wxEvtHandler chain; an assert will fail
if it's not unlinked (see wxEvtHandler::IsUnlinked).
@remarks An event handler is an object that is capable of processing the
events sent to a window. By default, the window is its
own event handler, but an application may wish to
substitute another, for example to allow central
implementation of event-handling for a variety of
different window classes.
wxWindow::PushEventHandler allows an application to set up a
chain of event handlers, where an event not handled by one event
handler is handed to the next one in the chain.
Use wxWindow::PopEventHandler to remove the event handler.
@see SetEventHandler(), GetEventHandler(),
PopEventHandler(), wxEvtHandler::ProcessEvent, wxEvtHandler
@see @ref overview_eventhandling_processing
*/
void PushEventHandler(wxEvtHandler* handler);
/**
Find the given @a handler in the windows event handler chain and remove
(but not delete) it from it.
Find the given @a handler in the windows event handler stack and unlinks
(but not delete) it. See wxEvtHandler::Unlink() for more info.
@param handler
The event handler to remove, must be non-@NULL and
must be present in this windows event handlers chain
must be present in this windows event handlers stack.
@return Returns @true if it was found and @false otherwise (this also
results in an assert failure so this function should
@ -1741,27 +1761,41 @@ public:
/**
Sets the event handler for this window.
Note that if you use this function you may want to use as the "next" handler
of @a handler the window itself; in this way when @a handler doesn't process
an event, the window itself will have a chance to do it.
@param handler
Specifies the handler to be set.
Specifies the handler to be set. Cannot be @NULL.
@remarks An event handler is an object that is capable of processing the
events sent to a window. By default, the window is its
own event handler, but an application may wish to
substitute another, for example to allow central
implementation of event-handling for a variety of
different window classes.
It is usually better to use wxWindow::PushEventHandler since
this sets up a chain of event handlers, where an event not
handled by one event handler is handed to the next one in the chain.
@see GetEventHandler(), PushEventHandler(),
PopEventHandler(), wxEvtHandler::ProcessEvent, wxEvtHandler
@see @ref overview_eventhandling_processing
*/
void SetEventHandler(wxEvtHandler* handler);
/**
wxWindows cannot be used to form event handler chains; this function
thus will assert when called.
Note that instead you can use PushEventHandler() or SetEventHandler() to
implement a stack of event handlers to override wxWindow's own
event handling mechanism.
*/
virtual void SetNextHandler(wxEvtHandler* handler);
/**
wxWindows cannot be used to form event handler chains; this function
thus will assert when called.
Note that instead you can use PushEventHandler() or SetEventHandler() to
implement a stack of event handlers to override wxWindow's own
event handling mechanism.
*/
virtual void SetPreviousHandler(wxEvtHandler* handler);
//@}
/**
@name Window styles functions
*/

View File

@ -1036,12 +1036,7 @@ wxEvtHandler::wxEvtHandler()
wxEvtHandler::~wxEvtHandler()
{
// Takes itself out of the list of handlers
if (m_previousHandler)
m_previousHandler->m_nextHandler = m_nextHandler;
if (m_nextHandler)
m_nextHandler->m_previousHandler = m_previousHandler;
Unlink();
if (m_dynamicEvents)
{
@ -1103,6 +1098,26 @@ wxEvtHandler::~wxEvtHandler()
delete m_clientObject;
}
void wxEvtHandler::Unlink()
{
// this event handler must take itself out of the chain of handlers:
if (m_previousHandler)
m_previousHandler->SetNextHandler(m_nextHandler);
if (m_nextHandler)
m_nextHandler->SetPreviousHandler(m_previousHandler);
m_nextHandler = NULL;
m_previousHandler = NULL;
}
bool wxEvtHandler::IsUnlinked() const
{
return m_previousHandler == NULL &&
m_nextHandler == NULL;
}
#if wxUSE_THREADS
bool wxEvtHandler::ProcessThreadEvent(const wxEvent& event)

View File

@ -1096,73 +1096,121 @@ bool wxWindowBase::Reparent(wxWindowBase *newParent)
// event handler stuff
// ----------------------------------------------------------------------------
void wxWindowBase::PushEventHandler(wxEvtHandler *handler)
void wxWindowBase::SetEventHandler(wxEvtHandler *handler)
{
wxCHECK_RET(handler != NULL, "SetEventHandler(NULL) called");
m_eventHandler = handler;
}
void wxWindowBase::SetNextHandler(wxEvtHandler *WXUNUSED(handler))
{
// disable wxEvtHandler chain mechanism for wxWindows:
// wxWindow uses its own stack mechanism which doesn't mix well with wxEvtHandler's one
wxFAIL_MSG("wxWindow cannot be part of a wxEvtHandler chain");
}
void wxWindowBase::SetPreviousHandler(wxEvtHandler *WXUNUSED(handler))
{
// we can't simply wxFAIL here as in SetNextHandler: in fact the last
// handler of our stack when is destroyed will be Unlink()ed and thus
// will call this function to update the pointer of this window...
//wxFAIL_MSG("wxWindow cannot be part of a wxEvtHandler chain");
}
void wxWindowBase::PushEventHandler(wxEvtHandler *handlerToPush)
{
wxCHECK_RET( handlerToPush != NULL, "PushEventHandler(NULL) called" );
// the new handler is going to be part of the wxWindow stack of event handlers:
// it can't be part also of an event handler double-linked chain:
wxASSERT_MSG(handlerToPush->IsUnlinked(),
"The handler being pushed in the wxWindow stack shouldn't be part of "
"a wxEvtHandler chain; call Unlink() on it first");
wxEvtHandler *handlerOld = GetEventHandler();
wxCHECK_RET( handlerOld, "an old event handler is NULL?" );
handler->SetNextHandler(handlerOld);
// now use wxEvtHandler double-linked list to implement a stack:
handlerToPush->SetNextHandler(handlerOld);
if ( handlerOld )
GetEventHandler()->SetPreviousHandler(handler);
if (handlerOld != this)
handlerOld->SetPreviousHandler(handlerToPush);
SetEventHandler(handler);
SetEventHandler(handlerToPush);
#ifdef __WXDEBUG__
// final checks of the operations done above:
wxASSERT_MSG( handlerToPush->GetPreviousHandler() == NULL,
"the first handler of the wxWindow stack should have no previous handlers set" );
wxASSERT_MSG( handlerToPush->GetNextHandler() != NULL,
"the first handler of the wxWindow stack should have non-NULL next handler" );
wxEvtHandler* pLast = handlerToPush;
while (pLast && pLast != this)
pLast = pLast->GetNextHandler();
wxASSERT_MSG( pLast->GetNextHandler() == NULL,
"the last handler of the wxWindow stack should have this window as next handler" );
#endif
}
wxEvtHandler *wxWindowBase::PopEventHandler(bool deleteHandler)
{
wxEvtHandler *handlerA = GetEventHandler();
if ( handlerA )
// we need to pop the wxWindow stack, i.e. we need to remove the first handler
wxEvtHandler *firstHandler = GetEventHandler();
wxCHECK_MSG( firstHandler != NULL, NULL, "wxWindow cannot have a NULL event handler" );
wxCHECK_MSG( firstHandler != this, NULL, "cannot pop the wxWindow itself" );
wxCHECK_MSG( firstHandler->GetPreviousHandler() == NULL, NULL,
"the first handler of the wxWindow stack should have no previous handlers set" );
wxEvtHandler *secondHandler = firstHandler->GetNextHandler();
wxCHECK_MSG( secondHandler != NULL, NULL,
"the first handler of the wxWindow stack should have non-NULL next handler" );
firstHandler->SetNextHandler(NULL);
secondHandler->SetPreviousHandler(NULL);
// now firstHandler is completely unlinked; set secondHandler as the new window event handler
SetEventHandler(secondHandler);
if ( deleteHandler )
{
wxEvtHandler *handlerB = handlerA->GetNextHandler();
handlerA->SetNextHandler(NULL);
if ( handlerB )
handlerB->SetPreviousHandler(NULL);
SetEventHandler(handlerB);
if ( deleteHandler )
{
delete handlerA;
handlerA = NULL;
}
delete firstHandler;
firstHandler = NULL;
}
return handlerA;
return firstHandler;
}
bool wxWindowBase::RemoveEventHandler(wxEvtHandler *handler)
bool wxWindowBase::RemoveEventHandler(wxEvtHandler *handlerToRemove)
{
wxCHECK_MSG( handler, false, _T("RemoveEventHandler(NULL) called") );
wxCHECK_MSG( handlerToRemove != NULL, false, "RemoveEventHandler(NULL) called" );
wxCHECK_MSG( handlerToRemove != this, false, "Cannot remove the window itself" );
wxEvtHandler *handlerPrev = NULL,
*handlerCur = GetEventHandler();
while ( handlerCur )
if (handlerToRemove == GetEventHandler())
{
// removing the first event handler is equivalent to "popping" the stack
PopEventHandler(false);
return true;
}
// NOTE: the wxWindow event handler list is always terminated with "this" handler
wxEvtHandler *handlerCur = GetEventHandler()->GetNextHandler();
while ( handlerCur != this )
{
wxEvtHandler *handlerNext = handlerCur->GetNextHandler();
if ( handlerCur == handler )
if ( handlerCur == handlerToRemove )
{
if ( handlerPrev )
{
handlerPrev->SetNextHandler(handlerNext);
}
else
{
SetEventHandler(handlerNext);
}
if ( handlerNext )
{
handlerNext->SetPreviousHandler ( handlerPrev );
}
handler->SetNextHandler(NULL);
handler->SetPreviousHandler(NULL);
handlerCur->Unlink();
wxASSERT_MSG( handlerCur != GetEventHandler(),
"the case Remove == Pop should was already handled" );
return true;
}
handlerPrev = handlerCur;
handlerCur = handlerNext;
}
@ -1173,6 +1221,7 @@ bool wxWindowBase::RemoveEventHandler(wxEvtHandler *handler)
bool wxWindowBase::HandleWindowEvent(wxEvent& event) const
{
// SafelyProcessEvent() will handle exceptions nicely
return GetEventHandler()->SafelyProcessEvent(event);
}