滚动条处处可见,一次性展示不完整,要用它拖动查看全部,而且下载的软件,滚动条漂亮多样,本文就是一步步和大家一起制作一个比较漂亮的滚动条。
严格来说这个滚动条,不是Scrollbar,为了DIY一个漂亮的滚动条,这里我们用CStatic控件。
要实现自定义滚动条控件,主要有三种方法。
一是利用钩子技术重新绘制滚动条,该方法实现起来比较复杂。
二是获得滚动条的显示区域,将其扣除,然后在该区域显示自定义的滚动条控件。
三是自定义一个滚动条,将其与对话框中的某个控件关联,在创建滚动条控件时,将对话框中的某个控件隐藏,并在该控件的位置显示滚动条控件。
本文采用第3种方法。
利用CStatic控件派生一个自定义滚动条CCustomScroll,在CStatic控件上利用位图绘制滚动条箭头、滚动块及滚动条的滚动区域。在绘制滚动条时,由于滚动块能够被拖动,因此需要频繁绘制滚动条。为了防止出现屏幕闪烁,可以定义一个临时的CDC对象,将所有的绘图操作都在该临时对象上进行,然后再将临时对象的内容绘制在滚动块的显示区域。
为了简化操作,本文将临时CDC对象功能封装为 CMemDC 类,在该类释放时会自动将其自身的内容绘制到某一个显示区域上:
class CMemDC : public CDC { private: CBitmap* m_bmp; CBitmap* m_oldbmp; CDC* m_pDC; CRect m_Rect; public: CMemDC(CDC* pDC, const CRect& rect) : CDC() { CreateCompatibleDC(pDC); m_bmp = new CBitmap; m_bmp->CreateCompatibleBitmap(pDC, rect.Width(), rect.Height()); m_oldbmp = SelectObject(m_bmp); m_pDC = pDC; m_Rect = rect; } ~CMemDC() { m_pDC->BitBlt(m_Rect.left, m_Rect.top, m_Rect.Width(), m_Rect.Height(), this, m_Rect.left, m_Rect.top, SRCCOPY); SelectObject(m_oldbmp); if (m_bmp != NULL) delete m_bmp; } };
1. 新建一个基于对话框的MFC应用程序,在对话框中添加按钮、编辑框、静态文本控件,向对话框类中添加成员变量:
class CCustomScroll : public CStatic { // Construction public: CCustomScroll(); UINT m_ThumbWidth; //滚动块和箭头宽度 UINT m_ThumbHeight; //滚动块和箭头高度 CWnd* m_pParent; //父窗口 CRect m_ClientRect; //窗口客户区域 CRect m_ThumbRect; //滚动块区域 BOOL m_ButtonDown; //鼠标是否单击滚动块 CPoint m_Startpt; //鼠标按下时的起点 BOOL m_IsLeft; //滚动块是否超过左箭头 BOOL m_IsLeftArrow; //是否单击左滚动条按钮 BOOL m_IsRightArrow;//是否单击右滚动条按钮 BOOL m_IsLeftRange; //是否单击了左滚动区域 BOOL m_IsRightRange;//是否单击了右滚动区域 UINT m_MinRange; //最小滚动范围 UINT m_MaxRange; //最大滚动范围 UINT m_CurPos; //当前的位置(逻辑单位) double m_Rate; //物理像素与逻辑单位的比率 UINT m_LeftArrow; //左箭头位图ID UINT m_RightArrow; //右箭头位图ID UINT m_ChanelBK; //背景位图ID UINT m_ThumbBK; //滚动块位图ID }
2. 在CCustomScroll类的构造函数中初始化成员变量,代码如下:
CCustomScroll::CCustomScroll() { m_ButtonDown = FALSE; m_IsLeft = FALSE; m_MinRange = 0; m_MaxRange = 200; m_CurPos = 0; m_IsLeftArrow = FALSE; m_IsRightArrow = FALSE; m_IsLeftRange = FALSE; m_IsRightRange = FALSE; }
3. 向CCustomScroll类中添加CreateStatic成员函数,用于创建滚动条控件,如是:
BOOL CCustomScroll::CreateStatic(CWnd *pParent, DWORD dwStyle, UINT nIDStatic, UINT nID) { m_pParent = pParent; ASSERT(m_pParent); //获取父窗口中的静态文本 ASSERT(::IsWindow(pParent->GetDlgItem(nIDStatic)->m_hWnd)); CRect recttemp; pParent->GetDlgItem(nIDStatic)->GetWindowRect(recttemp); pParent->ScreenToClient(&recttemp); m_ClientRect = recttemp; pParent->GetDlgItem(nIDStatic)->ShowWindow(SW_HIDE); BOOL ret = CStatic::Create("",dwStyle,m_ClientRect,pParent,nID); pParent->GetDlgItem(nIDStatic)->GetClientRect(m_ClientRect); if (ret) { CBitmap bmp; bmp.LoadBitmap(m_LeftArrow); BITMAP bInfo; bmp.GetBitmap(&bInfo); m_ThumbHeight = bInfo.bmHeight; m_ThumbWidth = bInfo.bmWidth; if (bmp.GetSafeHandle()) bmp.DeleteObject(); bmp.LoadBitmap(IDB_THUMB); bmp.GetBitmap(&bInfo); m_ThumbRect.CopyRect(CRect(m_ThumbWidth,0,m_ThumbWidth+bInfo.bmWidth,bInfo.bmHeight)); if (bmp.GetSafeHandle()) bmp.DeleteObject(); SetScrollRange(m_MinRange,m_MaxRange); } ShowWindow(SW_SHOW); return ret; }
4. 向CCustomScroll类中添加DrawHorScroll方法,绘制滚动条:
void CCustomScroll::DrawHorScroll() { CClientDC dc(this); CMemDC memdc(&dc,m_ClientRect); CDC bmpdc; bmpdc.CreateCompatibleDC(&dc); CBitmap bmp; bmp.LoadBitmap(m_LeftArrow); CBitmap* pOldbmp = bmpdc.SelectObject(&bmp); CRect LeftArrowRect (m_ClientRect.left,m_ClientRect.top,m_ClientRect.left+m_ThumbWidth,m_ClientRect.bottom); memdc.StretchBlt(m_ClientRect.left,m_ClientRect.top,m_ThumbWidth,m_ThumbHeight,&bmpdc,0,0,m_ThumbWidth,m_ThumbHeight,SRCCOPY); if (pOldbmp) bmpdc.SelectObject(pOldbmp); if (bmp.GetSafeHandle()) bmp.DeleteObject(); pOldbmp = NULL; //通道的开始位置和宽度 int nChanelStart = m_ClientRect.left+m_ThumbWidth; int nChanelWidth = m_ClientRect.Width()- 2*m_ThumbWidth; //绘制通道 bmp.LoadBitmap(m_ChanelBK); pOldbmp = bmpdc.SelectObject(&bmp); memdc.StretchBlt(nChanelStart,m_ClientRect.top,nChanelWidth,m_ClientRect.Height(),&bmpdc,0,0,1,10,SRCCOPY); if (pOldbmp) bmpdc.SelectObject(pOldbmp); if (bmp.GetSafeHandle()) bmp.DeleteObject(); //绘制右箭头 bmp.LoadBitmap(m_RightArrow); pOldbmp = bmpdc.SelectObject(&bmp); int nRArrowStart = m_ThumbWidth+nChanelWidth; memdc.StretchBlt(nRArrowStart,m_ClientRect.top,m_ThumbWidth,m_ClientRect.Height(),&bmpdc,0,0,m_ThumbWidth,m_ThumbHeight,SRCCOPY); //绘制滚动块 if (bmp.GetSafeHandle()) bmp.DeleteObject(); bmp.LoadBitmap(m_ThumbBK); pOldbmp = bmpdc.SelectObject(&bmp); memdc.StretchBlt(m_ThumbRect.left,m_ThumbRect.top,m_ThumbRect.Width()+1,m_ThumbRect.Height(),&bmpdc,0,0,m_ThumbRect.Width(),m_ThumbRect.Height(),SRCCOPY); }
5. 处理CCustomScroll类的WM_LBUTTONDOWN消息,根据用户单击的不同区域移动滚动块,如下:
void CCustomScroll::OnLButtonDown(UINT nFlags, CPoint point) { m_Startpt = point; //确定滚动区域 CRect rcScroll = m_ClientRect; rcScroll.left += m_ThumbWidth; rcScroll.right-= m_ThumbWidth; DWORD wparam; SetCapture(); if (m_ThumbRect.PtInRect(point)) { m_ButtonDown = TRUE; } else if (rcScroll.PtInRect(point)) //单击滚动区域 { CPoint centerPt = m_ThumbRect.CenterPoint(); int offset = point.x-centerPt.x; if ((int)point.x<m_ThumbRect.left) //左滚动区域 m_IsLeftRange = TRUE; if ((int)point.x>m_ThumbRect.right) m_IsRightRange= TRUE; m_ThumbRect.OffsetRect(offset,0); int left = m_ThumbRect.left; int right = m_ThumbRect.right; if (left<(int)m_ThumbWidth) //判断当前滚动量是否超出了滚动范围 { int width = m_ThumbRect.Width(); m_ThumbRect.left = m_ThumbWidth; m_ThumbRect.right = m_ThumbRect.left+width; m_CurPos = m_MinRange; wparam = MAKELONG(SB_PAGELEFT,m_CurPos) ; ::SendMessage(GetParent()->m_hWnd,WM_HSCROLL,wparam,(LPARAM)m_hWnd); DrawControl(); return; } else if (right>(int)(m_ClientRect.Width()-m_ThumbWidth)) { int width = m_ThumbRect.Width(); m_ThumbRect.right = m_ClientRect.Width()-m_ThumbWidth; m_ThumbRect.left = m_ThumbRect.right -width; m_CurPos = m_MaxRange; wparam = MAKELONG(SB_PAGERIGHT,m_CurPos); ::SendMessage(GetParent()->m_hWnd,WM_HSCROLL,wparam,(LPARAM)m_hWnd); DrawControl(); return; } int range = m_ThumbRect.left-m_ThumbWidth; m_CurPos = m_Rate*(range); if (m_IsLeftRange) { wparam = MAKELONG(SB_PAGELEFT,m_CurPos) ; ::SendMessage(GetParent()->m_hWnd,WM_HSCROLL,wparam,(LPARAM)m_hWnd); } else if (m_IsRightRange) { wparam = MAKELONG(SB_PAGERIGHT,m_CurPos); ::SendMessage(GetParent()->m_hWnd,WM_HSCROLL,wparam,(LPARAM)m_hWnd); } DrawControl(); } else //单击箭头按钮 { if (point.x<=(int)m_ThumbWidth) //单击左箭头 { if (m_CurPos>m_MinRange) wparam = MAKELONG(SB_LINELEFT ,1); else wparam = MAKELONG(SB_LINELEFT ,0); ::SendMessage(GetParent()->m_hWnd,WM_HSCROLL,wparam,(LPARAM)m_hWnd); if (m_CurPos>m_MinRange) m_CurPos-=1; m_IsLeftArrow = TRUE; } else //单击右箭头 { if (m_CurPos>=m_MaxRange) wparam = MAKELONG(SB_LINERIGHT ,0); else wparam = MAKELONG(SB_LINERIGHT ,1); ::SendMessage(GetParent()->m_hWnd,WM_HSCROLL,wparam,(LPARAM)m_hWnd); if (m_CurPos<m_MaxRange) m_CurPos+=1; m_IsRightArrow = TRUE; } int factpos = m_CurPos/m_Rate; int width = m_ThumbRect.Width(); m_ThumbRect.left = m_ThumbWidth +factpos; m_ThumbRect.right = m_ThumbRect.left+width; DrawControl(); SetTimer(1,100,NULL); } CStatic::OnLButtonDown(nFlags, point); }
6. 处理CCustomScroll类的WM_MOUSEMOVE消息,如果用户正在拖动滚动条,将滚动块移动到适当的位置:
void CCustomScroll::OnMouseMove(UINT nFlags, CPoint point) { if (m_ButtonDown) { int offset = point.x-m_Startpt.x; m_Startpt = point; DWORD wparam; if (offset<=0) //向左拖动滚动块 { if (m_ThumbRect.left<=(int)m_ThumbWidth) return; else if (abs(offset)>(int)(m_ThumbRect.left-m_ThumbWidth)) //判断当前滚动量是否超出了滚动范围 { int width = m_ThumbRect.Width(); m_ThumbRect.left = m_ThumbWidth; m_ThumbRect.right = m_ThumbRect.left+width; m_CurPos = 0; wparam = MAKELONG(SB_THUMBPOSITION,m_CurPos); ::SendMessage(GetParent()->m_hWnd,WM_HSCROLL,wparam,(LPARAM)m_hWnd); DrawControl(); return; } } else if (offset>0) //向右拖动滚动块 { if (m_ThumbRect.right>=m_ClientRect.Width()-m_ThumbWidth) //超出右箭头 { return; } else if ( offset> m_ClientRect.Width()-m_ThumbWidth-m_ThumbRect.right) //判断当前滚动量是否超出了滚动范围 { int width = m_ThumbRect.Width(); m_ThumbRect.right = m_ClientRect.Width()-m_ThumbWidth; m_ThumbRect.left = m_ThumbRect.right -width; m_CurPos = m_MaxRange; wparam = MAKELONG(SB_THUMBPOSITION,m_CurPos); ::SendMessage(GetParent()->m_hWnd,WM_HSCROLL,wparam,(LPARAM)m_hWnd); DrawControl(); return; } } m_ThumbRect.OffsetRect(offset,0); int range = m_ThumbRect.left-m_ThumbWidth; m_CurPos = m_Rate*(range); wparam = MAKELONG(SB_THUMBPOSITION,m_CurPos); ::SendMessage(GetParent()->m_hWnd,WM_HSCROLL,wparam,(LPARAM)m_hWnd); DrawHorScroll(); } CStatic::OnMouseMove(nFlags, point); }
7. 处理对话框的WM_TIMER消息,当用户按下滚动条两端的箭头时,连续移动滚动块,代码如下:
void CCustomScroll::OnTimer(UINT nIDEvent) { DWORD wparam; if (m_IsLeftArrow) { if (m_CurPos>m_MinRange) wparam = MAKELONG(SB_LINELEFT ,1); else wparam = MAKELONG(SB_LINELEFT ,0); ::SendMessage(GetParent()->m_hWnd,WM_HSCROLL,wparam,(LPARAM)m_hWnd); if (m_CurPos>m_MinRange) m_CurPos-=1; } else if (m_IsRightArrow) { if (m_CurPos< m_MaxRange) wparam = MAKELONG(SB_LINERIGHT,1); else wparam = MAKELONG(SB_LINERIGHT ,0); ::SendMessage(GetParent()->m_hWnd,WM_HSCROLL,wparam,(LPARAM)m_hWnd); if (m_CurPos<m_MaxRange) m_CurPos+=1; } int factpos = m_CurPos/m_Rate; int width = m_ThumbRect.Width(); m_ThumbRect.left = m_ThumbWidth +factpos; m_ThumbRect.right = m_ThumbRect.left+width; DrawControl(); CStatic::OnTimer(nIDEvent); }