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 ):
1. Let tree do its default painting in memory DC ( saved in
memDC
).
2. Get parent’s background in another memory DC ( saved in
finalDC
).
3. Map tree’s and parent’s coordinates so I can grab correct portion of parent’s background bitmap.
4. 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[
^].
However, after
TransparentBlt()
call, final picture is not drawn properly.
On Windows XP[
^] checkboxes are the problem, and
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:
{
PAINTSTRUCT ps;
RECT rcClient = {0};
GetClientRect( hwnd, &rcClient );
HDC hdc = BeginPaint( hwnd, &ps );
HDC memDC = CreateCompatibleDC(hdc), finalDC = CreateCompatibleDC(hdc);
HBITMAP memBmp, finalBmp, bmpOld, bmpOldFinal;
memBmp = CreateCompatibleBitmap( hdc,
rcClient.right - rcClient.left,
rcClient.bottom - rcClient.top );
bmpOld = (HBITMAP)SelectObject( memDC, memBmp );
RECT rcParent;
GetClientRect( GetParent(hwnd), &rcParent );
POINT ptTreeUL, ptParentUL;
ptTreeUL.x = rcClient.left;
ptTreeUL.y = rcClient.top;
ClientToScreen( hwnd, &ptTreeUL );
ptParentUL.x = rcParent.left;
ptParentUL.y = rcParent.top;
ScreenToClient( GetParent(hwnd), &ptParentUL );
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) );
::DefSubclassProc( hwnd, WM_PAINT, (WPARAM)memDC, 0 );
COLORREF clrMask = TreeView_GetBkColor(hwnd);
if( clrMask == -1 ) clrMask = ::GetSysColor(COLOR_WINDOW);
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 );
BitBlt( hdc,
0, 0,
rcClient.right - rcClient.left,
rcClient.bottom - rcClient.top,
finalDC,
ptParentUL.x + ptTreeUL.x,
ptParentUL.y + ptTreeUL.y, SRCCOPY);
SelectObject( memDC, bmpOld );
DeleteDC( memDC );
DeleteObject( memBmp );
SelectObject( finalDC, bmpOldFinal );
DeleteDC( finalDC );
DeleteObject( finalBmp );
EndPaint( hwnd, &ps );
}
return 0L;
EDIT:
I was able to solve the problem with checkboxes by ditching
TransparentBlt()
and doing manual transparency. Only font issue remains to be solved. In order to improve my chances of solving this I submit a demo application with subclassing procedure and main window with sample treeview control that uses it :
#include <windows.h>
#include <windowsx.h>
#include <commctrl.h>
#pragma comment( lib, "comctl32.lib")
#pragma comment( lib,"Msimg32.lib") // needed for GradientFill() API
#pragma comment( linker, "/manifestdependency:\"type='win32' \
name='Microsoft.Windows.Common-Controls' version='6.0.0.0' \
processorArchitecture='*' publicKeyToken='6595b64144ccf1df' \
language='*'\"")
HINSTANCE hInst;
void GradientTriangle( HDC MemDC,
LONG x1, LONG y1,
LONG x2, LONG y2,
LONG x3, LONG y3,
COLORREF top, COLORREF bottom )
{
TRIVERTEX vertex[3];
vertex[0].x = x1;
vertex[0].y = y1;
vertex[0].Red = GetRValue(bottom) << 8;
vertex[0].Green = GetGValue(bottom) << 8;
vertex[0].Blue = GetBValue(bottom) << 8;
vertex[0].Alpha = 0x0000;
vertex[1].x = x3;
vertex[1].y = y3;
vertex[1].Red = GetRValue(bottom) << 8;
vertex[1].Green = GetGValue(bottom) << 8;
vertex[1].Blue = GetBValue(bottom) << 8;
vertex[1].Alpha = 0x0000;
vertex[2].x = x2;
vertex[2].y = y2;
vertex[2].Red = GetRValue(top) << 8;
vertex[2].Green = GetGValue(top) << 8;
vertex[2].Blue = GetBValue(top) << 8;
vertex[2].Alpha = 0x0000;
GRADIENT_TRIANGLE gTriangle;
gTriangle.Vertex1 = 0;
gTriangle.Vertex2 = 1;
gTriangle.Vertex3 = 2;
GradientFill( MemDC, vertex, 3, &gTriangle, 1, GRADIENT_FILL_TRIANGLE);
}
LRESULT CALLBACK TreeProc( HWND hwnd, UINT message,
WPARAM wParam, LPARAM lParam,
UINT_PTR uIdSubclass, DWORD_PTR dwRefData )
{
switch (message)
{
case WM_TIMER: 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:
{
PAINTSTRUCT ps;
HDC hdc = BeginPaint( hwnd, &ps );
RECT rcClient = {0}, rcParent = {0};
GetClientRect( hwnd, &rcClient );
GetClientRect( GetParent(hwnd), &rcParent );
HDC memDC = CreateCompatibleDC(hdc), finalDC = CreateCompatibleDC(hdc), maskDC = CreateCompatibleDC(hdc);
HBITMAP memBmp, finalBmp, maskBmp, bmpOld, bmpOldFinal, bmpOldMask;
memBmp = CreateCompatibleBitmap( hdc, rcClient.right - rcClient.left,
rcClient.bottom - rcClient.top );
bmpOld = (HBITMAP)SelectObject( memDC, memBmp );
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) );
::SendMessage( hwnd, WM_PRINTCLIENT,(WPARAM)memDC,
(LPARAM)(PRF_CLIENT) );
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 );
BitBlt( maskDC, 0, 0, rcClient.right - rcClient.left,
rcClient.bottom - rcClient.top, memDC, 0, 0, SRCCOPY );
POINT ptTreeUL;
ptTreeUL.x = rcClient.left;
ptTreeUL.y = rcClient.top;
ClientToScreen( hwnd, &ptTreeUL );
ScreenToClient( GetParent(hwnd), &ptTreeUL );
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 );
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 );
BitBlt( hdc, 0, 0,
rcClient.right - rcClient.left,
rcClient.bottom - rcClient.top,
finalDC, ptTreeUL.x, ptTreeUL.y, SRCCOPY );
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);
}
LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
switch(msg)
{
case WM_CREATE:
{
HWND TreeView = CreateWindowEx(0, WC_TREEVIEW,
TEXT("Tree View"), WS_VISIBLE | WS_CHILD | WS_BORDER
| TVS_HASBUTTONS | TVS_HASLINES | TVS_LINESATROOT,
50, 10, 200, 200,
hwnd, (HMENU)7000, hInst, NULL);
DWORD dwStyle = GetWindowLong( TreeView , GWL_STYLE);
dwStyle |= TVS_CHECKBOXES;
SetWindowLongPtr( TreeView , GWL_STYLE, dwStyle );
SetWindowSubclass( TreeView, TreeProc, 0, 0 );
HIMAGELIST hImages = ImageList_Create( 16, 16, ILC_MASK, 1, 0 );
HICON hiBG = reinterpret_cast<HICON>( LoadImage( 0,
MAKEINTRESOURCE(IDI_WARNING),
IMAGE_ICON, 0, 0, LR_SHARED ) );
ImageList_AddIcon( hImages, hiBG );
TreeView_SetImageList( GetDlgItem( hwnd, 7000 ), hImages,
TVSIL_NORMAL );
TVINSERTSTRUCT tvis = {0};
tvis.item.mask = TVIF_TEXT;
tvis.item.pszText = L"This is root item";
tvis.hInsertAfter = TVI_LAST;
tvis.hParent = TVI_ROOT;
HTREEITEM hRootItem = reinterpret_cast<HTREEITEM>(
SendMessage( TreeView , TVM_INSERTITEM, 0,
reinterpret_cast<LPARAM>( &tvis ) ) );
for( int i = 0; i < 15; i++ )
{
memset( &tvis, 0, sizeof(TVINSERTSTRUCT) );
tvis.item.mask = TVIF_TEXT;
tvis.item.pszText = L"This is subitem";
tvis.hInsertAfter = TVI_LAST;
tvis.hParent = hRootItem;
SendMessage( TreeView , TVM_INSERTITEM, 0,
reinterpret_cast<LPARAM>( &tvis ) );
}
}
return 0L;
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;
case WM_PRINTCLIENT:
{
RECT r;
GetClientRect( hwnd, &r );
GradientTriangle( (HDC)wParam, r.right, r.bottom - r.top,
r.left, r.bottom - r.top,
r.left, r.top,
RGB( 0x0, 0x0, 0xFF ),
RGB( 0xFF, 0xFF, 0x0 ) );
GradientTriangle( (HDC)wParam,
r.right, r.bottom - r.top,
r.right, r.top,
r.left, r.top,
RGB( 0xFF, 0x0, 0x0 ),
RGB( 0x0, 0xFF, 0x0 ) );
return 0L;
}
break;
case WM_ERASEBKGND:
return 1L;
case WM_PAINT:
{
PAINTSTRUCT ps;
HDC hdc = BeginPaint( hwnd, &ps );
RECT r;
GetClientRect( hwnd, &r );
GradientTriangle( hdc, r.right,
r.bottom - r.top,
r.left, r.bottom - r.top,
r.left, r.top,
RGB( 0x0, 0x0, 0xFF ),
RGB( 0xFF, 0xFF, 0x0 ) );
GradientTriangle( hdc, r.right,
r.bottom - r.top,
r.right, r.top,
r.left, r.top,
RGB( 0xFF, 0x0, 0x0 ),
RGB( 0x0, 0xFF, 0x0 ) );
EndPaint( hwnd, &ps );
}
return 0L;
case WM_CLOSE:
{
HIMAGELIST hImages = reinterpret_cast<HIMAGELIST>(
SendMessage( GetDlgItem( hwnd, 7000 ),
TVM_GETIMAGELIST, 0, 0 ) );
ImageList_Destroy(hImages);
DestroyWindow(hwnd);
}
break;
case WM_DESTROY:
PostQuitMessage(0);
break;
default:
return DefWindowProc(hwnd, msg, wParam, lParam);
}
return 0;
}
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
WNDCLASSEX wc;
HWND hwnd;
MSG Msg;
INITCOMMONCONTROLSEX iccex;
iccex.dwSize = sizeof(INITCOMMONCONTROLSEX);
iccex.dwICC = ICC_LISTVIEW_CLASSES | ICC_UPDOWN_CLASS | ICC_STANDARD_CLASSES ;
InitCommonControlsEx(&iccex);
hInst = hInstance;
wc.cbSize = sizeof(WNDCLASSEX);
wc.style = 0;
wc.lpfnWndProc = WndProc;
wc.cbClsExtra = 0;
wc.cbWndExtra = 0;
wc.hInstance = hInstance;
wc.hIcon = LoadIcon(NULL, IDI_APPLICATION);
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
wc.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);
wc.lpszMenuName = NULL;
wc.lpszClassName = L"Transparent TreeView";
wc.hIconSm = LoadIcon(NULL, IDI_APPLICATION);
if(!RegisterClassEx(&wc))
{
MessageBox(NULL, L"Window Registration Failed!", L"Error!",
MB_ICONEXCLAMATION | MB_OK);
return 0;
}
hwnd = CreateWindowEx( WS_EX_CLIENTEDGE, L"Transparent TreeView",
L"The title of my window",
WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT,
300, 300, NULL, NULL, hInstance, NULL);
if(hwnd == NULL)
{
MessageBox(NULL, L"Window Creation Failed!", L"Error!",
MB_ICONEXCLAMATION | MB_OK);
return 0;
}
ShowWindow(hwnd, nCmdShow);
UpdateWindow(hwnd);
while(GetMessage(&Msg, NULL, 0, 0) > 0)
{
TranslateMessage(&Msg);
DispatchMessage(&Msg);
}
return Msg.wParam;
}
How can I properly combine default tree bitmap with parent’s background, achieving transparency without visual artifacts ?