我想使用纯 Win32 API 创建一个具有透明背景的 TreeView 。
从 MSDN 阅读文档后,我能够成功创建它,但没有说明如何使其透明。 Google 也无济于事,因为示例在 MFC 中,它们不会创建透明背景,而是通过使用 TreeView_SetBkColor API 或 TVM_SETBKCOLOR 消息更改树的颜色。
举个例子,我创建了如下所示的窗口:
我添加了 TreeView 作为子窗口,如下所示:
我的问题是:如何使树的背景透明以便可以看到它后面的图片?
编辑#2:
如果其他人有更好的答案/建议,请发布,但此时我会接受 Joel 的解决方案。
最佳答案
你应该停止删除并重新添加这个问题。
已编辑:这是我能想到的最好的。我怎么强调这段代码是多么的骇人听闻,以及破解它有多么容易。然而,这将我的努力与 Jonathan 的评论结合起来,成为某种作品。它会闪烁,而且很难看,但它或多或少地完成了所要求的。
// 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
包含原始的预拉伸(stretch)位图并且必须可供子类 WndProc
访问。如果原始位图绘制在父级的 (0,0) 处,这将仅按原样工作。如图所示,它看起来非常糟糕。
它还有一些其他缺陷(可能比我在这里列出的更多):
它假定 TreeView 始终使用纯色背景绘制。如按钮所示,Microsoft 可能会随心所欲地更改它们的外观,因此请自行承担自定义风险。
它假定您知道导致 TreeView 重绘的所有消息。这不是一个好的假设。即使它现在是真的,也没有什么可以阻止它在将来对 TreeView 控件进行更新时中断,仅仅是因为没有记录它可以工作并且它正在使用不属于应用程序的代码 - 内置 TreeView
WndProc
。即使这是执行此操作的好方法,但在每个
WM_PAINT
上重新创建位图和内存 DC 可能不是一个好主意。
关于c++ - 透明 TreeView 控件,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/17888038/