2010-10-31 118 views
5

暗淡時,主窗口我有一個相當標準的MFC應用程序,它由一個主窗口中,偶爾會彈出模式對話框。正如我們都知道,除非模式對話框關閉,否則無法進行任何操作。MFC - 顯示模式對話框

因此,一個漂亮的UI功能是「暗淡」的對話框後面的主窗口中的其餘部分,在視覺上表示不能使用它,直到你與模態對話框完成。一些網絡應用程序和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 

有一個簡單的辦法有)任何的DoModal(之前變暗的窗口和事後恢復?我正在使用Visual Studio 2010以防更新後的MFC具有任何可能有所幫助的功能。

編輯:我已經發布了基於奧伊斯坦的答案的解決方案,但我開始的情況下,任何人都賞金可以改善它 - 尤其是光滑的淡入/淡出。

+0

首先,什麼是灰色的?你能提供想要的行爲截圖嗎? – 2010-10-31 16:43:00

+0

在Google圖片中發現了這個問題,它是一個圍繞對話框調整頁面其餘部分的網頁:http://www.irritatedvowel.com/pub/blog/UsingBlurBehindDialogsinSilverlight3Beta_13306/image。PNG - 就像那樣,但是在對話結束時調暗應用程序的主窗口。 – AshleysBrain 2010-10-31 16:58:46

回答

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; 

UPDATE:

我砍死在一起,更多的代碼對你來說,處理衰落。我仍然沒有MFC開發,和我離開的代碼在「粗糙」的狀態(小錯誤處理,不是很健壯)給你事情做了。 :)總之,這裏的做到這一點的一種方式,我認爲是相當乾淨:

要使用它,讓你的主窗口中包含

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()通話)在模態對話框的構造函數和析構函數,如果你想保持代碼的存在。如果你想要一個「範圍」-dim,就像在你發佈的例子中那樣,這個代碼將不得不進入CDimWnd類的構造函數析構函數&,你需要類似靜態成員變量來確保只有一個調光器一次運行(除非你想使用全局變量)。

對於調光器窗口 - 我這樣做:

CDimWnd。ħ

#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中如何使用(我認爲)定時器的一般概念。雖然我絕對不是合適的人,但是:)

+0

*我通常直接使用Windows API * - 它顯示;)在MFC中,窗口創建不會進入構造函數,但是是分開的。無論如何,+1咳嗽樣品。 – peterchen 2010-11-03 08:25:47

+0

在這個特定情況下將初始化放在構造函數中是非常有用的 - 然後你有一個'scoped window dimmer',它只需要一行聲明一個'CDimWnd'來調暗一個範圍。 – AshleysBrain 2010-11-03 14:57:59

3

我接受了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在其構造函數創建本身,您只需要添加CDimWnd dimmer作爲對話框類的成員,並且它會自動調暗主窗口,無論您從哪裏調用對話框。

你也可以用它的範圍內,昏暗系統模態對話框:

{ 
    CDimWnd dimmer; 
    MessageBox(...); 
} 
+0

對於漸變,您可以在構造函數/析構函數中製作一個計時器,逐漸增加/減少不透明度。除非你想要一個單獨的線程來做這件事,否則淡入將會非常快。 – Oystein 2010-11-03 17:11:17

+0

就是這樣,淡入淡出不應該延遲對話框的外觀。在Aero淡入轉換中出現模態對話框時,是否有可能發生這種情況?如果模態對話框正在運行,定時器是否仍然運行? – AshleysBrain 2010-11-03 19:32:53

+0

捨棄最後的評論。如果你註冊一個Windows定時器,你會收到一個tick事件。如果你在事件處理程序中進行淡入淡出,它將會獨立於其他對話框而褪色,並且不會延遲任何事情的執行。 – Oystein 2010-11-03 19:46:20

2

我無法抗拒這樣做。

這是您的代碼添加了定時器並實現淡入/淡出。此外,我更改爲使用中灰色而不是黑色作爲遮蔽區塊。

您可以通過增加持續時間或增加速率來調整控制淡入淡出的常量,使其更平滑。實驗表明我爲10Hz的速度平穩的我,但情況因人而異

// 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); 
} 
+0

似乎是一個乾淨的方式來做到這一點,只要你只需要示波器模型,並且不需要很長時間的淡出。這比我的版本更簡單,當然:) – Oystein 2010-11-06 05:18:15