经常有人问关于模态对话框和系统菜单内部实现原理方面的问题, 因为系统通过API隐藏了太多细节,这2个问题确实令初学者甚至是有经验的开发者困扰, 下面是我个人的一些经验总结。

先说模态对话框,外部看模态对话框其实就是Dialog弹出以后函数(或者说调用栈call stack)不直接返回, 而是要让你做出选择后关闭Dialog, 然后程序再继续往下执行。在你关闭Modal Dialog之前, 你不能做其他操作。
下面是我自己模拟模态对话框行为的代码:

#define MODAL_DLG_EXIT_NOTIFY    _T("modal_dialog_can_exit_now")
#define MODAL_DLG_EXIT_VALUE     _T("this_is_the_exit_code")

int RunModal(HWND hWnd)
{
    int nRet(-1);
    
    HWND hWndOwner = GetWindow(hWnd, GW_OWNER);
    BOOL bDisableOwner = FALSE;
    if(hWndOwner != GetDesktopWindow())
    {
        _ASSERT(!(::GetWindowLong(hWndOwner, GWL_STYLE) & WS_CHILD));
        EnableWindow(hWndOwner, FALSE);
        bDisableOwner = TRUE;
    }
    
    MSG msg = {0};
    while(GetMessage(&msg, 0, 0, 0))
    {
        TranslateMessage (&msg);
        DispatchMessageW (&msg);
        
        if(GetProp(hWnd, MODAL_DLG_EXIT_NOTIFY) != 0)
        {
            nRet = (int)GetProp(hWnd, MODAL_DLG_EXIT_VALUE);
            break;
        }
    }
    
    if(bDisableOwner)
    {
        EnableWindow(hWndOwner, TRUE);
    }
    
    DestroyWindow(hWnd);
    
    return nRet;
}

BOOL ExitModal(HWND hWnd, int nExitCode)
{
    BOOL bRet = SetProp(hWnd, MODAL_DLG_EXIT_NOTIFY, (HANDLE)1);
    SetProp(hWnd, MODAL_DLG_EXIT_VALUE, (HANDLE)nExitCode);

PostMessage(hWnd, WM_NULL, 0, 0);

return bRet;
}

可以看到,其实原理很简单, 主要就是Disable对话框的Owner窗口, 然后进入消息循坏, 直到你调用ExitModal (EndDialog) 才退出消息循坏。 现在你也应该知道为什么不能用DestroyWindow,而是一定要调用EndDialog来关闭模态对话框的原因了, 因为你直接DestroyWindow就没有机会Enable它的Owner窗口了。

下面我们再说菜单的实现原理, 相信菜单的原理即使对很多有经验的开发者也不一定清楚。
我们知道菜单其实也是一个普通的窗口,首先菜单窗口其实和模态对话框一样, 在我们关闭菜单,对菜单做出选择之前函数是不会返回的。 菜单窗口的特殊之处在于,菜单弹出的时候我们可以看到它下面的窗口还是保持激活状态, 也就是说当前的得到焦点的窗口其实是菜单的Owner窗口, 但是菜单窗口同时又能响应键盘消息(我们可以通过上下键或是Enter和Esc做出选择)。从窗口机制的原理上说两者是矛盾的,一个没有获得焦点的窗口怎么能够响应键盘消息呢? 下面是我自己对弹出菜单行为的模拟:

#define MENU_EXIT_NOTIFY        _T("menu_loop_can_exit_now")
#define MENU_EXIT_COMMAND_ID    _T("this_is_the_menu_command_id")

int RunMenu(HWND hWnd)
{
    int nRet(-1);
    
    BOOL bMenuDestroyed(FALSE);
    BOOL bMsgQuit(FALSE);
    HWND hWndOwner = GetWindow(hWnd, GW_OWNER);
    _ASSERT(GetForegroundWindow() == hWndOwner);
    
    while(TRUE)
    {
        if(GetProp(hWnd, MENU_EXIT_NOTIFY) != 0)
        {
            nRet = (int)GetProp(hWnd, MENU_EXIT_COMMAND_ID);
            break;
        }

if(GetForegroundWindow() != hWndOwner)
        {
            break;
        }

MSG msg = {0};
        if(PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
        {
            if(msg.message == WM_KEYDOWN
                || msg.message == WM_SYSKEYDOWN
                || msg.message == WM_KEYUP
                || msg.message == WM_SYSKEYUP
                || msg.message == WM_CHAR
                || msg.message == WM_IME_CHAR)
            {
                //transfer the message to menu window
                msg.hwnd = hWnd;
            }
            else if(msg.message == WM_LBUTTONDOWN
                || msg.message == WM_RBUTTONDOWN
                || msg.message == WM_NCLBUTTONDOWN
                || msg.message == WM_NCRBUTTONDOWN)
            {
                //click on other window
                if(msg.hwnd != hWnd)
                {
                    DestroyWindow(hWnd);
                    bMenuDestroyed = TRUE;
                }
            }
            else if(msg.message == WM_QUIT)
            {
                bMsgQuit = TRUE;
            }

TranslateMessage (&msg);
            DispatchMessageW (&msg);
        }
        else
        {
            MsgWaitForMultipleObjects (0, 0, 0, 10, QS_ALLINPUT);
        }

if(bMenuDestroyed) break;

if(bMsgQuit)
        {
            PostQuitMessage(msg.wParam);
            break;
        }
    }
    
    if(!bMenuDestroyed) DestroyWindow(hWnd);
    
    return nRet;
}

BOOL ExitMenu(HWND hWnd, int nCommandID = -1)
{
    BOOL bRet = SetProp(hWnd, MENU_EXIT_NOTIFY, (HANDLE)1);
    SetProp(hWnd, MENU_EXIT_COMMAND_ID, (HANDLE)nCommandID);

return bRet;
}

从代码可以看到,如果我们可以自己控制整个Windows消息循环,那么中间我们就有很多事可以做了,包括拦截和转发任何消息,比如我们可以把原来系统发给A窗口的消息直接转发给B窗口:菜单窗口的键盘消息最初是发给主窗口的,但是被我们在消息循环中拦截后,转发了。

简单总结下,Windows的API封装了太多细节, 尽管大部分时候我们只要知道如何使用它们,而不用关心它们的内部如何实现。 但是当你写一些相对底层的东西,比如开发自己的DirectUI界面库时, 还是需要真正理解某些API的内部实现原理,才能继续深入下去。

注:因为没有Windows源码,上面的代码只是个人的猜测和模拟,如有不正确的地方欢迎指正。

完整测试源码:ModalDialog&Menu Test

http://www.cppblog.com/weiym/archive/2013/04/07/199189.html

这个问题确实值得探讨,菜单弹出时父窗体貌似没有焦点了吧?
我的做法是菜单模拟窗体打开时用SetCapture取得控制权,一旦窗体收到WM_CAPTURECHANGED消息就把窗体退出。

DirectUI中模态对话框和菜单的原理(自己控制整个Windows消息循环。或者,用菜单模拟窗体打开时用SetCapture取得控制权,一旦窗体收到WM_CAPTURECHANGED消息就把窗体退出)的更多相关文章

  1. MFC中模态对话框和非模态对话框的差别

    在MFC中有模态对话框和非模态对话框,那这两种有什么差别呢. 又都是用于什么场合呢. 首先,要弄清楚2种对话框是怎样创建的. 然后要弄清楚2种对话框有什么差别,可能从表面上看,模态会堵塞主对话框.可原 ...

  2. QT中|Qt::Tool类型窗口自动退出消息循环问题解决(setQuitOnLastWindowClosed必须设置为false,最后一个窗口不显示的时候,程序会退出消息循环)

    为application 设置setQuitOnLastWindowClosed属性,确实为true: 将其显示为false; 退出该应该程序不能调用QDialog的close消息槽,只能调用qApp ...

  3. MFC中的模态对话框与非模态对话框

    模态对话框创建: MyDialog mydlg; mydlg.DoModal() 当前只能运行此模态对话框,且停止主窗口的运行,直到模态对话框退出,才允许主窗口运行. 模态对话框的关闭顺序: OnCl ...

  4. MFC模态对话框的消息循环

    MFC模态对话框的消息循环 单线程程序, 当主窗口响应函数中弹出模态对话框时,为什么主窗口响应函数可能照常工作? 当弹出模态对话框时,线程的消息循环无法返回,父窗口的事件本应没人处理,应该处于卡死状态 ...

  5. Bootstrap 模态对话框只加载一次 remote 数据的解决办法 转载

    http://my.oschina.net/qczhang/blog/190215 摘要 前端框架 Bootstrap 的模态对话框,可以使用 remote 选项指定一个 URL,这样对话框在第一次弹 ...

  6. Silverlight浮动窗体 floatablewindow 非模态对话框

    1.http://www.cnblogs.com/yinxiangpei/articles/2613913.html 说明:Silverlight的ChildWindow组件给我们的开发带来了便利,比 ...

  7. TMsgThread, TCommThread -- 在delphi线程中实现消息循环

    http://delphi.cjcsoft.net//viewthread.php?tid=635 在delphi线程中实现消息循环 在delphi线程中实现消息循环 Delphi的TThread类使 ...

  8. TMsgThread, TCommThread -- 在delphi线程中实现消息循环(105篇博客,好多研究消息的文章)

    在delphi线程中实现消息循环 在delphi线程中实现消息循环 Delphi的TThread类使用很方便,但是有时候我们需要在线程类中使用消息循环,delphi没有提供.   花了两天的事件研究了 ...

  9. 安卓中的消息循环机制Handler及Looper详解

    我们知道安卓中的UI线程不是线程安全的,我们不能在UI线程中进行耗时操作,通常我们的做法是开启一个子线程在子线程中处理耗时操作,但是安卓规定不允许在子线程中进行UI的更新操作,通常我们会通过Handl ...

随机推荐

  1. Codeforce 221 div1

    A 只要打个表就能发现,1,6,8,9的所有排列就可以产生0~6的余数了... 所以...走不下去的时候一定要打表... #define rep(i,n) for(int i=0 ; i<(n) ...

  2. Java语言实现简单FTP软件------>上传下载队列窗口的实现(七)

    1.首先看一下队列窗口的界面 2.看一下上传队列窗口的界面 3.看一下下载队列窗口的界面 4.队列窗口的实现 package com.oyp.ftp.panel.queue; import stati ...

  3. jQuery简单的Ajax调用示例

    jQuery确实方便,下面做个简单的Ajax调用: 建立一个简单的html文件: <!DOCTYPE HTML> <html> <head> <script ...

  4. hdu 4277 USACO ORZ(dfs+剪枝)

    Problem Description Like everyone, cows enjoy variety. Their current fancy is new shapes for pasture ...

  5. WINDOWS BITLOCK

    BitLocker是什么? Windows BitLocker驱动器加密通过加密Windows操作系统卷上存储的所有数据可以更好地保护计算机中的数据.BitLocker使用TPM帮助保护Windows ...

  6. mysql中pager命令妙用

    pager命令的妙用在mysql,可以大大提高工作效率. 一 当处理大量数据时,不想显示查询的结果,而只需知道查询话费的时间. mysql> select *   from t3; +----- ...

  7. android studio 更改快捷键为eclipse中习惯的方式

    虽然之前看了不少android studio的快捷键,但主要开发依然还是在eclipse上,仍然不习惯android studio的快捷键方式,今天看一视频说可以改快捷键为eclipse的方式,不由得 ...

  8. 福昕阅读器drm加密解密总结

    drm是数字版权保护的一种方式,前一段时间在做四川文轩数字图书馆项目的时候用到了相关的知识,感觉这东西对于一些在线阅读和视频播放还是有很大用处的. 对于其工作原理我也很好奇,先摘抄度娘的内容如下,当然 ...

  9. 使用Mina框架开发 QQ Android 客户端

    Apache MINA是一个网络应用程序框架,用来帮助用户简单地开发高性能和高可靠性的网络应用程序.它提供了一个通过Java NIO在不同的传输例如TCP/IP和UDP/IP上抽象的事件驱动的异步AP ...

  10. Java基础知识强化58:经典排序之二叉树排序(BinaryTreeSort)

    1. 二叉树排序 二叉树排序的描述也是一个递归的描述, 所以二叉树排序的构造自然也用递归的: 二叉排序树或者是一棵空树,或者是具有下列性质的二叉树: (1)若左子树不空,则左子树上所有结点的值均小于它 ...