Fix handling events from their items in submenu itself

This previously worked in wxGTK, but not in wxMSW and even under wxGTK
it could be surprising that the submenu got the event, but its parent
menu did not.

Make things consistent between the platforms and send the event to the
menu directly containing it first, but then also to its parent menu(s).

Document the new behaviour and verify that it works as intended with a
new unit test.

Closes #18202.
This commit is contained in:
Vadim Zeitlin
2018-08-24 19:03:15 +02:00
parent 5b2c905fb9
commit de5ba70203
5 changed files with 47 additions and 29 deletions

View File

@@ -112,6 +112,7 @@ All (GUI):
- Add support for style="page-break-inside:avoid" to <div> in wxHTML.
- Support strike-through in wxDataViewItem attributes (approach, Igor Korot).
- Allow distinguishing between user- and script-opened windows in wxWebView.
- Allow binding to events generated by their items in submenus too.
wxGTK:

View File

@@ -482,18 +482,20 @@ public:
@section menu_eventhandling Event handling
If the menu is part of a menubar, then wxMenuBar event processing is used.
Event handlers for the commands generated by the menu items can be
connected directly to the menu object itself using wxEvtHandler::Bind(). If
this menu is a submenu of another one, the events from its items can also
be processed in the parent menu and so on, recursively.
With a popup menu (see wxWindow::PopupMenu), there is a variety of ways to
handle a menu selection event (@c wxEVT_MENU):
- Provide @c EVT_MENU handlers in the window which pops up the menu, or in an
ancestor of that window (the simplest method);
- Derive a new class from wxMenu and define event table entries using the @c EVT_MENU macro;
- Set a new event handler for wxMenu, through wxEvtHandler::SetNextHandler,
specifying an object whose class has @c EVT_MENU entries;
If the menu is part of a menu bar, then events can also be handled in
wxMenuBar object.
Note that instead of static @c EVT_MENU macros you can also use dynamic
connection; see @ref overview_events_bind.
Finally, menu events can also be handled in the associated window, which is
either the wxFrame associated with the menu bar this menu belongs to or the
window for which wxWindow::PopupMenu() was called for the popup menus.
See @ref overview_events_bind for how to bind event handlers to the various
objects.
@library{wxcore}
@category{menus}

View File

@@ -648,11 +648,12 @@ bool wxMenuBase::DoProcessEvent(wxMenuBase* menu, wxEvent& event, wxWindow* win)
{
event.SetEventObject(menu);
if ( menu )
{
wxMenuBar* const mb = menu->GetMenuBar();
wxMenuBar* const mb = menu ? menu->GetMenuBar() : NULL;
// Try the menu's event handler first
// Process event in the menu itself and all its parent menus, if it's a
// submenu, first.
for ( ; menu; menu = menu->GetParent() )
{
wxEvtHandler *handler = menu->GetEventHandler();
if ( handler )
{
@@ -666,8 +667,9 @@ bool wxMenuBase::DoProcessEvent(wxMenuBase* menu, wxEvent& event, wxWindow* win)
if ( handler->SafelyProcessEvent(event) )
return true;
}
}
// If this menu is part of the menu bar, try the event there. this
// If this menu is part of the menu bar, try the event there.
if ( mb )
{
if ( mb->HandleWindowEvent(event) )
@@ -679,7 +681,6 @@ bool wxMenuBase::DoProcessEvent(wxMenuBase* menu, wxEvent& event, wxWindow* win)
if ( event.ShouldPropagate() )
return false;
}
}
// Try the window the menu was popped up from.
if ( win )

View File

@@ -795,7 +795,7 @@ bool wxMenu::MSWCommand(WXUINT WXUNUSED(param), WXWORD id_)
}
}
SendEvent(id, checked);
item->GetMenu()->SendEvent(id, checked);
}
return true;

View File

@@ -445,11 +445,14 @@ wxMenu* CreateTestMenu(wxFrame* frame)
// reliable than using wxUIActionSimulator and currently works in all ports as
// they all call wxMenuBase::SendEvent() from their respective menu event
// handlers.
#define ASSERT_MENU_EVENT_RESULT(menu, result) \
#define ASSERT_MENU_EVENT_RESULT_FOR(cmd, menu, result) \
g_str.clear(); \
menu->SendEvent(wxID_APPLY); \
menu->SendEvent(cmd); \
CHECK( g_str == result )
#define ASSERT_MENU_EVENT_RESULT(menu, result) \
ASSERT_MENU_EVENT_RESULT_FOR(wxID_APPLY, menu, result)
void EventPropagationTestCase::MenuEvent()
{
wxFrame* const frame = static_cast<wxFrame*>(wxTheApp->GetTopWindow());
@@ -472,6 +475,17 @@ void EventPropagationTestCase::MenuEvent()
ASSERT_MENU_EVENT_RESULT( menu, "aomA" );
// Check that a handler can also be attached to a submenu.
wxMenu* const submenu = new wxMenu;
submenu->Append(wxID_ABOUT);
menu->Append(wxID_ANY, "Submenu", submenu);
TestMenuEvtHandler hs('s'); // 's' for "submenu"
submenu->SetNextHandler(&hs);
wxON_BLOCK_EXIT_OBJ1( *submenu,
wxEvtHandler::SetNextHandler, (wxEvtHandler*)NULL );
ASSERT_MENU_EVENT_RESULT_FOR( wxID_ABOUT, submenu, "aosomA" );
// Test that the event handler associated with the menu bar gets the event.
TestMenuEvtHandler hb('b'); // 'b' for "menu Bar"
mb->PushEventHandler(&hb);