自定义半透明选择矩形窗口的实现

图形用户界面(GUI)编程中,实现一个半透明的选择矩形是一个常见的需求。本文将介绍如何在C++中创建一个自定义的半透明选择矩形窗口,以及如何使用它。

在传统的API函数中,并没有一个直接的函数可以创建一个半透明的选择矩形。因此,需要自己实现这样的功能。一个分层窗口(layered window)可以很好地扮演这个角色,因为它可以以alpha混合的方式显示。这样,就不需要控制父视图的重绘区域,这在动态改变尺寸时是必要的。

步骤1:创建分层窗口

首先,需要为分层窗口设置一个特殊的扩展窗口样式WS_EX_LAYERED。此外,还需要记住,分层窗口不能是子窗口。由于选择矩形没有标题栏,可以指定普通窗口样式为WS_POPUP:

bool CCoverWnd::Create(CWnd* pParentView) { bool bResult = false; if (pParentView) { CreateEx(WS_EX_LAYERED, _T("STATIC"), NULL, WS_POPUP | WS_VISIBLE, -1, -1, 0, 0, pParentView->GetSafeHwnd(), NULL); if (GetSafeHwnd()) { pParentView->GetClientRect(m_cParentCliRect); pParentView->ClientToScreen(m_cParentCliRect); pParentView->SetFocus(); bResult = true; } } return bResult; }

现在可以创建覆盖窗口,并且需要一种方法来显示它在某个位置、某个大小以及带有一些绘制的表面。

步骤2:更新分层窗口

有一个API函数可以更新分层窗口的位置和内容:

BOOL WINAPI UpdateLayeredWindow(HWND hWnd, HDC hdcDst, POINT *pptDst, SIZE *psize, HDC hdcSrc, POINT *pptSrc, COLORREF crKey, BLENDFUNCTION *pblend, DWORD dwFlags);

参数pblend是一个类型为BLENDFUNCTION的结构体指针,它定义了混合参数:

typedef struct _BLENDFUNCTION { BYTE BlendOp; BYTE BlendFlags; BYTE SourceConstantAlpha; BYTE AlphaFormat; } BLENDFUNCTION, *PBLENDFUNCTION;

现在可以提供自己的函数来显示它:

void CCoverWnd::ShowAt(const CRect& crPos) { if (GetSafeHwnd()) { CRect cMoveRect(crPos); cMoveRect.NormalizeRect(); CRect cIntersectRect(cMoveRect); cIntersectRect.IntersectRect(m_cDrawRect, cMoveRect); int iWidth(cMoveRect.Width()); int iHeight(cMoveRect.Height()); HDC hdcScreen = ::GetDC(NULL); HDC hDC = ::CreateCompatibleDC(hdcScreen); BITMAPINFO sBI = {0}; sBI.bmiHeader.biSize = sizeof(BITMAPINFOHEADER); sBI.bmiHeader.biWidth = iWidth; sBI.bmiHeader.biHeight = iHeight; sBI.bmiHeader.biPlanes = 1; sBI.bmiHeader.biBitCount = 32; sBI.bmiHeader.biCompression = BI_RGB; HBITMAP hBmp = ::CreateDIBSection(hDC, &sBI, DIB_RGB_COLORS, NULL, NULL, 0); HBITMAP hBmpOld = (HBITMAP) ::SelectObject(hDC, hBmp); bool bFillAlphaOK = FillAlpha(hBmp); if (!bFillAlphaOK) { FillRGB(hDC, iWidth, iHeight); } BLENDFUNCTION blend = {0}; blend.BlendOp = AC_SRC_OVER; blend.SourceConstantAlpha = bFillAlphaOK ? 160 : 64; blend.AlphaFormat = bFillAlphaOK ? AC_SRC_ALPHA : 0; POINT ptPos = {cIntersectRect.left, cIntersectRect.top}; SIZE sizeWnd = {cIntersectRect.Width(), cIntersectRect.Height()}; POINT ptSrc = {cIntersectRect.left - cMoveRect.left, cIntersectRect.top - cMoveRect.top}; ::UpdateLayeredWindow(m_hWnd, hdcScreen, &ptPos, &sizeWnd, hDC, &ptSrc, 0, &blend, ULW_ALPHA); ::SelectObject(hDC, hBmpOld); ::DeleteObject(hBmp); ::DeleteDC(hDC); ::ReleaseDC(NULL, hdcScreen); } }

填充过程被封装在CCoverWnd中,可以在源文件中查看。

使用代码

只需要在CWnd派生对象中实例化并创建一个CCoverWnd类的对象,它应该在其表面上提供选择。

class CCoverTestDlg : public CDialog { bool m_bCaptured; CPoint m_cpStart, m_cpEnd; CCoverWnd m_cCoverWnd; ... };

现在可以显示覆盖层,例如:

void CCoverTestDlg::ShowCover() { if (!m_cCoverWnd.GetSafeHwnd()) { m_cCoverWnd.Create(this); } if (m_cCoverWnd.GetSafeHwnd()) { CRect cShowRect(m_cpStart, m_cpEnd); ClientToScreen(cShowRect); m_cCoverWnd.ShowAt(cShowRect); } }

...以及“隐藏”它:

void CCoverTestDlg::DestroyCover() { if (m_cCoverWnd.GetSafeHwnd()) { m_cCoverWnd.DestroyWindow(); } }

感兴趣的点

感谢SledgeHammer01的建议,直接写入颜色数据,而不是使用函数CBitmap::SetBitmapBits(..)。

bool CCoverWnd::FillAlpha(HBITMAP hBmp) { bool bResult = false; if (hBmp) { BITMAP bmp; GetObject(hBmp, sizeof(BITMAP), &bmp); DWORD dwCount = bmp.bmWidthBytes * bmp.bmHeight; if (dwCount >= sizeof(DWORD)) { DWORD* pcBitsWords = (DWORD*) bmp.bmBits; if (pcBitsWords) { DWORD dwIndex(dwCount / sizeof(DWORD)); DWORD dwUp = bmp.bmWidth; DWORD dwDn = dwIndex - dwUp; DWORD dwR = bmp.bmWidth - 1; while (dwIndex--) { DWORD dwSides = dwIndex % bmp.bmWidth; if (dwIndex < dwUp || dwIndex > dwDn || 0 == dwSides || dwR == dwSides) { pcBitsWords[dwIndex] = sm_clrPenA; } else { pcBitsWords[dwIndex] = sm_clrBrushA; } } bResult = true; } } } return bResult; }
沪ICP备2024098111号-1
上海秋旦网络科技中心:上海市奉贤区金大公路8218号1幢 联系电话:17898875485