I am working on a transparent treeview control. In order to achieve transparency I have subclassed the tree and am overriding WM_PAINT
( in my WM_ERASEBKGND
handler I just return TRUE
. Scrolling, mousewheel and other relevant messages are handled properly ).
To make tree’s background transparent I am using the following algorithm ( based on this CodeGuru article ):
Let tree do its default painting in memory DC ( saved in memDC
).
Get parent’s background in another memory DC ( saved in finalDC
).
Map tree’s and parent’s coordinates so I can grab correct portion of parent’s background bitmap.
Combine these two images using TransparentBlt
and tree’s background color ( TransparentBlt( finalDC, ... , memDC, ... );
).
In parent window I have implemented WM_PRINTCLIENT
, so I can copy its background into memory DC ( step 2 ) with a simple ::SendMessage( GetParent(hwnd), WM_PRINTCLIENT, (WPARAM)finalDC, (LPARAM)(PRF_CLIENT) );
call. The result I get is correct both on Windows XP and Windows7 :
I obtain tree’s default bitmap ( step 1 ) with ::DefSubclassProc( hwnd, WM_PAINT, (WPARAM)memDC, 0 );
call. Here too, the result is correct both on Windows XP and Windows7:
On Windows XP ( I do not know why uploaded image misses checkboxes, everything looks fine on my computer ) :
On Windows7 ( I do not know why uploaded image misses checkboxes, everything looks fine on my computer ) :
However, after TransparentBlt()
call, final picture is not drawn properly:
On Windows XP checkboxes are the problem ->
On Windows7 some white is left in letters ->
These pictures are result of exporting bitmaps from device contexts into file ( I have modified this code to achieve that ).
Here is the code snippet for WM_PAINT
:
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;
How can I properly combine default tree bitmap with parent’s background, achieving transparency without visual artifacts ?
EDIT:
I was able to fix checkbox issue by ditching TransparentBlt()
and doing things myself.
ClearType font is still a problem, artifacts remained. Mask creation is the problem. As member arx said, background combining is responsible for that. If I could create proper mask then my problem would be solved.
Here is the entire subclass procedure :
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);
}
In parent window draw the image from WM_PAINT
in response to WM_PRINTCLIENT
( remember to use (HDC)wParam
instead of BeginPaint
, and to return (LRESULT)0;
). I drew a gradient as can be seen on the pictures above.
In parent window you must add the following in your WM_NOTIFY
handler:
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;
Only font remains to be fixed.
END OF EDIT