整体效果图: (先放背景图,再放listbox控件,否则有问题)
任何一个有经验的windows工程师都觉得在windows中,透明度不是一个很细小的任务。一个透明的listbox控件也不例外。事实上ListBox会比其他控件难一点。原因是ListBox自带滚动条。但是总体来说,实现起来是一个非常简单的概念。
例如,实现一个透明的static控件,要处理WM_ERASEBKGND并且当用户调用SetWindowText时重绘控件。
一个ListBox,我们都要接管WM_ERASEBKGND消息并且让它的返回值为TRUE(基本上,这是透明最容易的方法)。当用户按ListBox中滚动条的下面按钮时,windows会按顺序把顶端index+1项贴到上一个显示项,然后再绘制新项。此时项目的背景被复制。
二.透明ListBox的实现
如何实现透明的ListBox?让我们从自绘ListBox开始。
首先在第一次绘制ListBox前,我们必须要复制其父窗口的背景图形,这样我们才能为listbox准备背景图。这个,我们可以在WM_ERASEBKGND消息中处理。当这个消息第一次到达时,还没绘制ListBox,因此,在这里截取父窗口的背景是安全的。
BOOL CTransListBox::OnEraseBkgnd(CDC* pDC)
{
if (!m_HasBackGround)
{
CWnd *pParent = GetParent();
if (pParent)
{
CRect Rect;
GetClientRect(&Rect);
ClientToScreen(&Rect);
pParent->ScreenToClient(&Rect);
CDC *pDC = pParent->GetDC();
m_Width = Rect.Width();
m_Height = Rect.Height();
CDC memdc;
memdc.CreateCompatibleDC(pDC);
CBitmap *oldbmp = memdc.SelectObject(&m_Bmp);
memdc.BitBlt(0,0,Rect.Width(),Rect.Height(),
pDC,Rect.left,Rect.top,SRCCOPY);
memdc.SelectObject(oldbmp);
m_HasBackGround = TRUE;
pParent->ReleaseDC(pDC);
}
}
return TRUE;
}
其次我们得在屏幕上绘制listbox的每一个item。因为有滚动条,我们不能让ListBox为我们做任何绘图。因此,重载DrawItem方法,但什么也不干。往下有一个自己定义的函数DrawItem,当在OnPaint函数中绘制ListBox时,我们可以调用它。在OnPaint函数中做了:把背景图绘制到一个内存DC中,然后把可见的items绘制到同样的内存DC中,最后把整个东西都贴到ListBox的DC中去。代码如下:
void CTransListBox::DrawItem( LPDRAWITEMSTRUCT lpDrawItemStruct )
{
//do nothing when the listbox asks you to draw an item
}
void CTransListBox::DrawItem(CDC &Dc, int Index,CRect &Rect,BOOL Selected)
{
if (Index == LB_ERR || Index >= GetCount())
return;
if (Rect.top < 0 || Rect.bottom > m_Height)
{
return;
}
CRect TheRect = Rect;
Dc.SetBkMode(TRANSPARENT);
CDC memdc;
memdc.CreateCompatibleDC(&Dc);
CFont *pFont = GetFont();
CFont *oldFont = Dc.SelectObject(pFont);
CBitmap *oldbmp = memdc.SelectObject(&m_Bmp);
Dc.BitBlt(TheRect.left,TheRect.top,TheRect.Width(),
TheRect.Height(),&memdc,TheRect.left,
TheRect.top,SRCCOPY);
CString Text;
GetText(Index,Text);
if (m_Shadow)
{
if (IsWindowEnabled())
{
Dc.SetTextColor(m_ShadowColor);
}
else
{
Dc.SetTextColor(RGB(255,255,255));
}
TheRect.OffsetRect(m_ShadowOffset,m_ShadowOffset);
Dc.DrawText(Text,TheRect,DT_LEFT|DT_EXPANDTABS|DT_NOPREFIX);
TheRect.OffsetRect(-m_ShadowOffset,-m_ShadowOffset);
}
if (IsWindowEnabled())
{
if (Selected)
{
Dc.SetTextColor(m_SelColor);
}
else
{
Dc.SetTextColor(m_Color);
}
}
else
{
Dc.SetTextColor(RGB(140,140,140));
}
Dc.DrawText(Text,TheRect,DT_LEFT|DT_EXPANDTABS|DT_NOPREFIX);
Dc.SelectObject(oldFont);
memdc.SelectObject(oldbmp);
}
void CTransListBox::OnPaint()
{
CPaintDC dc(this); // device context for painting
CRect Rect;
GetClientRect(&Rect);
int Width = Rect.Width();
int Height = Rect.Height();
//create memory DC's
CDC MemDC;
MemDC.CreateCompatibleDC(&dc);
CBitmap MemBmp;
MemBmp.CreateCompatibleBitmap(&dc,Width,Height);
CBitmap *pOldMemBmp = MemDC.SelectObject(&MemBmp);
//paint the background bitmap on the memory dc
CBitmap *pOldbmp = dc.SelectObject(&m_Bmp);
MemDC.BitBlt(0,0,Width,Height,&dc,0,0,SRCCOPY);
dc.SelectObject(pOldbmp);
Rect.top = 0;
Rect.left = 0;
Rect.bottom = Rect.top + GetItemHeight(0);
Rect.right = Width;
int size = GetCount();
//draw each item on memory DC
for (int i = GetTopIndex(); i < size
&& Rect.top <= Height;++i)
{
DrawItem(MemDC,i,Rect,GetSel(i));
Rect.OffsetRect(0,GetItemHeight(i));
}
//draw the results onto the listbox dc
dc.BitBlt(0,0,Width,Height,&MemDC,0,0,SRCCOPY);
MemDC.SelectObject(pOldMemBmp);
}
最后也是最棘手的滚动消息部分。为了处理滚动问题,我们可以拦截WM_VSCROLL消息,并且把CListBox::OnVScroll 放SetRedraw(FALSE)和SetRedraw(TRUE)之间;即在调用ListBox的滚动函数前,禁止绘制,之后才重启绘制。接下来,调用RedrawWindow更新listbox中的内容。代码如下:
void CTransListBox::OnVScroll(UINT nSBCode,
UINT nPos, CScrollBar* pScrollBar)
{
SetRedraw(FALSE); //prevent any drawing
CListBox::OnVScroll(nSBCode,nPos,pScrollBar);
SetRedraw(TRUE); //restore drawing
//draw the frame and window content in one shot
RedrawWindow(0,0,RDW_FRAME|RDW_INVALIDATE|RDW_UPDATENOW);
}
当一个items被选中时,将产生同类型的方法。因此要拦截LBN_SELCHANGE消息,并且使其重绘,
因为我们自己的DrawItem什么也没干。
BOOL CTransListBox::OnLbnSelchange() { Invalidate(); UpdateWindow(); return TRUE; }
三.透明ListBox类CTransListBox的使用
使用这个类的话,只需在你的对话框中添加一个ListBox控件,然后在Listbox控件属性中设置Owner-draw和 Has Strings两项,给listbox控件关联一个CTransListBox类型的变量m_TransList。自绘类CTransListBox还有一些别的功能:指定不同的字体、颜色、阴影。代码如下:
class CTransparentListboxDemoDlg : public CDialog { .... CTransListBox m_TransList; }; void CTransparentListboxDemoDlg::DoDataExchange(CDataExchange* pDX) { CDialog::DoDataExchange(pDX); DDX_Control(pDX, IDC_LIST1, m_ListBox); } BOOL CTransparentListboxDemoDlg::OnInitDialog() { CDialog::OnInitDialog(); m_TransList.SetFont(12,"Aria", RGB(255,255,255),RGB(255,0,0)); //Optional m_TransList.AddString(“Test”); }