Duilib 源码分析之 CScrollBarUI 篇

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 ,导致纵向滚动条的可拖动部分很短,鼠标不容易选中。后来我就加了一个属性,控制可拖动部分的最小长度。

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

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
duilib是一个开的C++图形界面库,它提供了丰富的控件和布局管理功能,以及良好的跨平台支持。下面我将对duilib码进行简要分析。 1. 基础架构:duilib码采用了模块化的设计,主要分为“Core”和“UI”两个部分。其中,“Core”模块是duilib的核心部分,提供了基础的窗口、消息循环等功能;而“UI”模块则提供了各种控件和布局管理等高级功能。 2. 控件类别:duilib提供了丰富的控件类别,包括基础的窗口类(如窗口、对话框)、容器类(如水平布局、垂直布局)、常用控件类(如按钮、文本框)、自定义控件类等。每个控件类都有相应的成员函数和消息处理函数,以便实现对控件的创建、设置属性和处理事件等操作。 3. 消息处理机制:duilib使用了消息映射的机制来处理控件的事件。每个控件类都有自己的消息映射表,用于将消息和相应的处理函数关联起来。当控件接收到特定的消息时,duilib会根据映射表找到对应的处理函数进行处理。 4. 布局管理:duilib提供了灵活且强大的布局管理功能,可以通过设置布局属性实现控件的自动适应和自动排列。布局管理器可以根据指定的规则对子控件进行自动布局,以适应不同的窗口尺寸。 5. 绘制引擎:duilib使用了自定义的绘制引擎来实现界面绘制。该绘制引擎可以根据控件的属性和状态来决定绘制的方式,以实现不同的视觉效果。 总结来说,duilib分析涉及到基础架构、控件类别、消息处理机制、布局管理和绘制引擎等方面。通过深入研究这些内容,我们可以更好地理解duilib的设计理念和工作原理,以便能够更好地使用和定制duilib提供的功能。

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

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值