Question

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 :

enter image description here


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 ) :

enter image description here

On Windows7 ( I do not know why uploaded image misses checkboxes, everything looks fine on my computer ) :

enter image description here


However, after TransparentBlt() call, final picture is not drawn properly:

On Windows XP checkboxes are the problem -> enter image description here

On Windows7 some white is left in letters -> enter image description here


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

Was it helpful?

Solution

On Windows 7 the text is smoothed with anti-aliasing. Windows doesn't paint pixels of black text on a background of solid colour, it paints a blend between black and the background colour.

TransparentBlt treats a single solid colour as transparent. Hence it doesn't treat the antialiased edges of the text as transparent, so these edges are visible in the final bitmap.

To fix this you could select a font with anti-aliasing disabled, but obviously this will give you more lumpy text.

The problem on XP is that the corner of the check-boxes is the same colour as the background. You can probably fix this by changing the background to a colour that doesn't clash (e.g. magenta).

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top