【发布时间】:2014-06-19 08:56:09
【问题描述】:
我正在开发一个透明的树视图控件。为了实现透明度,我对树进行了子类化并覆盖了WM_PAINT(在我的WM_ERASEBKGND 处理程序中,我只返回TRUE。滚动、鼠标滚轮和其他相关消息都得到了正确处理)。
为了让树的背景透明,我使用了以下算法(基于thisCodeGuru 文章):
让树在内存DC中进行默认绘制(保存在
memDC中)。在另一个内存DC中获取父母的背景(保存在
finalDC中)。映射树和父级的坐标,以便我可以抓取父级背景位图的正确部分。
使用
TransparentBlt和树的背景颜色 (TransparentBlt( finalDC, ... , memDC, ... );) 组合这两个图像。
在父窗口中,我实现了WM_PRINTCLIENT,因此我可以通过简单的::SendMessage( GetParent(hwnd), WM_PRINTCLIENT, (WPARAM)finalDC, (LPARAM)(PRF_CLIENT) ); 调用将其背景复制到内存DC(步骤2)。我得到的结果在 Windows XP 和 Windows7 上都是正确的:
我通过::DefSubclassProc( hwnd, WM_PAINT, (WPARAM)memDC, 0 ); 调用获得了树的默认位图(步骤1)。在这里,结果在 Windows XP 和 Windows7 上都是正确的:
在 Windows XP 上(我不知道为什么上传的图片缺少复选框,我的电脑上一切正常):
在 Windows7 上(我不知道为什么上传的图片缺少复选框,在我的电脑上一切正常):
但是,TransparentBlt() 调用后,最终的图片没有正确绘制:
在 Windows XP 复选框是问题 ->
在 Windows7 上,字母中会留下一些白色 ->
这些图片是将位图从设备上下文导出到文件的结果(我已经修改了this code 来实现这一点)。
这是WM_PAINT的代码sn-p:
case WM_PAINT:
{
// usual stuff
PAINTSTRUCT ps;
RECT rcClient = {0};
GetClientRect( hwnd, &rcClient );
HDC hdc = BeginPaint( hwnd, &ps );
// create helper memory DCs
HDC memDC = CreateCompatibleDC(hdc), finalDC = CreateCompatibleDC(hdc);
// create helper bitmaps
HBITMAP memBmp, // default tree's paint
finalBmp, // parent's background image
bmpOld, bmpOldFinal; // needed for cleanup
memBmp = CreateCompatibleBitmap( hdc,
rcClient.right - rcClient.left,
rcClient.bottom - rcClient.top );
bmpOld = (HBITMAP)SelectObject( memDC, memBmp );
// map parent and child rectangles
RECT rcParent;
GetClientRect( GetParent(hwnd), &rcParent );
// upper left corners of the treeview, parent window
POINT ptTreeUL, ptParentUL;
// map tree's coordinates
ptTreeUL.x = rcClient.left;
ptTreeUL.y = rcClient.top;
ClientToScreen( hwnd, &ptTreeUL );
// map parent's coordinates
ptParentUL.x = rcParent.left;
ptParentUL.y = rcParent.top;
ScreenToClient( GetParent(hwnd), &ptParentUL );
/********* get parent's background image *******/
finalBmp = CreateCompatibleBitmap( hdc,
rcParent.right - rcParent.left,
rcParent.bottom - rcParent.top );
bmpOldFinal = (HBITMAP)SelectObject( finalDC, finalBmp );
::SendMessage( GetParent(hwnd), WM_PRINTCLIENT,(WPARAM)finalDC,
(LPARAM)(PRF_CLIENT) );
/********* capture default tree image *********/
::DefSubclassProc( hwnd, WM_PAINT, (WPARAM)memDC, 0 );
// get tree's background color
COLORREF clrMask = TreeView_GetBkColor(hwnd);
if( clrMask == -1 ) // this means tree uses default system color
clrMask = ::GetSysColor(COLOR_WINDOW);
/**** combine tree's default image with parent's background ****/
/**** so we can erase default background with parent's background ****/
TransparentBlt( finalDC,
ptParentUL.x + ptTreeUL.x,
ptParentUL.y + ptTreeUL.y,
rcClient.right - rcClient.left,
rcClient.bottom - rcClient.top,
memDC,
0, 0,
rcClient.right - rcClient.left,
rcClient.bottom - rcClient.top,
clrMask );
// draw the result into tree's DC
BitBlt( hdc,
0, 0,
rcClient.right - rcClient.left,
rcClient.bottom - rcClient.top,
finalDC,
ptParentUL.x + ptTreeUL.x,
ptParentUL.y + ptTreeUL.y, SRCCOPY);
// cleanup
SelectObject( memDC, bmpOld );
DeleteDC( memDC );
DeleteObject( memBmp );
SelectObject( finalDC, bmpOldFinal );
DeleteDC( finalDC );
DeleteObject( finalBmp );
EndPaint( hwnd, &ps );
}
return 0L;
如何将默认树位图与父背景正确结合,实现透明而没有视觉伪影?
编辑:
我能够通过放弃 TransparentBlt() 并自己做事来解决复选框问题。
ClearType 字体 仍然是个问题,工件仍然存在。面具创作是个问题。正如成员 arx 所说,背景组合负责这一点。如果我能创建适当的掩码,那么我的问题就会得到解决。
这是整个子类过程:
LRESULT CALLBACK TreeProc( HWND hwnd, UINT message,
WPARAM wParam, LPARAM lParam,
UINT_PTR uIdSubclass, DWORD_PTR dwRefData )
{
switch (message)
{
// handle messages that paint tree without WM_PAINT
case WM_TIMER: // handles autoscrolling when item is partially visible
case TVM_DELETEITEM:
case TVM_INSERTITEM:
case WM_MOUSEWHEEL:
case WM_HSCROLL:
case WM_VSCROLL:
{
::SendMessage( hwnd, WM_SETREDRAW, (WPARAM)FALSE, 0 );
LRESULT lres = ::DefSubclassProc( hwnd, message, wParam, lParam );
::SendMessage( hwnd, WM_SETREDRAW, (WPARAM)TRUE, 0 );
::RedrawWindow( hwnd, NULL, NULL,
RDW_FRAME | RDW_INVALIDATE | RDW_UPDATENOW );
return lres;
}
case WM_PAINT:
{
// usual stuff
PAINTSTRUCT ps;
HDC hdc = BeginPaint( hwnd, &ps );
// get client coordinates of parent and tree window
RECT rcClient = {0}, rcParent = {0};
GetClientRect( hwnd, &rcClient );
GetClientRect( GetParent(hwnd), &rcParent );
// create helper DCs and bitmaps
HDC memDC = CreateCompatibleDC(hdc), //default tree paint
finalDC = CreateCompatibleDC(hdc), // parent's WM_PRINTCLIENT
maskDC = CreateCompatibleDC(hdc); // DC that holds monochrome mask
HBITMAP memBmp, // default tree's paint
finalBmp, // parent's background image
maskBmp, // monochrome mask
bmpOld, bmpOldFinal, bmpOldMask; // needed for cleanup
memBmp = CreateCompatibleBitmap( hdc, rcClient.right - rcClient.left,
rcClient.bottom - rcClient.top );
bmpOld = (HBITMAP)SelectObject( memDC, memBmp );
/****** get parent's background image *******/
finalBmp = CreateCompatibleBitmap( hdc, rcParent.right - rcParent.left,
rcParent.bottom - rcParent.top );
bmpOldFinal = (HBITMAP)SelectObject( finalDC, finalBmp );
::SendMessage( GetParent(hwnd), WM_PRINTCLIENT,(WPARAM)finalDC,
(LPARAM)(PRF_CLIENT) );
/****** capture default tree image *********/
::SendMessage( hwnd, WM_PRINTCLIENT,(WPARAM)memDC, (LPARAM)(PRF_CLIENT) );
/********** create monochrome mask *******/
// get tree's background color
COLORREF clrMask = TreeView_GetBkColor(hwnd);
if( clrMask == -1 )
clrMask = ::GetSysColor(COLOR_WINDOW);
maskBmp = CreateBitmap( rcClient.right - rcClient.left,
rcClient.bottom - rcClient.top, 1, 1, NULL );
bmpOldMask = (HBITMAP)SelectObject( maskDC, maskBmp );
SetBkColor( memDC, clrMask ); // this color becomes white, all others black
BitBlt( maskDC, 0, 0, rcClient.right - rcClient.left,
rcClient.bottom - rcClient.top, memDC, 0, 0, SRCCOPY );
/***** map tree's coordinates to parent window *****/
POINT ptTreeUL;
ptTreeUL.x = rcClient.left;
ptTreeUL.y = rcClient.top;
ClientToScreen( hwnd, &ptTreeUL );
ScreenToClient( GetParent(hwnd), &ptTreeUL );
/***** creating transparent background ********/
// mask the original image
SetBkColor( memDC, RGB( 0, 0, 0 ) );
SetTextColor( memDC, RGB( 255, 255, 255 ) );
BitBlt( memDC, 0, 0,
rcClient.right - rcClient.left,
rcClient.bottom - rcClient.top,
maskDC, 0, 0, SRCAND );
// create transparent treeview image
SetBkColor( finalDC, RGB ( 255, 255, 255 ) );
SetTextColor( finalDC, RGB ( 0, 0, 0 ) );
BitBlt( finalDC, ptTreeUL.x, ptTreeUL.y,
rcClient.right - rcClient.left,
rcClient.bottom - rcClient.top,
maskDC, 0, 0, SRCAND );
BitBlt( finalDC, ptTreeUL.x, ptTreeUL.y,
rcClient.right - rcClient.left,
rcClient.bottom - rcClient.top,
memDC, 0, 0, SRCPAINT );
// display the result
BitBlt( hdc, 0, 0,
rcClient.right - rcClient.left,
rcClient.bottom - rcClient.top,
finalDC, ptTreeUL.x, ptTreeUL.y, SRCCOPY );
/***************** cleanup ******************/
SelectObject( memDC, bmpOld );
DeleteDC( memDC );
DeleteObject( memBmp );
SelectObject( finalDC, bmpOldFinal );
DeleteDC( finalDC );
DeleteObject( finalBmp );
SelectObject( maskDC, bmpOldMask );
DeleteDC( maskDC );
DeleteObject( maskBmp );
EndPaint( hwnd, &ps );
}
return 0L;
case WM_ERASEBKGND:
return TRUE;
case WM_NCDESTROY:
::RemoveWindowSubclass( hwnd, TreeProc, 0 );
return ::DefSubclassProc( hwnd, message, wParam, lParam);
}
return ::DefSubclassProc( hwnd, message, wParam, lParam);
}
在父窗口中从WM_PAINT 绘制图像以响应WM_PRINTCLIENT(请记住使用(HDC)wParam 而不是BeginPaint,并使用return (LRESULT)0;)。我画了一个渐变,如上图所示。
在父窗口中,您必须在 WM_NOTIFY 处理程序中添加以下内容:
case WM_NOTIFY:
{
if( ((LPNMHDR)lParam)->code == TVN_SELCHANGING )
{
InvalidateRect( ((LPNMHDR)lParam)->hwndFrom, NULL, FALSE );
break;
}
if( ((LPNMHDR)lParam)->code == TVN_ITEMEXPANDING )
{
InvalidateRect( ((LPNMHDR)lParam)->hwndFrom, NULL, FALSE );
break;
}
}
break;
只有字体有待修复。
编辑结束
【问题讨论】:
-
如果您仍然遇到麻烦,我有一些代码可以帮助您,而无需更改您的大部分代码,但是我需要您提供一些您自己的代码(以重现此问题)。这将通过手动操作位图来完成。可以自己贴出来,但是我的代码目前正在重写,有些功能还不能用,所以我得专门为你写功能。
-
@Helix:我在复选框方面取得了进展,但 ClearType 字体正在扼杀我。这是唯一剩下的东西......经过一点研究后,我开始担心修复它是不可能的。不过,如果您有兴趣,我可以发布整个子类程序甚至更好,一个小型 SSCCE 项目?感谢您提供帮助,我一如既往地感谢您。
-
嗯,我有兴趣修复你的代码,所以如果你能发布它,那就太好了。
-
@Helix:我用子类程序编辑了我的帖子。测试时不要忘记为
WM_NOTIFY添加东西,并注意WM_PRINTCLIENT(我不知道你之前是否使用过这个消息,所以我必须警告你)。希望你能想出办法。在那之前最好的问候。 -
这实际上几乎是不可能的,看来您需要自己绘制文字:/