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_cliRect=sbi.rcScrollBar;
 //计算左箭头按钮大小
 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=CRect(sbi.xyThumbTop,0,sbi.xyThumbBottom,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_hWnd);//左(上)按钮被单击

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下载的工程地址:http://www.codeproject.com/KB/dialog/skinscrollbar.aspx  这个工程比较完善,可以参考一下。我就是参考它的。。。。。。

如果实现滚动条热点功能,可以用定时器,来实现,每隔一段时间,判断。(鼠标离开滚动条时取消计时,节约资源)


  • 0
    点赞
  • 7
    收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
©️2022 CSDN 皮肤主题:编程工作室 设计师:CSDN官方博客 返回首页
评论
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值