Duilib 源码分析之 CScrollBarUI 篇

来源:互联网 发布:淘宝流量推广工具 编辑:程序博客网 时间:2024/06/04 00:40

Scrollbar 的创建时机是在解析 UIContainer 类型的控件时,发现有 hscrollbarvscrollbar 时,调用 void CContainerUI::EnableScrollBar(bool bEnableVertical = true, bool bEnableHorizontal = false) 实现,请关注下方法中的以下代码:

  • void CScrollBarUI::SetHorizontal(bool bHorizontal = true) 设置 Scrollbar 的类型,依赖于当前解析的是 hscrollbar 属性还是 vscrollbar
  • void CScrollBarUI::SetOwner(CContainerUI* pOwner) 设置 Scrollbar 所属的父控件
  • void CControlUI::SetManager(CPaintManagerUI* pManager, CControlUI* pParent, bool bInit = true) 关联当前 CPaintManagerUI 对象
  • CControlUI* CControlUI::ApplyAttributeList(LPCTSTR pstrList) 一般来说我们会将共通的 Scrollbar 属性写在 xml 的 Default 属性中,此时通过调用 m_pManager->GetDefaultAttributeList(_T("HScrollBar")) 获取对应 Scrollbar 的属性列表并设置到当前 Scrollbar 中

Scrollbar 的位置设置是在所属父控件执行 SetPos 时,调用 void ProcessScrollBar(RECT rc, int cxRequired, int cyRequired) 实现,由于代码稍长且比较简单,在此不再列出,只简单说一下逻辑,以横向滚动条为例,若有不明白的地方可以留言

  • cxRequired 代表滚动条所属控件横向所需大小, cxRequired 是在所属控件 SetPos 过程中遍历所有子控件后确定,也就是从最左端的子控件到最右端子控件共有多长
  • cxRequired 不大于所属控件的宽度,则不再需要横向滚动条
  • 在符合滚动条显示条件的情况下,确定 Scrollbar 的位置,且调用 void SetScrollRange(int nRange) 函数设置 Scrollbar 的范围, nRange 的值为 cxRequired - 所属控件的宽度(减掉 insetleftright 后的宽度)

接下来说一下 Scrollbar 最重要的部分,关于位移相关的逻辑,关键的两个方法为 SetPosDoEvent ,接下来详细介绍一下

  • void DoEvent(TEventUI& event)

    • m_nLastScrollOffset 鼠标拖动滚动条时单位时间内,父控件需位移的距离,单位时间指的是 m_pManager->SetTimer(this, DEFAULT_TIMERID, 50U) 中的 50ms。 关于 m_nLastScrollOffset 的计算代码如下(竖向滚动条):

      int vRange = m_rcItem.bottom - m_rcItem.top - m_rcThumb.bottom + m_rcThumb.top - 2 * m_cxyFixed.cx;if (vRange != 0) m_nLastScrollOffset = (event.ptMouse.y - ptLastMouse.y) * m_nRange / vRange;

      vRange 代表滚动条可以移动的距离,也就是滚动条内除了两端的按钮,和可拖动的条状长度外剩下的长度。比例关系为: 滚动条可以移动的距离(vRange) / 父控件可以移动的距离(m_nRange) = 滚动条已经移动的距离(event.ptMouse.y - ptLastMouse.y) / 父控件已经移动的距离(m_nLastScrollOffset)。 由此得出上述计算方法

    • m_nScrollRepeatDelay 鼠标持续按下两端的按钮或空白区域时, Timer 的执行次数。m_nScrollRepeatDelay 相关的代码—— if( event.Type == UIEVENT_TIMER && event.wParam == DEFAULT_TIMERID ) 代码段中, 目的是为了实现持续按下时,不断地移动滚动条和父控件。 在刚按下时,首先位移一次,且在 m_nScrollRepeatDelay > 6 的情况下,也就是经过 300ms 后,开始持续位移

    • 鼠标点击两端按钮,和鼠标点在空白区域时,父控件的位移大小不一样

      • 点击在按钮时,若是竖向滚动条,代码如下:

        void CContainerUI::LineDown(){int cyLine = GetScrollStepSize();if (cyLine == 0) {    cyLine = 8;if( m_pManager ) cyLine = m_pManager->GetDefaultFontInfo()->tm.tmHeight + 8;}SIZE sz = GetScrollPos();sz.cy += cyLine;SetScrollPos(sz);}

        若父控件设置了 scrollstepsize 属性,则移动对应的距离。否则移动 m_pManager->GetDefaultFontInfo()->tm.tmHeight + 8
        若是横向滚动条,则移动 m_nLineSize, 默认为 8, 可以由滚动条的属性 linesize 控制

      • 若点击在空白区域,则移动的距离为父控件可见区域的大小,相当于翻页
      • 点击在可拖动的区域上时,会设置一个关键是属性 UISTATE_CAPTURED, 在鼠标松开会删除此属性。 之后在响应 Timer 时若发现有此属性,则认为是用户在拖动滚动条
  • void SetPos(RECT rc, bool bNeedInvalidate = true)
    这个方法内算法分为横向和纵向两种,代码不难且比较长,不再详细介绍。这里解释一下以下代码:(横向为例)

    int cxThumb = cx * (rc.right - rc.left) / (m_nRange + rc.right - rc.left);if( cxThumb < m_cxyFixed.cy ) cxThumb = m_cxyFixed.cy;m_rcThumb.left = m_nScrollPos * (cx - cxThumb) / m_nRange + m_rcButton1.right;
    • cxThumb 指可以拖动的部分的长度,这个长度的定义不固定,这里定义的比例关系为: 滚动条的总长度(rc.right - rc.left) / 父控件内部总长度(m_nRange + rc.right - rc.left) = 滚动条可拖动长度(cxThumb) / 滚动条不包括两端按钮的长度(cx)。 之所以说这个定义不固定,大家可以这样想: 假如 cxThumb 比较短,大不了做成拖动比较长的距离,父控件才移动单位距离, 若 cxThumb 比较长,则会拖动比较短的距离,父控件就会移动单位距离
    • 在这部分代码中cxThumb 最小被设置为 m_cxyFixed.cy。也就是和滚动条等高。 在实际项目中,我曾经碰到过一个很长的 list ,导致纵向滚动条的可拖动部分很短,鼠标不容易选中。后来我就加了一个属性,控制可拖动部分的最小长度。

以上内容介绍了滚动条的位移实现原理,若有不明白的地方可以留言询问。滚动条剩下的内容就是绘制了,这部分就不做具体介绍了,绘制的顺序为: 背景->两端的按钮->可拖动部分->可拖动部分上面的图案->边框

原创粉丝点击