1

純粋な Win32 API を使用して、背景が透明なツリー ビューを作成したいと考えています。

MSDN のドキュメントを読んだ後、問題なく作成できましたが、透明にする方法については何も述べられていません。例はMFCにあり、透明な背景を作成せず、TreeView_SetBkColor APIまたはTVM_SETBKCOLORメッセージを使用してツリーの色を変更するため、Googleも役に立ちません。

例として、以下のようなウィンドウを作成しました。

ここに画像の説明を入力

そして、次のように子ウィンドウとしてツリー ビューを追加しました。

ここに画像の説明を入力

私の質問は次のとおりです。ツリーの背景を透明にして、その背後にある画像が見えるようにするにはどうすればよいですか?

編集#2:

他の誰かがより良い回答/提案を持っている場合は、投稿してください。ただし、この時点で Joel の解決策を受け入れます。


4

1 に答える 1

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. ツリー ビューは常に無地の背景色で描画されることを前提としています。ボタンが示すように、Microsoft は気まぐれに外観を変更する可能性があるため、自己責任でカスタマイズしてください。

  2. ツリー ビューの再描画を引き起こすすべてのメッセージを知っていることを前提としています。これは良い仮定ではありません。たとえ今それが真実であったとしても、ツリー ビュー コントロールへの将来の更新で壊れるのを防ぐものは何もありません。ツリー ビューWndProc

  3. これが良い方法であるとしても、ビットマップとメモリ DC を毎回WM_PAINT.

于 2013-07-26T18:38:55.663 に答える