透明树视图控件

时间:2013-07-26 18:03:11

标签: c++ winapi

我想使用纯Win32 API创建一个透明背景的树视图。

从MSDN阅读文档后,我能够成功创建它,但没有任何关于如何使其透明的说法。 Google也没有帮助,因为示例是在MFC中,并且它们不会创建透明背景,而是使用TreeView_SetBkColor API或TVM_SETBKCOLOR消息来更改树的颜色。

仅举例,我创建了如下所示的窗口:

enter image description here

我已将树视图添加为这样的子窗口:

enter image description here

我的问题是:如何让树的背景透明,以便可以看到背后的图片?

编辑#2:

如果有其他人有更好的答案/建议,请发布,但此时我会接受Joel的解决方案。


1 个答案:

答案 0 :(得分:3)

您应该停止删除并重新添加此问题。

编辑:这是我能想到的最好的。我不能强调这个代码是什么,以及破解它是多么容易。然而,这将我的努力与乔纳森的评论结合起来,形成了一些有用的东西。它闪烁,而且很难看,但它或多或少都会被要求。

// 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;
}

正如我之前提到的,hDCMem包含原始的预拉伸位图,并且必须可以被子类WndProc访问。如果原始位图在父级中的(0,0)处绘制,则此方法仅按原样工作。而且正如图像所示,看起来非常糟糕。

它还有一些其他缺陷(可能比我在这里列出的更多):

  1. 假设树视图始终以纯色背景绘制。正如按钮所示,微软可能会随心所欲地改变它们的外观,因此自定义风险。

  2. 它假设您知道导致树视图重绘的所有消息。这不是一个好的假设。即使它现在是真的,也没有什么可以阻止它打破树视图控件的未来更新,只是因为没有记录工作并且它正在使用不属于应用程序的代码 - 内置树视图WndProc

  3. 即使这是一个很好的方法,在每个WM_PAINT上重新创建位图和内存DC也不是一个好主意。