在GUI應用編程中,咱們一般會提供給用戶一些右鍵菜單選項。假若有這樣的編程案例:
右鍵菜單依託於某個窗口,若是某個右鍵菜單項提供這樣的功能:當用戶選擇它以後,GUI代碼作了一些事情後,須要跳轉到其它窗口,在跳轉以前或以後,咱們不得不銷燬以前右鍵菜單依託的窗口類對象。
1. 問題描述:
在wxGTK版本的程序中,當跳轉到另外一個窗口以後,GUI程序會crash。位置爲$wxsrc/gtk/menu.cpp:145行。
你能夠猜到win這時已是野指針了,對的。menu的Invoking Window就是那個已經銷燬的依託的窗口。
2. 緣由分析:
popup的wxMenu的代碼一般會這樣寫:shell
wxMenu menu; menu.Append(...); ... invokingwin->PopupMenu(&menu);
當調用PopupMenu時,函數將會進入一個事件循環。編程
1660 bool wxWindowGTK::DoPopupMenu( wxMenu *menu, int x, int y ) 1661 { 1662 wxCHECK_MSG( m_widget != NULL, false, wxT("invalid window") ); 1663 1664 wxCHECK_MSG( menu != NULL, false, wxT("invalid popup-menu") ); 1665 1666 // NOTE: if you change this code, you need to update 1667 // the same code in taskbar.cpp as well. This 1668 // is ugly code duplication, I know. 1669 1670 SetInvokingWindow( menu, this ); 1671 1672 menu->UpdateUI(); 1673 1674 bool is_waiting = true; 1675 1676 gulong handler = g_signal_connect (menu->m_menu, "hide", 1677 G_CALLBACK (gtk_pop_hide_callback), 1678 &is_waiting); 1679 1680 wxPoint pos; 1681 gpointer userdata; 1682 GtkMenuPositionFunc posfunc; 1683 if ( x == -1 && y == -1 ) 1684 { 1685 // use GTK's default positioning algorithm 1686 userdata = NULL; 1687 posfunc = NULL; 1688 } 1689 else 1690 { 1691 pos = ClientToScreen(wxPoint(x, y)); 1692 userdata = &pos; 1693 posfunc = wxPopupMenuPositionCallback; 1694 } 1695 1696 wxMenuEvent eventOpen(wxEVT_MENU_OPEN, -1, menu); 1697 DoCommonMenuCallbackCode(menu, eventOpen); 1698 1699 gtk_menu_popup( 1700 GTK_MENU(menu->m_menu), 1701 (GtkWidget *) NULL, // parent menu shell 1702 (GtkWidget *) NULL, // parent menu item 1703 posfunc, // function to position it 1704 userdata, // client data 1705 0, // button used to activate it 1706 gtk_get_current_event_time() 1707 ); 1708 1709 while (is_waiting) 1710 { 1711 gtk_main_iteration(); 1712 } 1713 1714 g_signal_handler_disconnect (menu->m_menu, handler); 1715 1716 wxMenuEvent eventClose(wxEVT_MENU_CLOSE, -1, menu); 1717 DoCommonMenuCallbackCode(menu, eventClose); 1718 1719 return true; 1720 }
3. 一種可能的解決方案:
不知你注意到$wxsrc/gtk/menu.cpp:139-141行代碼了沒?不嫌麻煩地將代碼貼在下面:
若是在代碼crash以前,咱們讓執行流從141行代碼退出,程序就不會crash。我想這是能夠作到的。 注意到140代碼是一個事件處理,這是提供給客戶端代碼處理那個event的一個機會(一般plugin編程能夠利用這樣的方式)。想到若是咱們可以讓menu->GetEventHandler()返回的event handler可以process這個event,並且返回true(一般表明已經處理這個事件),這個問題就能夠比較順利的解決了。 所以,直接提供下面的event handler,push到menu的event handler chain的鏈表頭:ide
class PopupMenuCloseEvtHandler : public wxEvtHandler { public: explicit PopupMenuCloseEvtHandler(wxMenu* pMenu) : m_pMenu(pMenu) { if (m_pMenu) { m_pMenu->SetEventHandler(this); Connect(wxEVT_MENU_CLOSE, wxMenuEventHandler(PopupMenuCloseEvtHandler::HandlePopupMenuClose), NULL, this); } } ~PopupMenuCloseEvtHandler() { if (m_pMenu) { m_pMenu->SetEventHandler(NULL); Disconnect(wxEVT_MENU_CLOSE, wxMenuEventHandler(PopupMenuCloseEvtHandler::HandlePopupMenuClose), NULL, this); } } protected: void HandlePopupMenuClose(wxMenuEvent& evt) { evt.Skip(false); // means handled } private: wxMenu* m_pMenu; };
在PopupMenu調用以前,添加下面相似的代碼:函數
wxMenu menu; menu.Append(...); ... #ifdef _WXGTK_ PopupMenuCloseEvtHandler menuEvtHandler(&menu); #endif invokingwin->PopupMenu(&menu);
之因此說上面的方案是一個可能的方案,是由於:你的invoking window在正常流程下,若是須要處理彈出菜單的wxEVT_MENU_CLOSE事件的話,上面方案會break你的代碼流程。this