1、非模态对话框的模板必须具有Visible风格,否则对话框将不可见,而模态对话框则无需设置该项风格。更保险的办法是调用CWnd::ShowWindow(SW_SHOW)来显示对话框,而不管对话框是否具有Visible风格。
2、非模态对话框对象是用new操作符在堆中动态创建的,而不是以成员变量的形式嵌入到别的对象中或以局部变量的形式构建在堆栈上。通常应在对话框的拥有者窗口类内声明一个指向对话框类的指针成员变量,通过该指针可访问对话框对象。
3、*通过调用CDialog::Create函数来启动对话框,而不是CDialog::DoModal,这是模态对话框的关键所在。由于Create函数不会启动新的消息循环,对话框与应用程序共用同一个消息循环,这样对话框就不会垄断用户的输入。Create在显示了对话框后就立即返回,而DoModal是在对话框被关闭后才返回的。众所周知,在MFC程序中,窗口对象的生存期应长于对应的窗口,也就是说,不能在未关闭屏幕上窗口的情况下先把对应的窗口对象删除掉。由于在Create返回后,不能确定对话框是否已关闭,这样也就无法确定对话框对象的生存期,因此只好在堆中构建对话框对象,而不能以局部变量的形式来构建之。
4、代码示例:
对话框资源ID为:IDD_MYDIALOG
对话框的类定义为:CMyDialog
CMyView的成员变量为:CMyDialog* m_myDlg
CMyView::OnOpenDialogButton()
{
CMainFrame* pWnd=(CMainFrame*)AfxGetMainWnd();
ASSERT_VALID(pWnd); //定义父窗口指针pWnd
m_myDlg = new CMyDialog(pWnd); //堆分配非模态对话框内存空间, //CMyDialog(CWnd* pParent=NULL)为构造函数
m_myDlg->Create(IDD_MYDIALOG,pWnd); // pWnd可以为 GetDesktopWindow(),这样可以避免遮盖
m_myDlg->ShowWindow(SW_SHOW);
//可用this指针代替pWnd指针,则省略头两行
}
5、必须有一个标志表明非模态对话框是否是打开的。这样做的原因是用户有可能在打开一个模态对话框的情况下,又一次选择打开命令。程序根据标志来决定是打开一个新的对话框,还是仅仅把原来打开的对话框激活。通常可以用拥有者窗口中的指向对话框对象的指针作为这种标志,当对话框关闭时,给该指针赋NULL值,以表明对话框对象已不存在了。
[注]:在C++编程中,判断一个位于堆中的对象是否存在的常用方法是判断指向该对象的指针是否为空。这种机制要求程序员将指向该对象的指针初始化为NULL值,在创建对象时将返回的地址赋给该指针,而在删除对象时将该指针置成NULL值。
6、必须调用CWnd::DestroyWindow而不是CDialog::EndDialog来关闭非模态对话框。调用CWnd::DestroyWindow是直接删除窗口的一般方法。由于缺省的CDialog::OnOK和CDialog::OnCancel函数均调用EndDialog,故程序员必须编写自己的OnOK和OnCancel函数并且在函数中调用DestroyWindow来关闭对话框。
7、因为是用new操作符构建非模态对话框对象,因此必须在对话框关闭后,用delete操作符删除对话框对象。在屏幕上一个窗口被删除后,框架会调用CWnd::PostNcDestroy,这是一个虚拟函数,程序可以在该函数中完成删除窗口对象的工作,具体代码如下
void CModelessDialog::PostNcDestroy (#add 如OnDestroy一样,OnNcDestroy应该也可以)
{
delete this; //删除对象本身
}
这样,在删除屏幕上的对话框后,对话框对象将被自动删除。拥有者对象就不必显式的调用delete来删除对话框对象了。
也可以通过由的DestroyWindow()引起的WM_DESTROY消息处理函数OnDestroy()种实现delete this操作。
void CModelessDlg::OnDestroy()
{
CDialog::OnDestroy();
delete this;
}
非模态对话框\模态对话框的转换
1、意图
有时候我们希望将非模态窗口显示为模态窗口。比如在IE的“文件”菜单下选择“打印”,弹出的“打印”对话框就是非模态的(也许我们不太清楚Microsoft的设计意图,一般来说这里的“打印”对话框应该是模态的)。这种情况下如何将“打印”对话框显示为模态的呢(这个对话框对我们来说是Black Box)?
2、简单实现
简单地说,模态窗口显示时,其父窗口是被Disable的,所以模态窗口才呈现“模态”,所以只要在显示我们非模态窗口前将父窗口Disable即可实现,如下:
以下为引用的内容: …… AfxGetMainWnd()->EnableWindow(FALSE);//将主窗口Disable,显示出的非模态窗口就变成模态的了 ShowModelessWindow(); …… |
问题在于非模态窗口显示之后是立即返回的,那我们将父窗口Enable的代码放在哪里呢?笨办法是用时钟,不断地检测显示出来的非模态窗口是否已经关闭,若关闭则将父窗口Enable。
当然,还要更好的办法。
3、WH_CBT Hook
WH_CBT钩子的详细说明请参阅MSDN,我们仅仅需要知道的是在窗口创建、销毁之前系统都会调用挂上了WH_CBT的钩子函数,这正是我们需要的。具体就是在显示非模态窗口之前挂上我们的WH_CBT钩子处理函数,之后非模态窗口创建的句柄就可以在钩子函数的nCode为HCBT_CREATEWND(创建窗口)时从wParam参数获得,将其保存下来,并在钩子函数的nCode为HCBT_DESTROYWND(销毁窗口)时与wParam参数进行比较,如果匹配则恢复主窗口的Enable状态。
2、实现
1)首先定义两个变量,此处为全局静态变量。
以下为引用的内容: static HHOOK g_hHook = NULL; static HWND g_hWndDialog = NULL;//用以保存窗口句柄 |
2)再添加一个函数CbtProc,由于是回调函数,注意要声明为static。
static LRESULT CALLBACK CbtProc(int nCode, WPARAM wParam, LPARAM lParam);
3)挂钩
假设下面是我们的某个浏览器中调用“打印”对话框的函数
以下为引用的内容: void CMyHtmlView::OnFilePrint() { AfxGetMainWnd()->EnableWindow(FALSE); g_hWndDialog = 0; //可能多次调用,需要重置保存窗口句柄的变量 g_hHook = SetWindowsHookEx(WH_CBT, CbtProc, NULL, GetCurrentThreadId()); if (!g_hHook) { AfxGetMainWnd()->EnableWindow(TRUE); return; } |
调用“打印”对话框
以下为引用的内容: } LRESULT CALLBACK CMyHtmlView::CbtProc(int nCode, WPARAM wParam, LPARAM lParam) { switch (nCode) { case HCBT_CREATEWND: { HWND hWnd = (HWND)wParam; LPCBT_CREATEWND pcbt = (LPCBT_CREATEWND)lParam; LPCREATESTRUCT pcs = pcbt->lpcs; if ((DWORD)pcs->lpszClass == 0x00008002)//#32770,“打印”对话框类名 { if ( g_hWndDialog == 0 ) g_hWndDialog = hWnd; // 只保存一次保存“打印”窗口的句柄 } break; } case HCBT_DESTROYWND: { HWND hwnd = (HWND)wParam; if (hwnd == g_hWndDialog) { AfxGetMainWnd()->EnableWindow(TRUE);//恢复窗口状态 UnhookWindowsHookEx(g_hHook);//去除挂钩 } break; } } return CallNextHookEx(g_hHook, nCode, wParam, lParam); } |