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);
        }

举一反三

根据本实例,读者可以:

自定义树视图控件。