前几天,突然心血来潮,想做个自己的滚动条来玩玩,今天算完成个小样(注意是小样,所以代码写得不是很好,高手见谅,我只是看看要用到什么技术)。
刚开始我猜想可能要用到CScrollBar里面的函数,所以我就继承了它,生成派生类CScrollBarEx。等整个东西做下来发现,根本没有用到CScrollBar里面的函数,所以可以直接重CWnd派生。
技术细节:
在类的构造函数里,加载绘制状态栏要用到的图片,并且将它选入设备上下文。
CScrollBarEx::CScrollBarEx() { CBitmap bmp; bmp.LoadBitmap(IDB_BITMAP3); m_ScrollBarDC.CreateCompatibleDC(NULL); m_ScrollBarDC.SelectObject(&bmp); nThumbHeight = 50; ThumbPos = 17;//Thumb的位置的y坐标 nMin = 17;//Thumb的位置的最小值 m_Timer = 0;//定时器标示 bMouseDown = false;//标示鼠标是否在Thumb中按下 nHeightToThumbTop = 0;//鼠标位置相对于Thumb顶部的距离 }
响应WM_PAINT消息,自制滚动条的函数就在这里了
void CScrollBarEx::OnPaint() { CPaintDC paintdc(this); CRect ClientRect; GetClientRect(&ClientRect); CDC* pDC = &paintdc,dc; dc.CreateCompatibleDC(pDC); CBitmap BMP,*pOldBMP; BMP.CreateCompatibleBitmap(pDC,ClientRect.Width(),ClientRect.Height()); pOldBMP = dc.SelectObject(&BMP); DrawScoreFrame(&dc); //滚动条的thumb dc.StretchBlt(1,ThumbPos+0,16,9,&m_ScrollBarDC,36,0,16,9,SRCCOPY); dc.StretchBlt(1,ThumbPos+9,16,31,&m_ScrollBarDC,54,0,16,12,SRCCOPY); dc.StretchBlt(1,ThumbPos+40,16,10,&m_ScrollBarDC,36,6,16,10,SRCCOPY); pDC->StretchBlt(0,0,ClientRect.Width(),ClientRect.Height(), &dc,0,0,ClientRect.Width(),ClientRect.Height(),SRCCOPY); dc.SelectObject(pOldBMP); }
上面这个函数里,为防止画面闪烁用了双缓冲的绘图技术(名字挺高深的,其实就是在内存设备上下文里画图,然后用StretchBlt往屏幕设备上下文上贴图),里面还有个函数DrawScoreFrame(&dc);是画除去Thumb滚动条的其他部件的函数,该函数实现如下
void CScrollBarEx::DrawScoreFrame(CDC *pDC) { CRect ClientRect; GetClientRect(&ClientRect); pDC->StretchBlt(0,0,ClientRect.Width(),ClientRect.Height(), &m_ScrollBarDC,90,0,15,15,SRCCOPY); pDC->StretchBlt(1,1,16,16, &m_ScrollBarDC,0,0,16,16,SRCCOPY); pDC->StretchBlt(1,ClientRect.bottom-16,16,16, &m_ScrollBarDC,18,0,16,16,SRCCOPY); }
好的,现在绘图就完毕了,现在是确定鼠标点击的是CScrollBarEx的什么位置了
int CScrollBarEx::GetControlClicked(const CPoint& point) { CRect ClientRect; GetClientRect(&ClientRect); CRect UP(0,0,16,16), DOWN(0,ClientRect.bottom-16,16,ClientRect.bottom), Thumb(0,ThumbPos,16,nThumbHeight+ThumbPos); if(UP.PtInRect(point)) { return 1;//单击了向上按钮 } else if(DOWN.PtInRect(point)) { return 2;//单击了向下按钮 } else if(Thumb.PtInRect(point)) { nHeightToThumbTop = point.y - Thumb.top; return 0;//单击了Thumb } return -1; }
下面这个函数是处理鼠标按下事件的,如果按下的是向上或者向下按钮,并且按住不放,那么Thumb必须连续移动,但是MFC是不处理按下不放的事件的,所以这里得设置一个定制器。还有个问题就是如果鼠标拖动Thumb,在拖动过程中鼠标移出了CScrollBarEx区域,MFC也不会处理鼠标移动事件的,这个问题困扰了好久,网上一查一个函数搞定了,就是用SetCapture();来捕捉消息。注意要在鼠标谈起的时候释放捕捉ReleaseCapture();否则你就等着抓狂吧,呵呵
void CScrollBarEx::OnLButtonDown(UINT nFlags, CPoint point) { SetCapture(); SetFocus(); int nRet = GetControlClicked(point); if( nRet == 1)//点击的是向上按钮 { m_Timer = SetTimer(9999,100,0); ThumbPos -= 3; if(ThumbPos < nMin) ThumbPos = nMin; Invalidate(); } else if( nRet == 2)//点击的是向下按钮 { m_Timer = SetTimer(9999,100,0); ThumbPos += 3; if(ThumbPos > nMax) ThumbPos = nMax; Invalidate(); } else if( nRet == 0 )//点击的是thumb { bMouseDown = true; } else ; }
下面这个函数就是定时器的处理函数了
void CScrollBarEx::OnTimer(UINT_PTR nIDEvent) { CPoint point; GetCursorPos(&point); ScreenToClient(&point); if( GetControlClicked(point) == 1)//点击的是向上按钮 { ThumbPos -= 3; if(ThumbPos < nMin) ThumbPos = nMin; Invalidate(); } else if( GetControlClicked(point) == 2)//点击的是向下按钮 { ThumbPos += 3; if(ThumbPos > nMax) ThumbPos = nMax; Invalidate(); } else { KillTimer(m_Timer); Invalidate(); } CScrollBar::OnTimer(nIDEvent); }
下面这些就是余下的函数,较为简单这里就不解释了
void CScrollBarEx::OnLButtonUp(UINT nFlags, CPoint point) { KillTimer(m_Timer); ReleaseCapture(); Invalidate(); bMouseDown = false; nHeightToThumbTop = 0; } void CScrollBarEx::OnShowWindow(BOOL bShow, UINT nStatus) { CScrollBar::OnShowWindow(bShow, nStatus); CRect ClientRect; GetClientRect(&ClientRect); nMax = ClientRect.Height() - 16 - nThumbHeight; } void CScrollBarEx::OnMouseMove(UINT nFlags, CPoint point) { if(bMouseDown) { ThumbPos = point.y-nHeightToThumbTop; if(ThumbPos < nMin) ThumbPos = nMin; if(ThumbPos > nMax) ThumbPos = nMax; Invalidate(); } }
OK,Enjoy your CScrollBarEx.