[Effective WX] 有关wxGTK的模态对话框(modal dialog)弹出的非模态窗口的问题

来源:互联网 发布:手机淘宝免费开店流程 编辑:程序博客网 时间:2024/04/29 17:54

wxGTK上有这样的的编程问题:

已经有一个模态对话框(通过ShowModal()显示出来),然后从它上面弹出一个非模态窗口(为什么会是非模态?应该是或者是业务需求,或者是想重用已存在所需功能的非模态窗口类),调用它的Show()函数将其显示出来。结果我们发现,根本就不能再对这个非模态窗口做任何操作。它无法接受任何用户输入,反而它的父窗口(那个模态对话框)仍像之前一样可以接受用户输入。


这篇文章将给出一些浅显的分析和一个可能的解决方案。


1. GTK版本的模态对话框ShowModal()函数到底做了什么?

在$wxsrc/gtk/dialog.cpp文件的135-153行中,ShowModal()做了如下的事情:

135     wxBusyCursorSuspender cs; // temporarily suppress the busy cursor136 137     Show( true );138 139     m_modalShowing = true;140 141     g_openDialogs++;142 143     // NOTE: gtk_window_set_modal internally calls gtk_grab_add() !144     gtk_window_set_modal(GTK_WINDOW(m_widget), TRUE);145 146     wxEventLoop().Run();147 148     gtk_window_set_modal(GTK_WINDOW(m_widget), FALSE);149 150     g_openDialogs--;151 152     return GetReturnCode();153 }
它利用gtk代码库,直接新启了一个事件循环。

2. 一个解决方案


3. 上面方案带来的问题,如何解决?

如果你采用上面的方案,可能会发现你仍然无法使用窗口右上角的"X"按钮来关闭这个非模态窗口。

gdb调试代码,你会发现当用户点击"X"按钮后,wx代码库会调用到它注册gtk库的一个callback。代码行处于文件的$wxsrc/src/gtk/toplevel.cpp的260-273行。至于如何定位到是这个函数被调用的,你可以让这个非模态窗口直接创建显示出来,而不是从模态对话框中创建然后显示,设置断点到它的Close Window事件的响应函数,然后浏览它的调用栈就可以定位到这个callback首先被调用。


复现问题时,设置断点到268行,然后检查该行每一判断条件。你会发现g_openDialogs=1, win->GetExtraStyle()没有那个wxTOPLEVEL_EX_DIALOG属性,win->IsGrabbed()返回false,从而271行的win->Close()总是不会调用,进而窗口“X"无法响应的原因,你已清楚。

找到问题的原因,想解决它应该不会太难。

1) g_openDialogs 是一个全局变量,它并没有public出来,只是在dialog的ShowModal函数中会被改写。对这个变量我们最好不要有其它想法。

2) 对于win->GetExtraStyle(),我们在创建非模态窗口前,先增加wxTOPLEVEL_EX_DIALOG属性

win->SetExtraStyle(win->GetExtraStyle() | wxTOPLEVEL_EX_DIALOG)

马上动手试验(需要将非模态窗口加上wxCLOSE属性,让右上角出现“X"),你会发现它可以工作,非模态窗口可以响应"X"关闭事件,而且窗口上的其它控件并没有受任何影响。我想这是个比较好的解决方案。

3) 对于win->IsGrabbed(),这是一个wx对gtk特别处理的函数,定义在gtk版本的子类topwindow类中。跟它一起的还有2个函数AddGrab()和RemoveGrab()。

看下它们的实现就知道了:

1316 void wxTopLevelWindowGTK::AddGrab()1317 {1318     if (!m_grabbed)1319     {1320         m_grabbed = true;1321         gtk_grab_add( m_widget );1322         wxEventLoop().Run();1323         gtk_grab_remove( m_widget );1324     }       1325 }       1326     1327 void wxTopLevelWindowGTK::RemoveGrab()1328 {   1329     if (m_grabbed)1330     {       1331         gtk_main_quit();1332         m_grabbed = false;1333     }1334 }       
AddGrab()是新启一个事件循环,让这个非模态窗口成为一个模态的。如果我们在非模态窗口显示之后,调用这个函数,应该也可以工作。非模态窗口上的所有控件都可以响应用户输入,包括"X"按钮。但是它更改了之前非模态窗口的设计初衷,可能会带来更多的麻烦。譬如之前非模态窗口上可能会弹出其他的非模态窗口,这样的话,新的非模态窗口就无法接受用户输入了。

4. 更好的解决方案?

0 0
原创粉丝点击