选择矩形:“资源管理器风格”实现!





5.00/5 (12投票s)
半透明选择矩形

引言
你好。
我找不到一个完整的替代方案来代替旧的 API 函数 DrawFocusRect
(...),以便在我的视图中使用它...
所以我用自己的方式做了。现在它在这里,也供你参考。
背景
分层窗口 (layered window) 可以很好地扮演这种选择矩形的角色——因为它可以以 Alpha 混合的方式显示。在这种情况下,我们不需要控制选择的父视图的客户端区域的重绘(如果我们在父视图的设备上下文中绘制动态可变尺寸的内容,则需要这样做)。
我编写了一个 class CCoverWnd : public CWnd
类来实现选择矩形的行为。这个窗口现在必须是半透明的,并且分层工作。
步骤 1. 创建分层窗口
在创建分层窗口时,我们必须设置一个特殊的扩展窗口样式 WS_EX_LAYERED
。此外,我们必须记住,分层窗口不能是子窗口。由于选择矩形没有标题栏,我们可以将普通窗口样式指定为 WS_POPUP
bool CCoverWnd::Create(CWnd* pParentView)
{
bool bResult = false;
if (pParentView) {
// The special style of the layered windows (WS_EX_LAYERED) will be used:
CreateEx(WS_EX_LAYERED, // to be created as a layered window
_T("STATIC"), // using of an existing window class
NULL, // there is no title
// for the rectangle
WS_POPUP | WS_VISIBLE, // common window styles
-1, -1, 0, 0, // initial position and dimensions
pParentView->GetSafeHwnd(), // parent view of the rectangle
NULL); // not used menu identifier
if (GetSafeHwnd()) {
pParentView->GetClientRect(m_cParentCliRect); // client area of the parent view
pParentView->ClientToScreen(m_cParentCliRect); // our coordinates
// are screen related
pParentView->SetFocus(); // return the focus at parent
bResult = true;
}
}
return bResult;
}
现在我们可以创建我们的覆盖窗口,并且需要能够以某种位置、某种大小和某种绘制的表面显示它... :)
步骤 2. 更新分层窗口
有一个 API 函数用于更新分层窗口的位置和内容 (WinUser.h)
WINUSERAPI
BOOL
WINAPI
UpdateLayeredWindow(
__in HWND hWnd, // handle of the layered window
__in_opt HDC hdcDst, // destination DC (in our case screen DC)
__in_opt POINT *pptDst, // destination position at the screen
__in_opt SIZE *psize, // dimensions of window at the screen
__in_opt HDC hdcSrc, // source (memory) DC of prepainting
__in_opt POINT *pptSrc, // source position for the surface bits transfer
__in COLORREF crKey, // color to be fully transparent (not our case)
__in_opt BLENDFUNCTION *pblend, // blending parameters
__in DWORD dwFlags); // kind of the transfer
// (in our case ULW_ALPHA - for alpha blending)
参数 pblend
是一个指向 struct BLENDFUNCTION
类型的指针,如下所示 (WinGDI.h)
typedef struct _BLENDFUNCTION
{
BYTE BlendOp; // must be AC_SRC_OVER currently
BYTE BlendFlags; // must be zero
BYTE SourceConstantAlpha; // general surface transparency
// [0(opaque) -255(transparent)]
BYTE AlphaFormat; // 0 - the transparency of the surface bits
// has no role
// AC_SRC_ALPHA - the transparency of the
// surface bits has a role
// this parameter is independent from
// SourceConstantAlpha
} BLENDFUNCTION, *PBLENDFUNCTION;
更新的实现
现在我们可以为 CCoverWnd
提供一个自己的函数来显示它 :).
// Placing the window at the screen position crPos
void CCoverWnd::ShowAt(const CRect& crPos)
{
if (GetSafeHwnd()) {
CRect cMoveRect(crPos);
cMoveRect.NormalizeRect();
CRect cIntersectRect(cMoveRect);
cIntersectRect.IntersectRect(m_cDrawRect, cMoveRect); // allowed area for placing
int iWidth(cMoveRect.Width()); // painting dimension per X
int iHeight(cMoveRect.Height()); // painting dimension per Y
HDC hdcScreen = ::GetDC(NULL); // destination screen DC
HDC hDC = ::CreateCompatibleDC(hdcScreen); // source memory DC
// We have to create a bitmap by CreateDIBSection(..)
// to operate with its bits directly. Thanks to SledgeHammer01
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); // try to fill the surface bits
// with alpha channel
if (!bFillAlphaOK) {
FillRGB(hDC, iWidth, iHeight); // else - without the alpha channel
}
// Preparing the blend parameters
BLENDFUNCTION blend = {0};
blend.BlendOp = AC_SRC_OVER;
blend.SourceConstantAlpha = bFillAlphaOK ? 160 : 64;
blend.AlphaFormat = bFillAlphaOK ? AC_SRC_ALPHA : 0;
// Destination position at the screen
POINT ptPos = {cIntersectRect.left,
cIntersectRect.top};
// Dimensions of the bits transfer
SIZE sizeWnd = {cIntersectRect.Width(),
cIntersectRect.Height()};
// Source position in source (memory DC)
POINT ptSrc = {cIntersectRect.left - cMoveRect.left,
cIntersectRect.top - cMoveRect.top};
// Call the wizard :)
::UpdateLayeredWindow(m_hWnd, hdcScreen, &ptPos, &sizeWnd,
hDC, &ptSrc, 0, &blend, ULW_ALPHA);
// Clearance
::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; // An own test instance, could be static :)
...
现在可以显示覆盖,例如
void CCoverTestDlg::ShowCover()
{
if (!m_cCoverWnd.GetSafeHwnd()) {
m_cCoverWnd.Create(this); // The info of the client area will be used
// by the child-cover, see CCoverWnd::Create(..)
}
if (m_cCoverWnd.GetSafeHwnd()) {
CRect cShowRect(m_cpStart, m_cpEnd);
ClientToScreen(cShowRect); // The cover is screen related
m_cCoverWnd.ShowAt(cShowRect); // Good luck...
}
}
... 也可以“隐藏”它
void CCoverTestDlg::DestroyCover()
{
if (m_cCoverWnd.GetSafeHwnd()) {
m_cCoverWnd.DestroyWindow(); // Thanks...
}
}
关注点
感谢 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; // 0xFF0080FF (Edge, AA =0xFF)
} else {
pcBitsWords[dwIndex] = sm_clrBrushA; // 0x400020FF (Plain, AA =0x40)
}
}
bResult = true;
}
}
}
return bResult;
}
我很高兴收到进一步的建议来改进位写入。
谢谢!
历史
- 2010年1月25日 12:46:50 UTC+0100 -- 创建
- 2010年1月25日 17:48:01 UTC+0100 -- 修改了“背景”部分
- 2010年1月25日 18:01:36 UTC+0100 -- 修改了“要点”部分
- 2010年1月28日 12:13:54 UTC+0100 -- 改进了代码