简体   繁体   中英

Transparent tree view control

I want to create a tree view with transparent background, using pure Win32 API.

After reading documentation from MSDN I was able to successfully create it, but there is nothing saying on how to make it transparent. Google doesn't help either, since examples are in MFC, and they do not create transparent background, but rather change tree's color by using TreeView_SetBkColor API or with TVM_SETBKCOLOR message.

Just for an example , I have created window like this one below:

在此输入图像描述

And I have added tree view as a child window like this:

在此输入图像描述

My question is: How do I make tree's background transparent so the picture behind it can be seen ?

EDIT #2:

If anybody else has a better answer/suggestion, please post it, but at this point I will accept Joel's solution.


You should probably stop deleting and re-adding this question.

Edited: This is the best I've been able to come up with. I can't emphasize enough what a hack this code is and how easy it would be for it to break. However, this combines my efforts with Jonathan's comments into something that sort of works. It flickers, and it's ugly, but it more or less does what was requested.

// Making these globals is bad practice, but I'm not trying to show perfect
// practice here.

HDC     hDCMem;          // memory DC for background bitmap
HBITMAP hBmp;            // the background bitmap
WNDPROC oldTreeWndProc;  // old WndProc for tree view
HWND    hWndTree;        // HWND of the tree view

// Subclassed tree view WndProc

LRESULT CALLBACK TreeWndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
    switch(msg)
    {
    case WM_VSCROLL:
    case WM_HSCROLL:
    case WM_MOUSEWHEEL:
    case WM_KEYDOWN:
    case TVM_INSERTITEM:
    case TVM_DELETEITEM:
    case TVM_SELECTITEM:
    case TVM_EXPAND:
    case TVM_ENSUREVISIBLE:
        {
            // For a whole bunch of messages that might cause repainting apart
            // from WM_PAINT, let the tree view process the message then
            // invalidate the window. This is a brittle hack and will break as
            // soon as tree views handle some other kind of message that isn't
            // included in the list above. Fundamentally, tree views just don't
            // seem to support this kind of transparency.
            //
            // If you use this in production, expect to get bug reports about
            // weird background artifacts when somebody scrolls the window
            // some way you didn't think of or that didn't exist at the time
            // the code was written.

            LRESULT result =
                CallWindowProc(oldTreeWndProc, hWnd, msg, wParam, lParam);

            InvalidateRect(hWnd, NULL, TRUE);
            return result;
        }

    case WM_PAINT:
        {
            ::CallWindowProc(oldTreeWndProc, hWnd, msg, wParam, lParam);

            COLORREF treeBGColor = SendMessage(hWnd, TVM_GETBKCOLOR, 0, 0);

            // This shouldn't return -1 because it should have been set in the
            // parent WndProc to an explicit color.

            assert(treeBGColor != ((COLORREF)(-1)));

            HDC hdc = GetDC(hWnd);

            RECT rect;
            GetWindowRect(hWnd, &rect);
            HWND hWndParent = GetParent(hWnd);

            POINT pt;
            pt.x = rect.left;
            pt.y = rect.top;
            ScreenToClient(hWndParent, &pt);
            rect.left = pt.x;
            rect.top = pt.y;

            pt.x = rect.right;
            pt.y = rect.bottom;
            ScreenToClient(hWndParent, &pt);
            rect.right = pt.x;
            rect.bottom = pt.y;

            int cx = rect.right - rect.left;
            int cy = rect.bottom - rect.top;

            HDC hdcMemTree = ::CreateCompatibleDC(hdc);
            HBITMAP hComposite = ::CreateCompatibleBitmap(hDCMem, cx, cy);
            hComposite = (HBITMAP)SelectObject(hdcMemTree, hComposite);

            // Blt the background bitmap to the tree view memory DC

            BitBlt(
                hdcMemTree, 0, 0, cx, cy, hDCMem, rect.left, rect.top, SRCCOPY);

            // TransparentBlt what the tree view drew for itself into the tree
            // view memory DC (this part overlays the tree view window onto the
            // background).

            TransparentBlt(
                hdcMemTree, 0, 0, cx, cy, hdc, 0, 0, cx, cy, treeBGColor);

            // Blt the memory DC back to the screen with the composite image.

            BitBlt(hdc, 0, 0, cx, cy, hdcMemTree, 0, 0, SRCCOPY);

            hComposite = (HBITMAP)SelectObject(hdcMemTree, hComposite);
            DeleteObject(hComposite);
            DeleteDC(hdcMemTree);
            ReleaseDC(hWnd, hdc);
        }
        return 0;
    }

    return ::CallWindowProc(oldTreeWndProc, hWnd, msg, wParam, lParam);
}

// Main window WndProc

LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    PAINTSTRUCT ps;
    HDC hdc;

    switch (message)
    {
    case WM_CREATE:
        {
            HDC hDCDisplay = GetDC(NULL);
            hDCMem = CreateCompatibleDC(hDCDisplay);
            ReleaseDC(NULL, hDCDisplay);

            // This code loads the bitmap from a file. You will need to replace it with
            // something that copies your image into the memory DC at the right size.

            hBmp = (HBITMAP)LoadImage(
                NULL, _T("Test.bmp"), IMAGE_BITMAP, 0, 0, LR_LOADFROMFILE);

            if (hBmp == NULL)
            {
                MessageBox(hWnd, _T("Failed to load bitmap"), _T("Error"), MB_OK);
            }

            hBmp = (HBITMAP)::SelectObject(hDCMem, hBmp);

            hWndTree = CreateWindowEx(
                0,
                WC_TREEVIEW,
                _T(""),
                WS_CHILD | WS_BORDER | WS_VISIBLE,
                CW_USEDEFAULT,
                CW_USEDEFAULT,
                CW_USEDEFAULT,
                CW_USEDEFAULT,
                hWnd,
                (HMENU)10000,
                NULL,
                0);

            if (hWndTree == NULL)
            {
                MessageBox(NULL, _T("Failed to make tree view"), _T("Error"), MB_OK);
            }

            oldTreeWndProc = (WNDPROC)SetWindowLongPtr(
                hWndTree, GWLP_WNDPROC, (LONG_PTR)TreeWndProc);

            // Make sure the background color for the tree view is not the
            // same as any of the selected colors so that selections don't
            // get messed up by transparency. If this feels like a hack,
            // that's because it is.

            COLORREF selectedBGColor = GetSysColor(COLOR_HIGHLIGHT);
            COLORREF selectedFGColor = GetSysColor(COLOR_HIGHLIGHTTEXT);

            COLORREF treeBGColor = (selectedBGColor + 1) % 0x00ffffff;

            if (treeBGColor == selectedFGColor)
            {
                treeBGColor = (selectedFGColor + 1) % 0x00ffffff;
            }

            SendMessage(hWndTree, TVM_SETBKCOLOR, 0, treeBGColor);

            // Add a bunch of dummy items to the tree view just for testing.

            TVINSERTSTRUCT tvis;
            ::ZeroMemory(&tvis, sizeof(tvis));
            tvis.hInsertAfter = TVI_LAST;
            tvis.item.mask = TVIF_TEXT;
            tvis.hParent = TVI_ROOT;

            TCHAR buffer[10];

            for (int i = 0; i < 20; ++i)
            {
                _stprintf(buffer, _T("Item %d"), i);
                tvis.item.pszText = buffer;
                tvis.item.cchTextMax = _tcslen(buffer);
                SendMessage(hWndTree, TVM_INSERTITEM, 0, (LPARAM)&tvis);
            }
        }
        return 0;

        // Leaving the WM_CTLCOLOREDIT stuff in here to show how that would
        // seem to work. I tried it, and it doesn't really work all that well.
        // Initially, the background shows through, but when you scroll the
        // window, it doesn't redraw the background. It just seems to do a
        // a ScrollWindow call and blts the background upward. Also, the
        // background of the tree view items stayed white even with the code
        // to change the background mode to TRANSPARENT.

    //case WM_CTLCOLOREDIT:
    //    {
    //        HDC hdcCtrl = GET_WM_CTLCOLOR_HDC(wParam, lParam, message);
    //        HWND hWndCtrl = GET_WM_CTLCOLOR_HWND(wParam, lParam, message);

    //        if (hWndCtrl != hWndTree)
    //        {
    //            return DefWindowProc(hWnd, message, wParam, lParam);
    //        }

    //        SetTextColor(hdcCtrl, RGB(0, 0, 0));
    //        SetBkColor(hdcCtrl, RGB(0xff, 0xff, 0xff));
    //        SetBkMode(hdcCtrl, TRANSPARENT);

    //        RECT rect;
    //        GetWindowRect(hWndCtrl, &rect);

    //        POINT pt;
    //        pt.x = rect.left;
    //        pt.y = rect.top;
    //        ScreenToClient(hWnd, &pt);
    //        rect.left = pt.x;
    //        rect.top = pt.y;

    //        pt.x = rect.right;
    //        pt.y = rect.bottom;
    //        ScreenToClient(hWnd, &pt);
    //        rect.right = pt.x;
    //        rect.bottom = pt.y;

    //        int cx = rect.right - rect.left;
    //        int cy = rect.bottom - rect.top;

    //        BitBlt(hdcCtrl, 0, 0, cx, cy, hDCMem, rect.left, rect.top, SRCCOPY);

    //        return (LRESULT)GetStockObject(NULL_BRUSH);
    //    }

    case WM_PAINT:
        hdc = BeginPaint(hWnd, &ps);

        // 960 x 540 is the size of the image I used for testing. Adjust for
        // your own image.

        ::BitBlt(hdc, 0, 0, 960, 540, hDCMem, 0, 0, SRCCOPY);

        EndPaint(hWnd, &ps);
        break;

    case WM_SIZE:

        // Positioning the tree view somewhere on the parent that is not the
        // upper left corner.

        MoveWindow(hWndTree, 20, 20, 100, 100, TRUE);
        break;

    case WM_DESTROY:
        hBmp = (HBITMAP)::SelectObject(hDCMem, hBmp);
        ::DeleteObject(hBmp);
        ::DeleteDC(hDCMem);
        PostQuitMessage(0);
        break;

    default:
        return DefWindowProc(hWnd, message, wParam, lParam);
    }
    return 0;
}

As I mentioned before, the hDCMem contains the original pre-stretched bitmap and has to be accessible to the subclassed WndProc . This will only work as-is if the original bitmap is drawn at (0,0) in the parent. And as the image shows it's pretty awful to look at.

It also has a few other flaws (and there are probably more than I've listed here):

  1. It assumes tree views are always drawn with a solid background color. As buttons have shown, Microsoft may change their appearance at whim, so customize at your own risk.

  2. It assumes you know all the messages that cause repaints on a tree view. This is not a good assumption. Even if it's true now, there's nothing to prevent it breaking with some future update to the tree view control, simply because this isn't documented to work and it's playing around with code that doesn't belong to the application - the built-in tree view WndProc .

  3. Even if this is a good way to do this, it probably isn't a good idea to recreate the bitmaps and memory DCs on every WM_PAINT .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM