windows开发中经常用DUI方式进行绘制,要自己处理窗口的全部绘制任务,包括阴影。那么就面临一个问题:阴影窗口是半透明窗口;这就意味着需要使用Layer窗口来绘制阴影,接下来就有了一个比较通用的方法:
1、创建一个Layer窗口绘制阴影
创建真正窗口
需要处理的消息包括WM_WINDOWPOSCHANGED, WM_CLOSE,
void C*****Wnd::OnWindowPosChanged(LPWINDOWPOS lpWndPos)
{
if ((lpWndPos->flags & (SWP_NOMOVE | SWP_NOSIZE)) != (SWP_NOSIZE | SWP_NOMOVE))
{
CRect rc;
GetWindowRect(&rc);
CSize sz = m_layerShadow.GetPreferredSize();
m_layerShadow.SetWindowPos(NULL, rc.left - sz.cx / 2, rc.top - sz.cy / 2,
rc.Width() + sz.cx, rc.Height() + sz.cy, SWP_NOZORDER | SWP_NOACTIVATE | SWP_SHOWWINDOW);
m_layerShadow.Refresh();
}
}
创建阴影
HWND CreateLayerWnd(HWND hParent)
{
DWORD dwFlags = WS_EX_LAYERED | WS_EX_NOACTIVATE | WS_EX_TRANSPARENT;
Create(hParent, CRect(0, 0, 0, 0), 0, WS_POPUP, dwFlags);
}
创建Layer窗口时,第一个要注意的点是WS_EX_NOACTIVATE,系统不会改变foreground窗口的activate状态。但是同时要处理WM_MOUSEACTIVATE,msdn中有表述:
The WS_EX_NOACTIVATE value for dwExStyle prevents foreground activation by the system. To prevent queue activation when the user clicks on the window, you must process the WM_MOUSEACTIVATE message appropriately.
所以一般会加一个窗口类,来处理这个消息;
class CLayerWrapper : public CWindowImpl<CLayerWrapper>
{
public:
CLayerWrapper() {}
virtual ~CLayerWrapper() {}
void Show(HWND hWnd, CRect rc, bool bActive, DWORD dwExStyle);
void Refresh();
virtual void OnPaint(CDCHandle dc) = 0;
private:
BEGIN_MSG_MAP_EX(CLayerWrapper)
MSG_WM_SIZE(OnSize)
MSG_WM_MOUSEACTIVATE(OnMouseActivate)
END_MSG_MAP()
void OnSize(UINT nType, CSize size);
int OnMouseActivate(CWindow wndTopLevel, UINT nHitTest, UINT message);
};
void CLayerWrapper::Show(HWND hWnd, CRect rc, bool bActive, DWORD dwExStyle)
{
if (!IsWindow())
{
DWORD dwFlags = WS_EX_LAYERED;
if (!bActive)
dwFlags |= WS_EX_NOACTIVATE;
Create(hWnd, rc, 0, WS_POPUP, dwFlags | dwExStyle);
}
DWORD dwFlags = SWP_SHOWWINDOW;
if (!bActive)
dwFlags |= SWP_NOACTIVATE;
SetWindowPos(NULL, rc, dwFlags);
}
void CLayerWrapper::Refresh()
{
CRect rc;
GetWindowRect(rc);
CLayeredMemDC MemDC;
MemDC.CreateMemDC(rc.Width(), rc.Height());
OnPaint((HDC)MemDC);
CPoint pt(rc.left, rc.top);
BLENDFUNCTION stBlend = { AC_SRC_OVER, 0, 255, AC_SRC_ALPHA };
::UpdateLayeredWindow(m_hWnd, NULL, &pt, &rc.Size(), MemDC, &CPoint(0, 0), 0, &stBlend, ULW_ALPHA);
}
void CLayerWrapper::OnSize(UINT nType, CSize size)
{
Refresh();
}
int CLayerWrapper::OnMouseActivate(CWindow wndTopLevel, UINT nHitTest, UINT message)
{
return MA_NOACTIVATE;
}
然后从LayerWrapper派生一下即可,这样在OnPaint中绘制阴影,Owner窗口为真正的窗口。
2、使用WS_THICKFRAME
加入这个属性,可以使用系统的边框、阴影,缺点是:用DUI绘制窗口时,大部分呢情况阴影都自己弄,另外无法处理异形窗口阴影
3、使用CS_DROPDOWN
这是一个class style,所以需要SetClassLong();
DWORD dwStyle = GetClassLong(m_hWnd, GCL_STYLE);
SetClassLong(m_hWnd, GCL_STYLE, dwStyle | CS_DROPSHADOW);
这种方法可以处理好WindowRgn的问题。
总结一下:通常情况下使用第一种方式,如果有异形窗口可以使用第3种,偷懒的情况下是用第二种。