代码之家  ›  专栏  ›  技术社区  ›  AshleysBrain

MFC-显示模式对话框时调暗主窗口

  •  5
  • AshleysBrain  · 技术社区  · 14 年前

    我有一个相当标准的MFC应用程序,它由一个主窗口组成,偶尔会出现模式对话框。我们都知道,在模式对话框关闭之前,任何事情都不能在它之外进行。

    因此,一个很好的UI特性是将对话框后面的主窗口的其余部分“调暗”,以便直观地指示在完成模式对话框之前不能使用它。一些Web应用程序和java/MAC应用程序都是这样做的,但我从未见过它在传统C++/MFC应用程序中完成。我想试一试,即使这个平台不寻常。

    怎么能做到?应用程序中有几个模式对话框,用于此模式:

    // pMainFrame is available as a pointer to the CWnd of the main window
    CMyDialog dialog;
    dialog.DoModal(); // invoke modal dialog; returns after dialog closed
    

    编辑:我已经发布了一个基于oystein的答案的解决方案,但我正在启动一个悬赏计划,以防有人可以改进它-特别是平滑淡入/淡出。

    3 回复  |  直到 14 年前
        1
  •  14
  •   Oystein    14 年前

    可以在要变暗的窗口顶部创建另一个完全黑色的窗口,并使用 SetLayeredWindowAttributes

    编辑:我黑了一个例子-但请注意,我不是一个MFC开发人员,我通常直接使用Windows API。不过,看起来效果不错。 Here 是一个垃圾箱。你可以自己添加淡入等。另外请注意,这会使整个屏幕变暗,如果不希望出现这种行为,则必须调整我的变暗窗口的大小。请参见代码注释。

    /**********************************************************************************************
    
        MFC screen dim test
            :: oystein          :: November 2010
    
        Creates a simple window - click it to toggle whether a translucent black "dimmer" window 
        is shown. The dimmer-window covers the entire screen, but the taskbar ("superbar" in 
        Windows 7) will jump on top of it if clicked - it seems. Simple suggestions to fix that
        are welcome.
    
        Should work on Windows 2000 and later. 
    
        Disclaimer: This is my first MFC program ever, so if anything seems wrong, it probably is.
        I have previously only coded with pure Win32 API, and hacked this together using online
        tutorials. Code provided "as-is" with no guarantees - I can not be held responsible for 
        anything bad that happens if you run this program.
    
    ***********************************************************************************************/
    
    #include "stdafx.h"
    
    #undef WINVER
    #define WINVER 0x500 // Windows 2000 & above, because of layered windows
    
    
    // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    //
    //                       Black window used to dim everything else 
    //
    class CDimWnd : public CFrameWnd
    {               
    public: 
        CDimWnd()
        {
            // Get screen res into rect
            RECT rc;
            GetDesktopWindow()->GetWindowRect(&rc);
    
            CreateEx(WS_EX_LAYERED |        // Layered window for translucency
                     WS_EX_TRANSPARENT |    // Click through
                     WS_EX_TOPMOST |        // Always on top
                     WS_EX_TOOLWINDOW,      // Do not appear in taskbar & similar
                     NULL, TEXT(""), 
                     WS_POPUP,              // No frame/borders - though there is 
                                            // still some border left - we'll remove 
                                            // it with regions
    
                     0, 0, rc.right + 10, rc.bottom + 10, // Make the window 10px larger 
                                                          // than screen resolution in both 
                                                          // directions - it is still positioned 
                                                          // at 0,0
                     NULL, NULL);
    
            // Grab a part of the window the size of the desktop - but 5px into it  
            // Because the window is larger than the desktop res, the borders are removed 
            CRgn rgn;                         
            rgn.CreateRectRgn(rc.left + 5, rc.top + 5, rc.right + 5, rc.bottom + 5);
            SetWindowRgn((HRGN)rgn, FALSE);
            rgn.Detach();                               
    
            // We have to reposition window - (0,0) of window has not changed
            SetWindowPos(NULL, -5, -5, 0, 0, SWP_NOSIZE | SWP_NOZORDER);        
    
            // This is where we set the opacity of the window: 0-255
            SetLayeredWindowAttributes(RGB(0,0,0), 150, LWA_ALPHA);                     
        }
        void Close()
        {
            CFrameWnd::OnClose();
        }
        BOOL CDimWnd::OnEraseBkgnd(CDC* pDC); // Set BKG color
        DECLARE_MESSAGE_MAP()
    };
    
    BOOL CDimWnd::OnEraseBkgnd(CDC* pDC)
    {
        // Set brush to desired background color
        CBrush backBrush(RGB(0, 0, 0));
    
        // Save old brush
        CBrush* pOldBrush = pDC->SelectObject(&backBrush);
    
        CRect rect;
        pDC->GetClipBox(&rect);     // Erase the area needed
    
        pDC->PatBlt(rect.left, rect.top, rect.Width(), rect.Height(), PATCOPY);
        pDC->SelectObject(pOldBrush);   
        return TRUE;
    }
    
    BEGIN_MESSAGE_MAP(CDimWnd, CFrameWnd)
        ON_WM_ERASEBKGND()
    END_MESSAGE_MAP()
    
    // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    
    
    // Global variable - is screen dimmed?
    bool g_bIsDimmed = false;
    
    
    // The main window
    class CMainWnd : public CFrameWnd
    {     
        // Contains a CDimWnd - I'm not sure if this is the "MFC way" of doing things
        CDimWnd dimmer; 
    
    public: 
        CMainWnd()
        {
            Create(NULL, TEXT("Screen dimmer - Press left mouse button on window to toggle"), 
                WS_OVERLAPPEDWINDOW, CRect(50, 50, 400, 250));
        }
        // Left mouse button toggles dimming
        afx_msg void OnLButtonDown(UINT Flags, CPoint Point)
        {
            if(!g_bIsDimmed)
            {
                dimmer.ShowWindow(SW_SHOW);
                dimmer.BringWindowToTop();          
                g_bIsDimmed = true;
            }
            else
            {           
                dimmer.ShowWindow(SW_HIDE);     
                g_bIsDimmed = false;
            }
        }
        DECLARE_MESSAGE_MAP()
    };
    
    BEGIN_MESSAGE_MAP(CMainWnd, CFrameWnd)
        ON_WM_LBUTTONDOWN()
    END_MESSAGE_MAP()
    
    
    // The app
    class CApp : public CWinApp
    {
    public:         
        virtual BOOL InitInstance();
    };
    
    BOOL CApp::InitInstance()
    {               
        m_pMainWnd = new CMainWnd();              
        m_pMainWnd->ShowWindow(m_nCmdShow);           
        m_pMainWnd->UpdateWindow();        
        return TRUE;
    }
    
    CApp HelloApp;
    

    更新:

    要使用它,请使主窗口包含一个较暗的窗口

    class CMainFrm : public CFrameWnd
    {     
        CDimWnd* dimmer; 
    
    public: 
        CMainFrm()
        {
            // constructor code here ...
            dimmer = new CDimWnd();         
        }
    
    // rest of class ...
    
    };  
    

    然后可以这样使用:

    dimmer->Show();
    MessageBox(TEXT("Hello world"));
    dimmer->Hide();
    

    或者我想你可以把这个密码( Show() Hide() 调用),如果您希望将代码保留在模态对话框的构造函数和析构函数中。如果您想要一个“scope”dim,就像您发布的示例一样,这段代码必须进入CDimWnd类的构造函数和析构函数中,并且您需要一个静态成员变量之类的东西来确保一次只运行一个dimmer(除非您想使用全局变量)。

    对于调光器窗口-我做到了:

    CDimWnd.h公司

    #define TARGET_OPACITY 70   // Target opacity 0-255 for dimmed window
    #define FADE_TIME 20        // Time between each fade step in milliseconds
    #define FADE_STEP 5      // How much to add to/remove from opacity each fade step
    #define ID_FADE_TIMER 1
    
    // Call Show() and Hide() to fade in/fade out dimmer. 
    // Creates the dimmer window in constructor.
    class CDimWnd : public CFrameWnd
    {         
        bool m_isDimming;       
    
    public: 
        CDimWnd();
        void Show();
        void Hide();            
    
    protected:
        BOOL OnEraseBkgnd(CDC* pDC);
        void OnTimer(UINT_PTR nIDEvent);
        DECLARE_MESSAGE_MAP()
    };
    

    CDimWnd.cpp

    #include "stdafx.h"
    #include "CDimWnd.h"
    #include "MainFrm.h"
    
    BEGIN_MESSAGE_MAP(CDimWnd, CFrameWnd)
        ON_WM_ERASEBKGND()
    END_MESSAGE_MAP()
    
    CDimWnd::CDimWnd()
    {
        // Get the main frame of the application which we want to dim.
        CMainFrame* pParent = theApp.pMainFrame;
    
        // Don't do anything if the main frame doesn't appear to be there
        if (pParent != NULL)
        {
            // Get the client area of the window to dim.
            CRect rc;
            pParent->GetClientRect(&rc);
            pParent->ClientToScreen(&rc);       // convert to screen coordinates
    
            // Do some fudging to fit the client area exactly.
            // Other applications may not need this if the above client area fits already.
            rc.top += GetSystemMetrics(SM_CYFRAME);
            rc.top += GetSystemMetrics(SM_CYCAPTION);           // MFC feature pack seems to include caption in client area
            rc.left -= GetSystemMetrics(SM_CXBORDER);
            rc.right += GetSystemMetrics(SM_CXBORDER) + 1;
            rc.bottom += GetSystemMetrics(SM_CYBORDER) + 1;
    
            // Create a layered window for transparency, with no caption/border.
            CreateEx(WS_EX_LAYERED | WS_EX_TRANSPARENT | WS_EX_TOOLWINDOW, NULL, TEXT(""), 
                WS_POPUP, rc.left, rc.top, rc.Width(), rc.Height(),
                pParent->GetSafeHwnd(), NULL);
        }
    }
    
    
    void CDimWnd::Show()
    {
        // If we are not already dimming, go for it
        if(!m_isDimming)
        {
            // Bring in front of main window.
            BringWindowToTop();
    
            // Set opacity to 0
            SetLayeredWindowAttributes(RGB(0,0,0), 0, LWA_ALPHA);
    
            // Show the dimmer window
            ShowWindow(SW_SHOW);
    
            // Create timer - the rest is handled in OnTimer() function
            SetTimer(ID_FADE_TIMER, FADE_TIME, NULL);
        }
    }
    
    
    void CDimWnd::Hide()
    {   
        // If we are dimming, go for it
        if(m_isDimming)
        {
            // Create timer - the rest is handled in OnTimer() function
            SetTimer(ID_FADE_TIMER, FADE_TIME, NULL);
        }
    }
    
    
    void CDimWnd::OnTimer(UINT_PTR nIDEvent)
    {
        static int fade = 0;
    
        if(nIDEvent == ID_FADE_TIMER)
        {
            // We are dimming => we want to fade out
            if(m_isDimming)
            {
                if(fade < 0)
                {
                    // Fading finished - hide window completely, update status & destroy timer
                    fade = 0;
                    ShowWindow(SW_HIDE);
                    KillTimer(nIDEvent);
                    m_isDimming = false;
                }
                else
                {
                    // Set window opacity & update fade counter
                    SetLayeredWindowAttributes(RGB(0,0,0), fade, LWA_ALPHA);
                    fade -= FADE_STEP;
                }
            }
            else
            // fade in
            {
                if(fade > TARGET_OPACITY)
                {   
                    // Fading finished - destroy timer & update status
    
                    fade = TARGET_OPACITY; // but first, let's be accurate.
                    SetLayeredWindowAttributes(RGB(0,0,0), fade, LWA_ALPHA);
    
                    KillTimer(nIDEvent);
                    m_isDimming = true;             
                }   
                else
                {
                    // Set window opacity & update fade counter
                    SetLayeredWindowAttributes(RGB(0,0,0), fade, LWA_ALPHA);
                    fade += FADE_STEP;
                }
            }
        }
    }
    
    
    BOOL CDimWnd::OnEraseBkgnd(CDC* pDC)
    {
        // Fill with black
        CBrush backBrush(RGB(0, 0, 0));
        CBrush* pOldBrush = pDC->SelectObject(&backBrush);
    
        CRect rect;
        pDC->GetClipBox(&rect);     // Erase the area needed
        pDC->PatBlt(rect.left, rect.top, rect.Width(), rect.Height(), PATCOPY);
    
        pDC->SelectObject(pOldBrush);   
        return TRUE;
    }
    

    可以。正如我所说的,这是相当快地组合在一起的,并且处于一个粗糙的状态,但是它应该给你一些代码来工作,以及如何(我认为)在MFC中使用计时器的一般概念。不过,我绝对不适合考虑这个问题:)

        2
  •  4
  •   AshleysBrain    14 年前

    我已经接受了oystein的答案,因为它让我找到了解决方案,但我想我会张贴我的修改。为了让它对我有用,我不得不对它稍加修改,这样它可能对其他人有用。

    据记录,调光效果很好,但看起来不像我希望的那么自然。在一个经常出现对话框的应用程序中,调光在其似乎打开和关闭主窗口的规律性上变得令人分心。为了折衷,我已经将调暗设置得相当微妙(大约25%的不透明度),它可以柔和地突出显示活动对话框;即时调暗仍然有点分散注意力,但我不确定如何使其平滑地淡入或淡出,特别是当设置了范围时。

    另外,我不是UI专家,但是暗显给我的印象是对话框与它后面的窗口内容关系不大。这使它感觉有点脱离了我在应用程序中所做的工作,即使对话框直接操作该内容。这可能是另一个干扰。

    不管怎样,这里是:

    CDimWnd.h公司

    // Dim the application main window over a scope.  Creates dimmer window in constructor.
    class CDimWnd : public CFrameWnd
    {               
    public: 
        CDimWnd();
        BOOL OnEraseBkgnd(CDC* pDC);
    
        ~CDimWnd();
    
    protected:
        DECLARE_MESSAGE_MAP()
    };
    

    CDimWnd.cpp

    #include "stdafx.h"
    #include "CDimWnd.h"
    #include "MainFrm.h"
    
    BEGIN_MESSAGE_MAP(CDimWnd, CFrameWnd)
        ON_WM_ERASEBKGND()
    END_MESSAGE_MAP()
    
    // For preventing two dimmer windows ever appearing
    bool is_dimmer_active = false;
    
    CDimWnd::CDimWnd()
    {
        // Get the main frame of the application which we want to dim.
        CMainFrame* pParent = theApp.pMainFrame;
    
        // Don't do anything if the main frame doesn't appear to be there,
        // or if there is already dimming happening.
        if (pParent != NULL && !is_dimmer_active)
        {
            // Get the client area of the window to dim.
            CRect rc;
            pParent->GetClientRect(&rc);
            pParent->ClientToScreen(&rc);       // convert to screen coordinates
    
            // Do some fudging to fit the client area exactly.
            // Other applications may not need this if the above client area fits already.
            rc.top += GetSystemMetrics(SM_CYFRAME);
            rc.top += GetSystemMetrics(SM_CYCAPTION);           // MFC feature pack seems to include caption in client area
            rc.left -= GetSystemMetrics(SM_CXBORDER);
            rc.right += GetSystemMetrics(SM_CXBORDER) + 1;
            rc.bottom += GetSystemMetrics(SM_CYBORDER) + 1;
    
            // Create a layered window for transparency, with no caption/border.
            CreateEx(WS_EX_LAYERED | WS_EX_TRANSPARENT | WS_EX_TOOLWINDOW, NULL, TEXT(""), 
                WS_POPUP, rc.left, rc.top, rc.Width(), rc.Height(),
                pParent->GetSafeHwnd(), NULL);
    
            // Bring in front of main window.
            BringWindowToTop();
    
            // Apply 25% opacity
            SetLayeredWindowAttributes(RGB(0,0,0), 64, LWA_ALPHA);
    
            // Show the dimmer window
            ShowWindow(SW_SHOW);
    
            is_dimmer_active = true;
        }
    }
    
    CDimWnd::~CDimWnd()
    {
        is_dimmer_active = false;
    }
    
    BOOL CDimWnd::OnEraseBkgnd(CDC* pDC)
    {
        // Fill with black
        CBrush backBrush(RGB(0, 0, 0));
        CBrush* pOldBrush = pDC->SelectObject(&backBrush);
    
        CRect rect;
        pDC->GetClipBox(&rect);     // Erase the area needed
        pDC->PatBlt(rect.left, rect.top, rect.Width(), rect.Height(), PATCOPY);
    
        pDC->SelectObject(pOldBrush);   
        return TRUE;
    }
    

    构造器 CDimWnd dimmer 作为对话框类的成员,它会自动使主窗口变暗,无论从何处调用对话框。

    也可以在范围内使用它来暗显系统模式对话框:

    {
        CDimWnd dimmer;
        MessageBox(...);
    }
    
        3
  •  3
  •   Richard Harrison    14 年前

    我忍不住要这么做。

    这是您的代码,添加了计时器并实现了淡入/淡出。此外,我改变了使用中灰色而不是黑色的模糊块。

    // DimWnd.h : header file
    #pragma once
    
    class CDimWnd : public CFrameWnd
    {
    public:
        CDimWnd(class CWnd * pParent);
        virtual ~CDimWnd();
        BOOL OnEraseBkgnd(CDC* pDC);
        int opacity, opacity_increment;
    protected:
        DECLARE_MESSAGE_MAP()
    
    public:
        afx_msg void OnTimer(UINT_PTR nIDEvent);
        void fadeOut();
    };
    

    // DimWnd.cpp : implementation file
    //
    
    #include "stdafx.h"
    #include "dimmer.h"
    #include "DimWnd.h"
    #include "MainFrm.h"
    #include <math.h>
    
    const int TIMER_ID = 111;
    
    // For preventing two dimmer windows ever appearing
    bool is_dimmer_active = false;
    
    // constants to control the fade.
    int    ticks_per_second  = 1000; // ms
    int    start_opacity     = 44;   // 20%
    int    max_opacity       = 220;  // 0->255
    double fade_in_duration  = 4;    // seconds to fade in  (appear)
    double fade_out_duration = 0.2;    // seconds to fade out (disappear)
    int    rate              = 100;  // Timer rate (ms
    
    
    CDimWnd::CDimWnd(CWnd * pParent)
    {
        // Don't do anything if the main frame doesn't appear to be there,
        // or if there is already dimming happening.
        if (pParent != NULL && !is_dimmer_active)
        {
            // Get the client area of the window to dim.
            CRect rc;
            pParent->GetClientRect(&rc);
            pParent->ClientToScreen(&rc);       // convert to screen coordinates
    
            // Do some fudging to fit the client area exactly.
            // Other applications may not need this if the above client area fits already.
            rc.top += GetSystemMetrics(SM_CYFRAME);
            rc.top += GetSystemMetrics(SM_CYCAPTION);           // MFC feature pack seems to include caption in client area
            rc.left -= GetSystemMetrics(SM_CXBORDER);
            rc.right += GetSystemMetrics(SM_CXBORDER) + 1;
            rc.bottom += GetSystemMetrics(SM_CYBORDER) + 1;
    
            // Create a layered window for transparency, with no caption/border.
            CreateEx(WS_EX_LAYERED | WS_EX_TRANSPARENT | WS_EX_TOOLWINDOW, NULL, TEXT(""), 
                WS_POPUP, rc.left, rc.top, rc.Width(), rc.Height(),
                pParent->GetSafeHwnd(), NULL);
    
            // Bring in front of main window.
            BringWindowToTop();
    
            // Show the dimmer window
            ShowWindow(SW_SHOW);
    
    
            double increment_per_second = ((max_opacity - start_opacity) / fade_in_duration);
            opacity_increment = ceil(  increment_per_second / (ticks_per_second / rate) ) ;
    
            is_dimmer_active = true;
            opacity = start_opacity;
    
            SetLayeredWindowAttributes(RGB(0,0,0), opacity, LWA_ALPHA);
    
            SetTimer(TIMER_ID, rate,NULL);  
    
        }
    }
    
    CDimWnd::~CDimWnd()
    {
        fadeOut(); // fade the window out rather than just disappearing.
        is_dimmer_active = false;
    }
    
    void CDimWnd::fadeOut()
    {
        // can't use timers as may be in the process of being destroyed so make it quick..
    
        double increment_per_second = ((opacity - start_opacity) / fade_out_duration);
        opacity_increment = ceil(  increment_per_second / (ticks_per_second / rate) ) ;
    
        while(opacity  > start_opacity)
        {
            opacity -= opacity_increment;
            SetLayeredWindowAttributes(RGB(0,0,0), opacity, LWA_ALPHA);
            Sleep(100);
        }
    }
    
    BOOL CDimWnd::OnEraseBkgnd(CDC* pDC)
    {
        // Fill with midgray
        CBrush backBrush(RGB(128,128,128));
        CBrush* pOldBrush = pDC->SelectObject(&backBrush);
    
        CRect rect;
        pDC->GetClipBox(&rect);     // Erase the area needed
        pDC->PatBlt(rect.left, rect.top, rect.Width(), rect.Height(), PATCOPY);
    
        pDC->SelectObject(pOldBrush);   
        return TRUE;
    }
    
    BEGIN_MESSAGE_MAP(CDimWnd, CFrameWnd)
        ON_WM_ERASEBKGND()
        ON_WM_TIMER()
    END_MESSAGE_MAP()
    
    void CDimWnd::OnTimer(UINT_PTR nIDEvent)
    {
        if (opacity >= max_opacity) 
        {
            // stop the timer when fade in finished.
            KillTimer(TIMER_ID);
            return;
        }
    
        opacity += opacity_increment;
        SetLayeredWindowAttributes(RGB(0,0,0), opacity, LWA_ALPHA);
    
        CFrameWnd::OnTimer(nIDEvent);
    }