2.9 滚动条控件典型实例
实例079 自定义滚动条控件
这是一个可以提高基础技能的实例
实例位置:光盘\mingrisoft\02\079
实例说明
网上的许多应用软件都具有漂亮的滚动条,例如,著名的腾讯OICQ软件。本实例利用CStatic控件设计一个自定义的滚动条,效果如图2.46所示。
图2.46 自定义滚动条控件
技术要点
要实现自定义滚动条控件,主要有3种方法。一是利用钩子技术重新绘制滚动条,该方法实现起来比较复杂。二是获得滚动条的显示区域,将其扣除,然后在该区域显示自定义的滚动条控件。三是自定义一个滚动条控件,将其与对话框中的某个控件关联,在创建滚动条控件时,将对话框中的某个控件隐藏,并在该控件的位置显示滚动条控件。本实例采用第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)新建一个基于对话框的应用程序。
(2)在对话框中添加按钮、编辑框、静态文本控件。
(3)向对话框类中添加成员变量:
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
(4)在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; }
(5)向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); //隐藏窗口 //判断滚动条类型 m_IsHor = (dwStyle&SBS_VERT)? FALSE: TRUE; 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; }
(6)向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); }
(7)处理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); }
(8)处理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); }
(9)处理对话框的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); }
举一反三
根据本实例,读者可以:
自定义树视图控件。