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

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

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


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


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

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

135     wxBusyCursorSuspender cs; // temporarily suppress the busy cursor
136 
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首先被调用。

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

复现问题时,设置断点到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. 更好的解决方案?

你可能感兴趣的:(wxWidget库应用)