MFC滚动条(CScrollBar)控件⾃绘
⾃绘是在滚动条WM_PAINT消息处理函数⾥完成的。第⼀步是得知道,滚动条的各组件⼤⼩信息,如左按钮宽度,滑块位置⼤⼩,右通道⼤⼩等,这些信息的获取可以⽤API函数GetScrollBarInfo来完成
那么我就在CScrollBar的派⽣类CNewScrollBar定义了6个变量CRect,对应着上⾯的信息。
CRect m_cliRect;//滚动条⼤⼩
CRect m_rcLeftButton;//左箭头按钮⼤⼩
CRect m_rcRightButton;//右箭头按钮⼤⼩
CRect m_rcThumb;//滑块位置,⼤⼩
CRect m_rcLeftChannel;//左通道⼤⼩
CRect m_rcRightChannel;//左通道⼤⼩
好接下来说说,怎么给它们正确赋值吧,GetScrollBarInfo得到的只是⼀些简单数据,如按钮宽度,滑块位置,宽度。那些通道还是得我们⾃⼰来计算。
GetScrollBarInfo函数定义:
BOOL GetScrollBarInfo(
HWND hwnd,/./滚动条窗⼝句柄,或者窗⼝拥有滚动条属性的窗⼝句柄(这是假滚动条,跟窗⼝⼀体的)
LONG idObject,//如果是滚动条窗⼝句柄,填OBJID_CLIENT,如果是“假滚动条”,⽔平填OBJID_HSCROLL ,垂直OBJID_VSCROLL PSCROLLBARINFO psbi);//这个结构包含的就是滚动条的各项信息了。
(如果编译代码的时候,出现SCROLLBARINFO结构未定义,在Stdafx.h头⽂件⾥最前⾯加上#define WINVER 0x500) SCROLLBARINFO 结构定义:
typedef struct tagSCROLLBARINFO {
DWORD cbSize;//初始化结构体,须赋值sizeof(SCROLLBARINFO),也就是结构体⼤⼩
RECT  rcScrollBar;//滚动条⼤⼩,位置,这个是相对于屏幕,不受⽗窗⼝限制。也就是调⽤GetWindowRect函数获取的⼤⼩。
int  dxyLineButton;//按钮宽度(⽔平),或按钮⾼度(垂直)
int  xyThumbTop;//滑块左边位置(⽔平),滑块顶部位置(垂直),这个位置是相对于滚动条的。
int  xyThumbBottom;//滑块右边位置(⽔平),滑块底部位置(垂直)。相对于滚动条
int  reserved;//预留。。。
DWORD rgstate[CCHILDREN_SCROLLBAR+1];//指明各组件状态,如按钮被按下,详细查MSDN,这个例⼦不会⽤到它。
} SCROLLBARINFO, *PSCROLLBARINFO, *LPSCROLLBARINFO;
为省事,我在WM_PAINT消息处理函数计算出各项信息。如下:
//获取滚动条信息,按钮⼤⼩,滑块⼤⼩等
SCROLLBARINFO sbi;
sbi.cbSize=sizeof(SCROLLBARINFO);
::GetScrollBarInfo(this->m_hWnd,OBJID_CLIENT,&sbi);
//滚动条⼤⼩
m_ScrollBar;
//计算左箭头按钮⼤⼩
m_rcLeftButton=CRect(0,0,sbi.dxyLineButton,m_cliRect.Height());
//计算右箭头按钮⼤⼩
m_rcRightButton=CRect(m_cliRect.Width()-sbi.dxyLineButton,0,m_cliRect.Width(),m_cliRect.Height());
//计算滑块位置
m_rcThumb=ThumbTop,ThumbBottom,m_cliRect.Height());
//计算左通道⼤⼩
m_rcLeftChannel=CRect(sbi.dxyLineButton,0,m_rcThumb.left,m_cliRect.Height());
//计算右通道⼤⼩
m_rcRightChannel=CRect(m_rcThumb.right,0,m_cliRect.Width()-sbi.dxyLineButton,m_cliRect.Height());
其实就算你在WM_PAINT消息处理中什么都不做,也不调⽤⽗类WM_PAINT消息处理函数,⽗类也是有机会绘制滚动条的,⽽我们要完全⾃绘,不需要⽗类来处理。这样总会出错。就⽐如双击滚动条,绘制滚动条,不通过WM_PAINT消息处理函数。。。所以我们就要禁⽌掉⽗类处理WM_LBUTTONDBLCLK消息,⽅法也很简单,给CNewScrollBar添加WM_LBUTTONDBLCLK消息处理函数,在那函数⾥不调⽤⽗类双击消息处理函数就⾏了。当然这只是举个例⼦。后⾯还是会有⽗类绘制滚动条的问题。。。⽐如,调⽤SetScrollPos函数设置滚动条位置的时候,再⼀次逃脱⾃绘的范围了。⽗类⼜参与绘制滚动条了。。。就是由于调⽤SetScrollPos设置滚动条位置,会给滚动条发送SBM_SETSCROLLINFO消息,其实也就是通过发送消息的⽅式来设置滚动信息。那么在⽗类这个消息处理函数⾥,肯定也绘制了滚动条。。所以我们要做的,就截获掉SBM_SETSCROLLINFO,不让⽗类处理。。。可是这⾥有⼀个问题,如果不让⽗类处理,那么数据也是对接不上了。。。毕竟存储滚动条诸如位置的信息,都是得⽗类来处理。我看过⼀个例⼦,就是完全⽤⾃⼰的数据来代替,也就是滚动条页⼤⼩,位置,也由新类来存储,设置。后⾯会解决这个问题的,现在先放⼀旁,来看看消息发送。滚动条发送消息给⽗窗⼝的代码⽰例,如拖动滚动条,左右按钮被单击,如下:
GetParent()->SendMessage(WM_HSCROLL,MAKELONG(SB_LINELEFT,0),(LPARAM)this->m_hW
nd);//左(上)按钮被单击GetParent()->SendMessage(WM_HSCROLL,MAKELONG(SB_LINERIGHT,0),(LPARAM)this->m_hWnd);//右(下)按钮被单击GetParent()->SendMessage(WM_HSCROLL,MAKELONG(SB_PAGELEFT,0),(LPARAM)this->m_hWnd);//左能道单击了下
GetParent()->SendMessage(WM_HSCROLL,MAKELONG(SB_PAGERIGHT,0),(LPARAM)this->m_hWnd);//右通道单击了下
GetParent()->SendMessage(WM_HSCROLL,MAKELONG(SB_THUMBTRACK,nPos),(LPARAM)this->m_hWnd);//拖动滑块
⽤到也只上⾯5个消息,另外的消息不需要也可以,这⾥也特别说⼀下SB_THUMBTRACE这个消息,这个消息附带的参数,要指明滑块位置。也就是那个nPos,nPos是给调⽤者设置滑块位置⽤的。⽽这个Pos需要我们⾃⼰来求出(拖动滑块时),这⾥我就给⼀个简单的计算⽅法,假设设置最⼤滑块位置是255,页⼤⼩是50.但实际滑块位置在205时,就到头了。原因是设置了页⼤⼩50。所以滑块位置的可滚动范围是0~205(减去页⼤⼩50)。那么滑块位置范围是0~205,⽽滚动的像素范围是0~235。那们我们就可以求出1个像素滚动多少点(⽐例)也就是205/235。具体应⽤代码就是:
在派⽣出的滚动条类定义⼀个double变量,存储⽐例,⼀个CRect变量,存储滚动动⼤⼩.
double m_Ratio;//⽐例
CRect m_cliRect;//滚动条⼤⼩
在WM_PAINT消息处理函数中,计算⽐例(接上⾯计算滚动各项信息的代码)
//获取滚动条信息,页⼤⼩,滑块位置
SCROLLINFO si;
si.cbSize=sizeof(SCROLLINFO);
GetScrollInfo(&si);
//计算通道可滚动像素范围
int chlWidth=m_cliRect.Width()-sbi.dxyLineButton*2-m_rcThumb.Width();
//计算滑块可移动的最⼤位置
int maxPos=si.nMax-si.nPage;
/
/计算⽐率,1个像素多少点
m_Ratio=(double)maxPos/chlWidth;
求出了⽐例,接着来计算⿏标拖动滑块移动时,滚动条正确位置,这⾥还要在派⽣类⾥定义⼀些变量,如下:
BOOL m_isTrace;//指明⿏标当前是否在滑块内,并且⿏标左键是按钮下状态
int m_nSize;//⿏标单击在滑块上,距滑块左边的距离
上⾯的m_nSize,在WM_LBUTTONDOWN消息处理函数求出,以上⾯的图为例,⿏标左键在X轴248像素处按下了,那么m_nSize的值
就是248-235,具体看WM_LBUTTONDOWN消息处理函数中的代码:
void CNewScrollBar::OnLButtonDown(UINT nFlags, CPoint point)
if(m_rcThumb.PtInRect(point))
{
m_isTrace=TRUE;
m_nSize=point.x-m_rcThumb.left;
SetCapture();
}
//CScrollBar::OnLButtonDown(nFlags, point);    不让⽗类处理
那么在WM_MOVEMOUSE消息处理函数中,代码就是这样的:
void CNewScrollBar::OnMouseMove(UINT nFlags, CPoint point)
{
// TODO: Add your message handler code here and/or call default
if(m_isTrace)
{
//-17是左边的按钮宽度,⽤常量代替了,动态求按钮宽度的话,在类中定义⼀个变量就可以了,这⾥就不这样做了。
int nPos=(point.x-m_nSize-17)*m_Ratio;
GetParent()->SendMessage(WM_HSCROLL,MAKELONG(SB_THUMBTRACK,nPos),(LPARAM)this->m_hWnd);
}
//CScrollBar::OnMouseMove(nFlags, point);
}
好了,总算完成了最困难的SB_THUMBTRACK消息发送,剩下⼏个消息的正确发送,参照⼯程⾥的代码吧,这⾥就不提了。前⾯说过要拦截SBM_SETSCROLLINFO消息,防⽌⽗类参与绘制滚动条,
这⾥就简单的处理下,这样做当然有很多弊端,但这是学习⽤的,从简到难。现在也不需要做出什么完善,漂亮的滚动来,有了这个基础,等以后需要的时候再来做。算是⼊门吧。
给CNewScrollBar添加虚函数WindowProc,该函数的代码如下:
LRESULT CNewScrollBar::WindowProc(UINT message, WPARAM wParam, LPARAM lParam)
{
// TODO: Add your specialized code here and/or call the base class
if(message==SBM_SETSCROLLINFO)
{
LRESULT res= CScrollBar::WindowProc(message, wParam, lParam);//让⽗类处理
//CNewScrollBar没有⾃⼰的滚动条信息结构,所以还得依靠⽗类。调⽤上⾯的函数,会让⽗类绘制滚动条。这是我们不想见到的
//所以在下⾯⽴即刷新,这⾥增加了⼀次不必要的绘制,如果要避免的话,必须拥有⾃⼰的信息结构也
就是SCROLLINFO来⾃⼰设置
//后⾯我会给⼀个⼯程,这是个完整的⼯程,这个⼯程是我在CodeProject⽹站⾥下载的,⾥⾯避免了调⽤⽗类WindowProc⽅法
this->Invalidate();//⽴即刷新
return res;
}
return CScrollBar::WindowProc(message, wParam, lParam);
}
先单击“Button1"设置滚动条信息。我只是简单的画了下,有兴趣的朋友可以⽤位图来绘制⼀下。另外,如果快速单击按钮的话,会发现有⼀定的间隔,速度跟不上标准的滚动条,这是因为滚动条的窗⼝类⽀持⿏标双击(CS_DBLCLKS),我不知道标准的滚动条是怎么做到的,既⽀持双击,速度⼜够快。⽽我只能去掉窗⼝类的双击属性。反正我也⽤不了双击,这也并没有什么影响。
⽅法是给CNewScrollBar添加虚函数PreCreateWindow,在创建滚动条的时候,改变它的窗⼝类。如下:
BOOL CNewScrollBar::PreCreateWindow(CREATESTRUCT& cs)
{
// TODO: Add your specialized code here and/or call the base class
WNDCLASS wndcls;
//窗⼝类名ScrollBar,也就是wndcls.lpszClassName,已经注册了,创建的时候直接⽤
//下⾯这个函数获取窗⼝类信息,以wndcls.lpszClassName做标识
GetClassInfo(NULL,"ScrollBar",&wndcls);
wndcls.style^=CS_DBLCLKS;//去掉双击属性
wndcls.lpszClassName="newScrollBar";//新窗⼝类名
cs.lpszClass=wndcls.lpszClassName;
AfxRegisterClass(&wndcls);//注册窗⼝类
return CScrollBar::PreCreateWindow(cs);
}
但是对上⾯的那个滚动条并没有效,依然产⽣双击消息,原因是上⾯滚动条是对话框⾥的资源,创建的时候,并没有通过PreCreateWindow 函数,⽽是系统直接创建了,然后我估计调⽤类似SubClassWindow的函数再给其关联起来。所以这个⽅法只对调⽤控件⾥的Create函数创造的滚动条有效。Create函数创建滚动条的代码⽰例如下:
CNewScrollBar m_sr;
CRect rect=CRect(0,0,200,17);
m_sr.Create(WS_CHILD|WS_VISIBLE,rect,this,1002);
⼀试,单击响应速度果然没有间隔了。
记得先单击“Button1”按钮设置滚动信息。
滚动条变短是什么原因
另外我从CodeProject下载的⼯程地址:这个⼯程⽐较完善,可以参考⼀下。我就是参考它的。。。。。。
如果实现滚动条热点功能,可以⽤定时器,来实现,每隔⼀段时间,判断。(⿏标离开滚动条时取消计时,节约资源)

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。