zoukankan      html  css  js  c++  java
  • DirectUI中模态对话框和菜单的原理

    经常有人问关于模态对话框和系统菜单内部实现原理方面的问题, 因为系统通过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
  • 相关阅读:
    WPF窗口和用户控件事件相互触发
    C#排序算法总结
    C#.NET操作数据库通用类
    在C#的WPF程序使用XAML实现画线
    centos7 用户介绍
    Linux系列2
    nginx的使用
    TCP协议、三次握手以及滑动窗口等的介绍(计算机网络基础知识)
    mysql的
    jQuery的东西
  • 原文地址:https://www.cnblogs.com/weiym/p/3006369.html
Copyright © 2011-2022 走看看