自定义背景和控件透明性处理

在Windows应用程序开发中,经常需要为对话框和属性页设置自定义背景,同时确保其中的控件能够正确地绘制其背景。然而,有些标准控件(如复选框、单选按钮等)在绘制背景时并不总是按照预期的方式进行。即使尝试使用扩展窗口样式(如WS_EX_TRANSPARENT),问题依然存在。本文将展示如何彻底解决这一问题。

自定义背景位图通常通过处理WM_ERASEBKGND(Win32)或CWnd::OnEraseBkgnd(MFC)消息来绘制,这适用于对话框和属性页。在启用了主题的Windows XP上,属性页背景可能是从shellstyle.dll加载的渐变位图。

另一个重要的Windows消息集是WM_CTLCOLORBTN、WM_CTLCOLORDLG、WM_CTLCOLOREDIT、WM_CTLCOLORLISTBOX、WM_CTLCOLORSCROLLBAR和WM_CTLCOLORSTATIC(Win32)或CWnd::OnCtlColor(MFC)。一个好的控件会向其父窗口(对话框或属性页)发送这些消息,请求用于绘制控件背景的画刷。

完全透明的控件

以下代码片段依赖于MFC,但也适用于使用纯Win32代码的情况。在Web上可以找到的最简单的解决方案是以下代码片段,用于处理CWnd::OnCtlColor:

HBRUSH CMyDialogClass::OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor) { if (CTLCOLOR_STATIC == nCtlColor) { pDC->SetBkMode(TRANSPARENT); return (HBRUSH)GetStockObject(NULL_BRUSH); } return CTheBaseClass::OnCtlColor(pDC, pWnd, nCtlColor); }

是的,这确实适用于某些控件,但并非全部,例如单选按钮和复选框的行为就不同。它们会忽略上述代码片段返回的NULL画刷,并用标准对话框背景色填充其背景——这非常烦人!

完整解决方案

如何确保所有控件都根据自定义背景位图(例如渐变位图)绘制其背景?只需按照以下小节中的指南操作。

请为类提供自己的实现CWnd::OnSize、CWnd::OnEraseBkgnd和CWnd::OnCtlColor。在类中添加以下属性:

HBITMAP m_hBackBitmap; LPVOID m_pvBackBits; int m_iBackBmpWidth; int m_iBackBmpHeight;

在类的构造函数中,将前两个属性初始化为NULL,将最后两个属性初始化为-1。在析构函数中,添加如下代码:

if (NULL != m_hBackBitmap) DeleteObject(m_hBackBitmap);

在CWnd::OnSize方法中,为背景位图创建一个DIB节,并根据需要进行填充:

void CMyDialogClass::OnSize(UINT nType, int cx, int cy) { CTheBaseClass::OnSize(nType, cx, cy); if ((m_iBackBmpWidth != cx) || (m_iBackBmpHeight != cy) || (NULL == m_hBackBitmap)) { if (NULL != m_hBackBitmap) { DeleteObject(m_hBackBitmap), m_hBackBitmap = NULL, m_pvBackBits = NULL; } m_iBackBmpWidth = cx; m_iBackBmpHeight = cy; HDC hDC = GetDC(NULL); if (NULL != hDC) { BITMAPINFO bi; memset(&bi, 0, sizeof(bi)); bi.bmiHeader.biBitCount = 32; bi.bmiHeader.biCompression = BI_RGB; bi.bmiHeader.biWidth = m_iBackBmpWidth; bi.bmiHeader.biHeight = m_iBackBmpHeight; bi.bmiHeader.biPlanes = 1; bi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER); bi.bmiHeader.biSizeImage = m_iBackBmpWidth*m_iBackBmpHeight*4; m_hBackBitmap = CreateDIBSection(hDC, &bi, DIB_RGB_COLORS, &m_pvBackBits, NULL, 0); if (NULL == m_hBackBitmap) { ReleaseDC(NULL, hDC); return; } if (IsBadWritePtr(m_pvBackBits, m_iBackBmpWidth*m_iBackBmpHeight*4)) { DeleteObject(m_hBackBitmap), m_hBackBitmap = NULL, m_pvBackBits = NULL; ReleaseDC(NULL, hDC); return; } ReleaseDC(NULL, hDC); // Paint your background bitmap in the memory region pointed to by m_pvBackBits // It is an A-R-G-B bitmap (set A to 0xFF), the four components are stored in // reverse order, i.e. B,G,R,A in memory. // Keep in mind that bitmaps are stored bottom-up, i.e. m_iBackBmpHeight-1 is // the topmost line of the bitmap. [... Place your code here ...] } } }

在CWnd::OnEraseBkgnd中执行以下操作:

BOOL CMyDialogClass::OnEraseBkgnd(CDC* pDC) { if (NULL != m_hBackBitmap) { HDC hDC = pDC->m_hDC; HDC hDCMem = CreateCompatibleDC(hDC); RECT rcClient; GetClientRect(&rcClient); if ((rcClient.right == m_iBackBmpWidth) && (rcClient.bottom == m_iBackBmpHeight)) { HGDIOBJ hOldBmp = SelectObject(hDCMem, m_hBackBitmap); BitBlt(hDC, 0, 0, m_iBackBmpWidth, m_iBackBmpHeight, hDCMem, 0, 0, SRCCOPY); SelectObject(hDCMem, hOldBmp); DeleteDC(hDCMem); return TRUE; } } return CTheBaseClass::OnEraseBkgnd(pDC); }

处理所有未正确绘制背景的控件:

for each control that does not paint properly, we will provide a bitmap brush that fits the position and size of the control. Add the following attribute to your class (for each control): HBRUSH m_hCtrlBrush; In the constructor, initialize the attribute with NULL. For each control, add the following lines to the destructor of your dialog box class: if (NULL != m_hCtrlBrush) DeleteObject(m_hCtrlBrush); In OnInitDialog, determine the size and position of the control and create a bitmap brush (brush generation code follows below): RECT rcControl; GetDlgItem(IDC_CONTROLxxx)->GetWindowRect(&rcControl); ScreenToClient(&rcControl); m_hCtrlBrush = CreateDIBBrush(rcControl.left, rcControl.top, rcControl.right-rcControl.left, rcControl.bottom-rcControl.top); In CWnd::OnCtlColor, perform the following: HBRUSH CMyDialogClass::OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor) { CWnd *pCtrl = GetDlgItem(IDC_CONTROLxxx); // add the control ID here if (CTLCOLOR_STATIC == nCtlColor) // in this example, a radio button or check box is assumed { if ((NULL != pCtrl) && (pCtrl->m_hWnd == pWnd->m_hWnd)) // match, this is an "evil" control, for which we have a bitmap brush { return m_hCtrlBrush; } } return CTheBaseClass::OnCtlColor(pDC, pWnd, nCtlColor); }

创建DIB画刷的方法:

HBRUSH CMyDialogClass::CreateDIBrush(int x, int y, int cx, int cy) { if ((x < 0) || (y < 0) || (0 == cx) || (0 == cy) || ((x+cx) > m_iBackBmpWidth) || ((y+cy) > m_iBackBmpHeight) || (NULL == m_pvBackBits)) return NULL; HGLOBAL hDIB = GlobalAlloc(GHND, sizeof(BITMAPINFOHEADER)+cx*cy*4); if (NULL == hDIB) return NULL; LPVOID lpvBits = GlobalLock(hDIB); if (NULL == lpvBits) { GlobalFree(hDIB); return NULL; } BITMAPINFOHEADER *bih = (BITMAPINFOHEADER*)lpvBits; bih->biBitCount = 32; bih->biCompression = BI_RGB; bih->biWidth = cx; bih->biHeight = cy; bih->biPlanes = 1; bih->biSize = sizeof(BITMAPINFOHEADER); bih->biSizeImage = cx*cy*4; PDWORD pdwData = (PDWORD)(bih+1); for (int y = 0; y < cy; y++) { PDWORD pdwDst = pdwData+(cy-1-y)*cx; PDWORD pdwSrc = ((PDWORD)m_pvBackBits)+(m_cy-1-y-y)*m_cx+x; memcpy(pdwDst, pdwSrc, cx<<2); } GlobalUnlock(hDIB); LOGBRUSH lb; lb.lbStyle = BS_DIBPATTERN; lb.lbColor = DIB_RGB_COLORS; lb.lbHatch = (LONG)hDIB; HBRUSH hBrush = CreateBrushIndirect(&lb); if (NULL == hBrush) GlobalFree(hDIB); return hBrush; }
沪ICP备2024098111号-1
上海秋旦网络科技中心:上海市奉贤区金大公路8218号1幢 联系电话:17898875485