首页 > 技术文章 > 窗口阴影效果

aishangxue 2014-01-16 21:06 原文

背景:

win7系统中可以在设置窗口底部有阴影效果,这样使得窗口看起来更有立体感。如果我们自定义窗口,没有用到系统默认的边框,这时阴影效果也随着没有了。我们需要在这样的窗口上加上阴影效果。

方法:

1)创建一个WS_EX_TRANSPARENT样式的窗口,该类窗口具有鼠标穿透的效果(这样的效果也可以用SetWindowRgn实现,但是我们这里比较特殊,只能使用前者)。

2)将窗口绘制成阴影,这个可以用GDI+来实现。

3)跟随目标窗口,这里需要处理这些消息:show、hide、minimize、maximize、paint、resize。这里采用的做法是hook目标窗口的窗口处理函数。这比编写代码是接受目标窗口发送来的消息更加方便,而且对于目标窗口来说阴影窗口是透明的。

实现步骤:

1)首先在窗口类中加入我们定义的阴影类对象:

CShadowWndBase m_shadowWnd;

2)我们在OnCreate(OnInitDlg)中将目标窗口的窗口句柄传给阴影窗口类对象,然后创建阴影窗口:

m_shadowWnd.SetShadowWnd(*this);
m_shadowWnd.Create(NULL,_T(""),WS_OVERLAPPEDWINDOW,CRect(0,0,0,0),GetDesktopWindow(),0);

阴影窗口创建过程还有一个工作要做,就是要hook目标窗口的窗口处理函数,注意这里需要保存原来目标窗口的处理函数地址,因为我们会在新处理函数中调用旧函数

int CShadowWndBase::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
    if (CWnd::OnCreate(lpCreateStruct) == -1)
        return -1;

    ASSERT(m_hWndShadow!=NULL);
    // TODO:  在此添加您专用的创建代码
    m_szShadowWindows.insert( make_pair(m_hWndShadow, this) );

    ModifyStyle(WS_MAXIMIZEBOX | WS_SIZEBOX | WS_CAPTION | WS_DLGFRAME, 0, 0);
    ::SetWindowLong(*this,GWL_EXSTYLE, GetWindowLong(*this,GWL_EXSTYLE) | WS_EX_LAYERED | WS_EX_TRANSPARENT);
    ModifyStyleEx(WS_EX_TOPMOST, WS_EX_NOACTIVATE);

    // Set parent window original processing.
    m_OriParentProc = ::GetWindowLong(m_hWndShadow, GWL_WNDPROC);
    ::SetWindowLong(m_hWndShadow, GWL_WNDPROC, (LONG)ShadowProc);

    return 0;
}

3)

这里还有一些问题,新处理函数是静态函数,无法访问非静态成员变量;另外还要考虑到另外的情况,可能同一进程中有多个窗口使用了阴影窗口类,我们也需要记录目标窗口和阴影窗口的对应关系。只需要定义一个类静态变量(其作用在这里就相当于一个全局变量):

static std::map<HWND, CShadowWndBase*>  m_szShadowWindows;

4)这样对于目标窗口的一些消息就可以直接处理,然后再传给默认函数。下面是新处理函数:

LRESULT CALLBACK CShadowWndBase::ShadowProc( HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam )
{
    // Find the shadow window pointer via parent window handle.
    ASSERT( m_szShadowWindows.find(hwnd) != m_szShadowWindows.end() );
    CShadowWndBase *pThis    = m_szShadowWindows[hwnd];
    WNDPROC pDefProc        = (WNDPROC)pThis->m_OriParentProc;

    switch(uMsg)
    {
    case WM_ERASEBKGND:
    case WM_PAINT:
    case WM_MOVE:
    case WM_ACTIVATE:
    case WM_NCACTIVATE:{
            if (::IsWindowVisible(hwnd)){
                pThis->AdjustWindowPos();
            }
            break;
        }
    case WM_DESTROY:{
            // Destroy the shadow window.
            pThis->DestroyWindow();   
            break;
        }
    case WM_NCDESTROY:{
            // Remove shadow window from map.
            m_szShadowWindows.erase(hwnd);   
            break;
        }
    case WM_SHOWWINDOW:{
            // the window is being hidden
            if (!wParam){
                pThis->ShowWindow(SW_HIDE);
            }
            else{
                CRect rect;
                pThis->GetWindowRect(rect);
                pThis->ShowWindow(SW_SHOW);
            }
            break;
        }
    default:
        break;
    }

    return pDefProc(hwnd, uMsg, wParam, lParam);//注意最后将消息都给原来的处理函数,让其保持原状(至少是外表上的)。
}

5)窗口在检测到目标窗口尺寸或位置改变时需要相应的改变:

void CShadowWndBase::AdjustWindowPos()
{
    CRect rcParent;

    ::GetWindowRect(m_hWndShadow, &rcParent);
    rcParent.InflateRect(m_nShadowSize, m_nShadowSize);
    rcParent.MoveToXY(rcParent.left+2,rcParent.top+2);
    ::SetWindowPos(*this, m_hWndShadow, rcParent.left,rcParent.top,rcParent.Width(), rcParent.Height(), SWP_NOACTIVATE);

    GetClientRect(m_rcClient);

    // If window was resized, redraw the shadow.
    if (m_bResize == TRUE)
    {
        m_bResize = FALSE;
        DrawShadow();
    }
}

6)利用GDI+绘制阴影效果:

void CShadowWndBase::DrawShadow()
{

    // Create shadow path.
    Rect rcShadow(m_rcClient.left, m_rcClient.top, m_rcClient.Width(), m_rcClient.Height());
    m_ShadowPath.CreateRoundRect(rcShadow, 18);

    Bitmap bitmap(rcShadow.Width, rcShadow.Height);
    Graphics graphics(&bitmap);

    // Create shadow brush.
    PathGradientBrush brShadow(m_ShadowPath.m_pPath);
    Color clrShadow[] = {Color::Transparent, Color(255, 0, 0, 0)};
    int nCount = 2;

    REAL szPos[] = {0.0F, 1.0F};
    brShadow.SetInterpolationColors(clrShadow, szPos, nCount);
    brShadow.SetFocusScales((REAL)(rcShadow.Width-6*m_nShadowSize)/(rcShadow.Width), (REAL)(rcShadow.Height-6*m_nShadowSize)/(rcShadow.Height));
    // Draw shadow.
    rcShadow.Inflate(-m_nShadowSize-6,-m_nShadowSize-6);
    graphics.ExcludeClip(rcShadow);
    graphics.FillPath(&brShadow, m_ShadowPath.m_pPath);

    // Update layered window.
    HBITMAP hBitMap;
    m_pBitmap.GetHBITMAP((ARGB)Color::Black, &hBitMap);
    CBitmap bitmap;
    bitmap.Attach(hBitMap);

    CClientDC dc(this);

    CDC dcCompat;
    dcCompat.Attach(::CreateCompatibleDC(NULL));
    dcCompat.SelectObject(bitmap);

    BITMAP bmpInfo = {0};
    bitmap.GetBitmap(&bmpInfo);
    CSize size(bmpInfo.bmWidth, bmpInfo.bmHeight);
    CRect rcWindow;
    GetWindowRect(&rcWindow);

    BLENDFUNCTION bf = { AC_SRC_OVER, 0, 128, AC_SRC_ALPHA };

    CPoint ptWnd(rcWindow.left, rcWindow.top);
    CPoint ptSource(0, 0);

    ::UpdateLayeredWindow(m_hWnd, dc.m_hDC, &ptWnd, &size,
        dcCompat.m_hDC, &ptSource, 0, &bf, ULW_ALPHA);
}

推荐阅读