【问题标题】:"Lights off" effect of the main application window主应用程序窗口的“熄灯”效果
【发布时间】:2021-06-29 17:32:42
【问题描述】:

如何为主应用程序窗口制作“关灯”效果?我的意思是:

  • 灯亮:

  • 关灯:

UPDATE-1: 我做了自己的“熄灯”窗口实现。

算法如下:

  1. 创建一个新的隐藏子窗口(暗窗口)
  2. 创建主窗口的屏幕截图
  3. 用黑色画笔填充变暗的窗口,并使用AlphaBlend()函数将屏幕截图HDC复制到上面,并具有一定的透明度值
  4. 显示变暗的窗口。

而且效果很好。但是有一个缺点 - 当显示和隐藏变暗的窗口时,主窗口的所有子控件都会在短时间内涂上它的颜色(变暗的窗口颜色):

这个:

这是来自 .rc 文件的描述:

mainWindow DIALOGEX 0, 0, 309, 177
STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | DS_CENTER | WS_MINIMIZEBOX | WS_POPUP | WS_CLIPCHILDREN | WS_CAPTION | WS_SYSMENU
CAPTION "Dialog"
FONT 8, "MS Shell Dlg", 400, 0, 0x1
BEGIN
    DEFPUSHBUTTON   "OK",IDOK,198,156,50,14,WS_CLIPSIBLINGS
    PUSHBUTTON      "Cancel",IDCANCEL,252,156,50,14,WS_CLIPSIBLINGS
    CONTROL         "",IDC_TAB1,"SysTabControl32",0x0,7,4,204,111,WS_CLIPSIBLINGS
    PUSHBUTTON      "Button1",IDC_BUTTON1,228,18,22,17,WS_CLIPSIBLINGS
    PUSHBUTTON      "Button2",IDC_BUTTON2,262,43,32,19,WS_CLIPSIBLINGS
    EDITTEXT        IDC_EDIT1,216,46,35,15,ES_AUTOHSCROLL | WS_CLIPSIBLINGS
    LTEXT           "Static",IDC_STATIC1,223,86,59,11,WS_CLIPSIBLINGS
    LTEXT           "Static",IDC_STATIC2,7,119,36,13,WS_CLIPSIBLINGS
    CONTROL         "Check1",IDC_CHECK1,"Button",BS_AUTOCHECKBOX | WS_CLIPSIBLINGS | WS_TABSTOP,45,121,56,8
    CONTROL         "Check2",IDC_CHECK2,"Button",BS_AUTOCHECKBOX | WS_CLIPSIBLINGS | WS_TABSTOP,110,119,36,13
    CONTROL         "Check3",IDC_CHECK3,"Button",BS_AUTOCHECKBOX | WS_CLIPSIBLINGS | WS_TABSTOP,127,137,44,11
    PUSHBUTTON      "Button3",IDC_BUTTON3,110,154,55,16,WS_CLIPSIBLINGS
    EDITTEXT        IDC_EDIT2,232,72,40,14,ES_AUTOHSCROLL|WS_CLIPSIBLINGS
    CONTROL         "",IDC_SLIDER1,"msctls_trackbar32",TBS_BOTH | TBS_NOTICKS | WS_TABSTOP | WS_CLIPSIBLINGS,220,106,69,15
    CONTROL         "Radio1",IDC_RADIO1,"Button",BS_AUTORADIOBUTTON|WS_CLIPSIBLINGS,21,136,38,10
    CONTROL         "",IDC_SPIN1,"msctls_updown32",UDS_ARROWKEYS|WS_CLIPSIBLINGS,100,137,11,14
    PUSHBUTTON      "Button4",IDC_BUTTON4,17,149,50,14,WS_CLIPSIBLINGS
    CONTROL         "Check4",IDC_CHECK4,"Button",BS_AUTOCHECKBOX | WS_TABSTOP | WS_CLIPSIBLINGS,203,131,35,11
END

现在的问题是 - 如何解决这个问题?

【问题讨论】:

  • 创建另一个具有黑色背景和 alpha 透明度的窗口,然后将其放置在主窗口上方。
  • 将指向所有小部件的指针放入容器中。遍历容器,告诉每个小部件隐藏。屏幕将显示窗口背景颜色,而不是黑色。我现在在我的 GUI 应用程序中执行此操作(隐藏需要解锁的功能)。
  • @RemyLebeau,感谢您的回答!第二个窗口应该有什么样式?它应该是一个子窗口吗?重要的是,第二个窗口应该响应主程序窗口的最小化、最大化和移动。
  • 从 Windows 8 开始,WS_EX_LAYEREDavailable for child controls。因此,您可以创建一个黑色半透明子窗口,它以 z 顺序位于其他控件之上。
  • @RemyLebeau,我已经更新了我的问题。你能评论吗?谢谢!

标签: c++ visual-studio winapi


【解决方案1】:

我为你写了Remy Lebeau提到的第一种方法:

LightLayer.h

#pragma once    
#include <Windows.h>

class LightLayer
{
public:
    /**
    *  darkness [0-255]
    *   0: light
    * 255: dark
    */
    LightLayer(HWND attachedWindow, int darkness = 180);

    bool isValid() const;

    void turnLightOn();
    void turnLightOff();    

    void setDarkness(int darkness);

    void onWindowPosChanged(LPARAM lParam); 
    void onShowWindow(WPARAM wParam);

private:
    bool mValid;
    bool mMustShow;
    bool mWinPosChanged;

    HWND mLayer;
    HWND mAttachedWin;

    WNDPROC mDefProc;

    void setVisibility(bool visibility);
    void updatePos();
    
    static INT_PTR CALLBACK LightsLayerProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
};

LightLayer.cpp

#include "LightLayer.h"

LightLayer::LightLayer(HWND attachedWindow, int darkness)
{    
    mValid = false;
    mMustShow = false;
    mWinPosChanged = false;
    mAttachedWin = NULL;

    mLayer = CreateWindowEx(
        WS_EX_LAYERED,
        L"STATIC", 
        NULL, 
        WS_POPUP, 
        0, 0, 0, 0, 
        attachedWindow,
        NULL, NULL, NULL);

    if (mLayer)
    {
        mValid = true;
        mAttachedWin = attachedWindow;

        SetWindowLongPtr(mLayer, GWLP_USERDATA, (LONG_PTR) this);

        mDefProc = (WNDPROC) SetWindowLongPtr(mLayer, GWLP_WNDPROC, (LONG_PTR) LightsLayerProc);        

        setDarkness(darkness);
    }            
}

bool LightLayer::isValid() const
{
    return mValid;
}   

void LightLayer::turnLightOn()
{
    setVisibility(false);
}

void LightLayer::turnLightOff()
{
    setVisibility(true);
}    

void LightLayer::setDarkness(int darkness)
{
    if(mValid)
        SetLayeredWindowAttributes(mLayer, 0, darkness, LWA_ALPHA);
}

void LightLayer::setVisibility(bool visibility)
{
    if (!mValid)
        return;

    mMustShow = visibility;

    if (visibility) 
        updatePos();
    
    ShowWindow(mLayer, visibility ? SW_SHOW : SW_HIDE);   
    SetFocus(mAttachedWin);
}

void LightLayer::updatePos()
{    
    if (!mValid)
        return;

    RECT rc;
    GetClientRect(mAttachedWin, &rc);
    MapWindowPoints(mAttachedWin, GetParent(mAttachedWin), (LPPOINT)&rc, 2);

    SetWindowPos(mLayer, HWND_NOTOPMOST,
        rc.left,
        rc.top,
        rc.right - rc.left,
        rc.bottom - rc.top,
        0);

    OutputDebugStringA("\nupdatePos");
}

void LightLayer::onWindowPosChanged(LPARAM lParam)
{
    if (!mValid)
        return;

    if (mMustShow)
    {        
        updatePos();
        mWinPosChanged = false;
    }
    else
        mWinPosChanged = true;
}

void LightLayer::onShowWindow(WPARAM wParam)
{
    setVisibility(wParam && mMustShow);
}


INT_PTR CALLBACK LightLayer::LightsLayerProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    LightLayer* caller = reinterpret_cast<LightLayer*>(GetWindowLongPtr(hWnd, GWLP_USERDATA));

    switch (uMsg)
    {
        case WM_PAINT:
        {
            PAINTSTRUCT ps;
            HDC hdc = BeginPaint(hWnd, &ps);
            FillRect(hdc, &ps.rcPaint, GetSysColorBrush(COLOR_BACKGROUND));
            EndPaint(hWnd, &ps);
        }
        break;
    }

    return caller->mDefProc(hWnd, uMsg, wParam, lParam);
}

现在将其添加到您的对话过程中:

LightLayer* lightLyr = nullptr;

INT_PTR CALLBACK DlgProc(HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    switch (uMsg)
    {
    case WM_INITDIALOG:
    {            
        lightLyr = new LightLayer(hDlg, 180);
        
        // Check if it's valid (e.g. no errors upon window creation)
        if (! lightLyr->isValid())
        {
            delete lightLyr;
            lightLyr = nullptr;
        }    
    }
    break;  
    case WM_COMMAND:
    {
        switch (LOWORD(wParam))
        {

        case IDC_OFF:
        {
            if(lightLyr)
            {
                lightLyr->turnLightOff();
                SendDlgItemMessage(hDlg, IDC_ON, BM_SETCHECK, BST_UNCHECKED, 0);    
            }
            break;
        }

        case IDC_ON:
        {
            if(lightLyr) 
            {
                lightLyr->turnLightOn();
                SendDlgItemMessage(hDlg, IDC_OFF, BM_SETCHECK, BST_UNCHECKED, 0);  
            }
            break;
        }
    }
    break;
                        
    case WM_WINDOWPOSCHANGED:
        if(lightLyr)
            lightLyr->onWindowPosChanged(lParam);
        break;

    case WM_SHOWWINDOW:
        if(lightLyr)
            lightLyr->onShowWindow(wParam);
        break;

    case WM_DESTROY:
        delete lightLyr;
        break;
    }

    return 0;
}

这是showcase

【讨论】:

  • 考虑同时处理WM_WINDOWPOSCHANGINGWM_SIZING/WM_SIZE等。此外,您应该使用SetWindowSubclass()而不是SetWindowLongPtr(GWLP_WNDPROC)。并考虑将LightLayer 子类化为attachedWindow,这样主窗口就不必将消息转发到LightLayer
  • @GZPERRA,非常感谢这段代码!但我在这里看到了一些缺陷。例如,当主窗口移动时,主窗口失去焦点。这是另一个 - 在最小化和恢复主窗口之后,主窗口和“阴影”窗口不同步“飞出”。而且,如果您将一些控件添加到“阴影”窗口,它们也将具有半透明性。不幸的是,这个实现对我不起作用。
  • 但我认为更合适的实现是创建一个子窗口,然后对主窗口进行截图,将其着色并绘制在子窗口上。现在可以将控件放置在顶部。很明显,这将是一个假阴影,但它适合我。我目前正在实现这个算法。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-02-07
相关资源
最近更新 更多