65.9K
CodeProject 正在变化。 阅读更多。
Home

带客户端阴影的动画对话框菜单

starIconstarIconstarIconemptyStarIconemptyStarIcon

3.00/5 (12投票s)

2003年10月24日

6分钟阅读

viewsIcon

77714

downloadIcon

1223

本文介绍了如何在任何 CWnd 上创建动画菜单。

Sample Image - AnimatedDialogMenu.gif

引言

你见过没有菜单项,或者只有一个弹出项的菜单吗?例如,VC 标准创建的帮助菜单只有“关于”一项。一些应用程序以非常基本的菜单面板开始。为什么不显示单个菜单项背后的对话框,而不是菜单项本身。这就是我带来的新想法!

工作原理

在 Windows 98、NT5 及更高版本上制作动画对话框菜单非常简单,而解决旧系统上的问题则复杂得多。这是我的代码的第一个版本,肯定有很多“bug”。我希望它对某人有用。

关键在于 Windows API 中深藏的 AnimateWindow 函数。在使用“初步”Windows API 函数时,一个好主意是在任何头文件包含到应用程序代码之前添加这两行:

   #undef     WINVER
   #define     WINVER 1000000


嗯,这并不是一个真正的好主意,但对于 Visual C++ 6 配合某些服务包和 Windows XP Pro SP1 来说,这是唯一的选择(而且,它确实有效!)。你应该首先检查 AnimateWindow 的调用是否在没有这些宏的情况下工作。WINVER 是 VC 定义的一个宏,用于识别你正在为其构建的 Windows 版本。它被预定义,API 声明在需要某些兼容性信息时会引用它。编译时,你会收到关于如何进行稳定构建的通知。

我创建了一个基于对话框的应用程序,它本身包含一个充当菜单栏的子对话框(名为 CDummy)。菜单栏对话框通过弹出对话框来处理用户请求。它还负责绘制效果以及大部分的清理/重绘操作。简单的按钮控制弹出操作。我使用的一个小技巧是,按钮的 ID 是连续的 1026、1027、1028,这样我就可以根据需要通过偏移量来访问它们。这在演示项目中并未实现。

绘制菜单的代码非常简单(而且速度慢),最好的解释就是直接阅读代码。

void CDummy::ShowDialogMenu(int number, int initial_control)
{
    HideAllDialogMenus();
    if(!statMenu[number])
    {
        CRect rc;
        GetDlgItem(initial_control)->GetClientRect(&rc);
        GetDlgItem(initial_control)->ClientToScreen(&rc);
        this->ScreenToClient(&rc);

        listMenu[number]->SetWindowPos(0,rc.left,rc.bottom,
                    0,0,SWP_NOSIZE|SWP_HIDEWINDOW);
        AnimateWindow(listMenu[number]->GetSafeHwnd(),300,
            AW_ACTIVATE|AW_SLIDE|AW_VER_POSITIVE|AW_HOR_POSITIVE);
        CWindowDC dc(0);
        CDC tdc;
        statMenu[number]=1;
        listMenu[number]->GetClientRect(&rc);
        listMenu[number]->ClientToScreen(&rc);
        dc.MoveTo(rc.left,rc.top);
        dc.LineTo(rc.right,rc.top);
        dc.LineTo(rc.right,rc.bottom);
        dc.LineTo(rc.left,rc.bottom);
        dc.LineTo(rc.left,rc.top);

        tdc.CreateCompatibleDC(&dc);
        CBitmap map1;
        map1.CreateCompatibleBitmap(&dc,1600,1200);
        tdc.SelectObject(&map1);
        tdc.BitBlt(rc.left,rc.top,rc.right+S_SHADE,
                    rc.bottom+S_SHADE,&dc,rc.left,rc.top,SRCCOPY);

        for(int w=rc.right;w<=rc.right+S_SHADE;w++)
        {
            float f=((float)w-rc.right)/S_SHADE;
            for(int e=rc.top+S_SHADE;e< rc.bottom;e++)
            {
                tdc.SetPixel(w,e,RGB(
                    GetRValue(tdc.GetPixel(w,e))/(2-f),
                    GetGValue(tdc.GetPixel(w,e))/(2-f),
                    GetBValue(tdc.GetPixel(w,e))/(2-f)));
            }
        }
        for(int e=rc.bottom;e<=rc.bottom+S_SHADE;e++)
        {
            float f=((float)e-rc.bottom)/S_SHADE;
            for(w=rc.left+S_SHADE;w<=rc.right+1;w++)
            {
                tdc.SetPixel(w,e,RGB(
                GetRValue(tdc.GetPixel(w,e))/(2-f),
                GetGValue(tdc.GetPixel(w,e))/(2-f),
                GetBValue(tdc.GetPixel(w,e))/(2-f)));
            }
        }
        for(e=rc.bottom;e<=rc.bottom+S_SHADE-2;e++)
        {
            float f=((float)e-rc.bottom)/S_SHADE;
            for(w=rc.right+2;w<=rc.right+S_SHADE-2;w++)
            {
                float g=((float)w-rc.right+2)/(S_SHADE);
                tdc.SetPixel(w,e,RGB(
                    GetRValue(tdc.GetPixel(w,e))/(2-(f+g)/2),
                    GetGValue(tdc.GetPixel(w,e))/(2-(f+g)/2),
                    GetBValue(tdc.GetPixel(w,e))/(2-(f+g)/2)));
            }
        }

        dc.BitBlt(rc.left,rc.top,rc.right+S_SHADE,rc.bottom+S_SHADE,
                    &tdc,rc.left,rc.top,SRCCOPY);
    }
}

阴影完全是所有者绘制的,它直接绘制在桌面窗口上。这会导致一些清理重绘问题,但对于阴影来说没问题。我很快就会更新为弹出窗口,那时会更有意义。然而,如果你关心效率或绘图安全,你可以简单地使用 CClientDC 进行绘制。隐藏菜单例程只是一个重绘。

值得一提的是,你可以通过修改 AnimateWindow 中的标志来更改动画类型。例如,将 AW_ACTIVATE | AW_SLIDE | AW_VER_POSITIVE | AW_HOR_POSITIVE 更改为 AW_ACTIVATE | AW_SLIDE | AW_HOR_POSITIVEAW_ACTIVATE | AW_SLIDE | AW_VER_POSITIVE 或 MSDN 中 AnimateWindow 定义的其他内容。

在你的应用程序中使用代码

我没有尝试创建一个类来实现这个功能,因为我想保持用户控制对话框的自定义方式。我很乐意在此接受建议。总之,我设法将以下步骤分开,这应该能让你在应用程序中更轻松地使用代码:

  • 创建你想要显示为菜单的所有对话框。建议使用 CDialog 继承的对话框。在属性中选择“child”(子对话框),勾选“control”(控件)和“visible”(可见),并设置“foreground”(前台)。如果你不指定任何这些样式,菜单可能无法正确绘制。它们都与 Z 顺序和父窗口管理有关,我不确定具体原因。你知道哪里可以找到细节,对吧。哦,别忘了在你的 CDummy 源文件中包含正确的头文件,以便创建实例。
  • 创建一个菜单栏对话框,比如 CDummy。这个对话框继承自 CDialog,但它也可以是任何基于 CWnd 的东西。对于 SDK 爱好者来说,你可以使用直接句柄,并进行一些修改来消除 MFC 设备上下文。在你的主窗口类中,声明一个 CDummy 的实例,并在 OnInitDialog() (例如)中像这样初始化它:
        dummy=new CDummy(this);
        dummy->Create(IDD_DIALOG2,this);
        dummy->SetWindowPos(0,0,0,0,0,SWP_NOSIZE|SWP_SHOWWINDOW);
    
  • 声明数组来存储你的对话框指针和当前状态(在演示项目中:listMenustatMenu)。创建你的菜单对话框实例,并在 WM_INITDIALOG 处理程序中放入初始化代码,像这样:
    BOOL CDummy::OnInitDialog() 
    {
        CDialog::OnInitDialog();
        
        memset(listMenu,0,sizeof(listMenu));
        menu1.Create(IDD_DIALOG1,this->GetParent());
        menu1.SetWindowPos(0,-1000,-1000,0,0,SWP_NOSIZE|SWP_SHOWWINDOW);
    
        menu2.Create(IDD_DIALOG3,this->GetParent());
        menu2.SetWindowPos(0,-1000,-1000,0,0,SWP_NOSIZE|SWP_SHOWWINDOW);
    
        menu3.Create(IDD_DIALOG4,this->GetParent());
        menu3.SetWindowPos(0,-1000,-1000,0,0,SWP_NOSIZE|SWP_SHOWWINDOW);
    
        menu4.Create(IDD_DIALOG5,this->GetParent());
        menu4.SetWindowPos(0,-1000,-1000,0,0,SWP_NOSIZE|SWP_SHOWWINDOW);
    
        listMenu[0]=&menu1;
        listMenu[1]=&menu2;
        listMenu[2]=&menu3;
        listMenu[3]=&menu4;
    
        return TRUE;  // return TRUE unless you set the focus to a control
                      // EXCEPTION: OCX Property Pages should return FALSE
    }
  • 触发菜单显示或隐藏的事件必须发生在 CDummy 中。放入一些按钮或其他控件并处理事件。在演示项目中,我使用了简单的按钮。这是我处理其中一个按钮的 BN_CLICKED 消息的方式:
     void CDummy::OnButton1() 
    {
        ShowDialogMenu(0,IDC_BUTTON1);
    }
    ShowDialogMenu(int arg1,int arg2) 会在屏幕上显示对话框。arg1 表示应该显示哪个对话框,例如,0 表示显示 listMenu[0] 引用的对话框。arg2 是对话框将要绘制的控件(静态文本、按钮、编辑框、复选框,等等)旁边的 ID。该函数获取控件的客户端矩形,并在矩形下方绘制对话框。
  • 在主窗口中处理 WM_KILLFOCUS ,这是所有菜单对话框都必须是子对话框的原因之一。
    void CDmenuDlg::OnKillFocus(CWnd* pNewWnd) 
    {
        CDialog::OnKillFocus(pNewWnd);
        
        dummy->HideAllDialogMenus();
    
    }
    
  • 在主窗口中处理 WM_ACTIVATE,这种方式可以修复“失去焦点后的重绘”问题,但并非总是有效(见下文)。
    void CDmenuDlg::OnActivate(UINT nState, CWnd* pWndOther, 
        BOOL bMinimized) 
    {
        CDialog::OnActivate(nState, pWndOther, bMinimized);
        dummy->HideAllDialogMenus();
        Invalidate();
    }
    

已知问题

Visual C++ 或我的代码中有几个 bug。

  • 有时 AnimateWindow 不起作用,但返回 ERROR_SUCCESS。演示项目应用程序永远不会发生这种情况,但你的应用程序可能没那么幸运。我还没有找出导致问题的原因,但它适用于所有初步函数。
  • 点击主窗口上的控件不会使菜单消失,而是会覆盖在菜单上绘制控件。要修复这个问题,你可以使用消息反射或手动将消息转发到主窗口,但这需要对主窗口上的所有控件都这样做。我正在研究另一个解决方案。
  • 如果一个菜单处于活动状态,而应用程序失去了焦点,将会发生不正确的重绘。最后一个问题对我来说是个谜!
  • 在 Windows 98 和 ME 上,并行任务配合得不是很好,我的意思是它们由于糟糕的 OS 线程而运行缓慢。不要让应用程序同时处理太多活动线程。

五角大楼雇佣我来做这个项目。他们需要一些软件来附加到控制界面。我没有包含你看到的驱动程序,但我可能不小心删除了/遗漏了一些其他代码。保持警惕!

嗯,开玩笑的,但保持警惕,我可能忘记提到一些让你成功的小技巧。这是一个添加“如果这段代码有效,那就是 Vladimir Ralev 写的,否则我不知道是谁写的!”的好地方,就像 MSDN 的作者一样。

感谢所有评论和修复。

致谢

作者:Vladimir Ralev <v_ralev@yahoo.com>,2003 年 10 月 20 日

特别感谢 PJ NaughterCTreeFileCtrl & CSortedArray v1.06 上的杰出工作。我使用了(稍微修改过的)CTreeFileCtrl 来演示如何在无需弹出模态对话框的情况下轻松选择文件。

© . All rights reserved.