Duilib中带有权重的灵活控件排列实现(一)

在开发播放器软件过程中,因为窗口的大小是可变的,为了让控制栏部分的控件(播放,上一集,下一集,全屏,字幕等)适应窗口的尺寸的变化而显示隐藏,产品经理会定义一系列的规则,好让在任何时候都最核心的功能提供给用户使用。

先列一下产品经理给予的需求:

两边往中间缩,保证左侧LOGO和右侧X最优先显示。

顶部隐藏优先级:搜索栏,换肤,意见反馈,播放记录,最小化,最大化

底部隐藏优先级:全屏,画质增强,无痕,打开文件,播放顺序,音量条

在处理这个需求过程中,前人也尝试了一些方法,比较通过全float绝对布局的方式,自己通过管理类来完全订制。而我个人还是希望通过而优雅的相对布局,并利用Container自身的排布算法来实现。经过几天的探索,大致实现上这种效果,在此分享一下思路与实现。

在分析此需求时,我希望引来类似于第3维的权重(weight)概念,即当父容器所提供的位置不足于将所有子控件摆放显示完全时,就按照重要性自低到高依次隐藏,将该控件的显示腾出来提供给更为重要的控件来摆放,简单推演一下,应该是可行的,按照这样简单的规则,可以比较轻松地解决这个需求,并且代码维护起来相对简单。只需要一个能够按照子控件的weight值来排列的父容器,命名为CWeightHorizontalLayoutUI。

OK,让我们来实现这个父容器,首先我介绍一下 CHorizontalLayoutUI::SetPos的方法。

其实就是两次for循环,前一次来试算,将没有设置宽度的控件记录下来,将剩余的空间求个平均数,设置给自适应的控件,而设置了宽度的控件则按设置的值排布。

    void CWeightHorizontalLayoutUI::SetPos(RECT rc)
    {
        CControlUI::SetPos(rc);
        rc = m_rcItem;

        std::map<CControlUI*, int /*width*/> mapAjust;
        // Adjust for inset
        rc.left     += m_rcInset.left;
        rc.top      += m_rcInset.top;
        rc.right    -= m_rcInset.right;
        rc.bottom   -= m_rcInset.bottom;

        if (m_items.GetSize() == 0) {
            ProcessScrollBar(rc, 0, 0);
            return;
        }

        if (!m_bScrollFloat && m_pVerticalScrollBar && m_pVerticalScrollBar->IsVisible())
            rc.right -= m_pVerticalScrollBar->GetFixedWidth();
        if (!m_bScrollFloat && m_pHorizontalScrollBar && m_pHorizontalScrollBar->IsVisible())
            rc.bottom -= m_pHorizontalScrollBar->GetFixedHeight();

        ResetWeightDisplayControlState(); 

//  读取当前子控件的权重
        m_mapWeight.clear();
        for (int i = 0; i < m_items.GetSize(); i++) {
            DuiLib::CControlUI* pControl = static_cast<DuiLib::CControlUI*>(m_items[i]);
            if (pControl == nullptr) continue;
            CStdString strweight = pControl->GetCustomAttribute(L"weight");
            int nweight = _ttoi(strweight.GetData());
            m_mapWeight[i] = nweight;
        }
        m_mapWeightCache = m_mapWeight;

        // 查找最小权重控件
        auto HideMinWeigth = [&](int index) -> bool {
            CControlUI* pControl = static_cast<CControlUI*>(m_items[index]);
            if (pControl && pControl->IsVisible()) {
                pControl->SetInternVisible(false);
                m_arrWeightHideControl.push_back(pControl);
                return true;
            }
            return false;
        };

        // Determine the width of elements that are sizeable
        SIZE szAvailable = { rc.right - rc.left, rc.bottom - rc.top };
        if (m_pHorizontalScrollBar && m_pHorizontalScrollBar->IsVisible())
            szAvailable.cx += m_pHorizontalScrollBar->GetScrollRange();

        int nAdjustables = 0;
        int cxFixed = 0;
        int nEstimateNum = 0;
        int cxExpand = 0;

        std::pair<int, int> min_weight; // first:control index, second:weight

        std::map<CControlUI*, int /*width*/> m_mapEstimateWidth;  // 试算时控件的预设宽度
        std::map<CControlUI*, int /*width*/> m_mapBookWidth;
        do {
            // 查找出当前权重值最小,并且显示着的控件
            min_weight = std::make_pair(999, 999);
            std::for_each(m_mapWeight.begin(), m_mapWeight.end(), [&](std::pair<int, int> item)-> void {
                CControlUI* pControl = static_cast<CControlUI*>(m_items[item.first]);
                if (item.second <= min_weight.second && pControl->IsVisible())
                    min_weight = item;
            });

            // 已经找到后从map中移除,以避免下次查找的还是这个控件
            auto min_it = std::find_if(m_mapWeight.begin(), m_mapWeight.end(), [min_weight](std::pair<int, int> item)-> bool {
                return (item.first == min_weight.first && item.second == item.second);
            });
            if (min_it != m_mapWeight.end()) m_mapWeight.erase(min_it);

            // 试算中
            nAdjustables = 0;
            cxFixed = 0;
            nEstimateNum = 0;
            for (int it1 = 0; it1 < m_items.GetSize(); it1++) {
                CControlUI* pControl = static_cast<CControlUI*>(m_items[it1]);
                if (pControl->IsFloat()) continue;
                if (!pControl->IsVisible()) continue;
                SIZE sz = pControl->EstimateSize(szAvailable);
                if (sz.cx == 0) {
                     nAdjustables++;
                }
                else {
                    if (sz.cx < pControl->GetMinWidth()) sz.cx = pControl->GetMinWidth();
                    if (sz.cx > pControl->GetMaxWidth()) sz.cx = pControl->GetMaxWidth();
                }
                m_mapEstimateWidth[pControl] = sz.cx;
                cxFixed += sz.cx + pControl->GetPadding().left + pControl->GetPadding().right;
                nEstimateNum++;
            }
            cxFixed += (nEstimateNum - 1) * m_iChildPadding;

            cxExpand = 0;
            if (nAdjustables > 0) cxExpand = max(0, (szAvailable.cx - cxFixed) / nAdjustables);

// 此处下节解释
            // 如果小于空间不够,先尝试从低到高缩减带有adjust属性的控件
            if (szAvailable.cx - cxFixed < 0) {
                std::vector<std::pair<CControlUI*, int>> adj_ctrls;
                for (int it1 = 0; it1 < m_items.GetSize(); it1++) {
                    CControlUI* pControl = static_cast<CControlUI*>(m_items[it1]);
                    if (pControl->IsFloat()) continue;
                    //if (!pControl->IsVisible()) continue;
                    if (pControl->GetCustomAttribute(L"adjustwidth") == NULL ||
                        _tcscmp(pControl->GetCustomAttribute(L"adjustwidth"), L"true")) continue;
                    adj_ctrls.push_back(std::make_pair(pControl, m_mapWeightCache[it1]));
                }

                // 自小到大按权重,先尝试最小权重
                std::sort(adj_ctrls.begin(), adj_ctrls.end(), [&](std::pair<CControlUI*, int> lhs,
                    std::pair<CControlUI*, int> rhs) -> bool {
                    return lhs.second < rhs.second;
                });

                for (auto it = adj_ctrls.begin(); it != adj_ctrls.end(); it++) {
                    auto ctrl = it->first;
                    if (m_mapEstimateWidth[ctrl] > ctrl->GetMinWidth()) {
                        int sub_width = min(abs(szAvailable.cx - cxFixed), m_mapEstimateWidth[ctrl] - ctrl->GetMinWidth());
                        m_mapEstimateWidth[ctrl] = m_mapEstimateWidth[ctrl] - sub_width;    // 新的试算宽度
                        m_mapBookWidth[ctrl] = m_mapEstimateWidth[ctrl];
                        cxFixed -= sub_width;
                        if (szAvailable.cx - cxFixed >= 0) break; // 已经满足排列空间
                    }
                }
            }

            // 空间不够,从权重最小的依次隐藏
        } while (szAvailable.cx - cxFixed < 0 && HideMinWeigth(min_weight.first));

        // Position the elements
        SIZE szRemaining = szAvailable;
        int iPosX = rc.left;
        if (m_pHorizontalScrollBar && m_pHorizontalScrollBar->IsVisible()) {
            iPosX -= m_pHorizontalScrollBar->GetScrollPos();
        }
        int iAdjustable = 0;
        int cxFixedRemaining = cxFixed;
        for (int it2 = 0; it2 < m_items.GetSize(); it2++) {
            CControlUI* pControl = static_cast<CControlUI*>(m_items[it2]);
            if (!pControl->IsVisible()) continue;
            if (pControl->IsFloat()) {
                SetFloatPos(it2);
                continue;
            }
            RECT rcPadding = pControl->GetPadding();
            szRemaining.cx -= rcPadding.left;
            SIZE sz = pControl->EstimateSize(szRemaining);
            if (sz.cx == 0) {
                iAdjustable++;
                sz.cx = cxExpand;
                //最后一个自适应尺寸大小无需另行计算
                //if( iAdjustable == nAdjustables ) {
                //	sz.cx = MAX(0, szRemaining.cx -cxFixedRemaining);
                //}
                if (sz.cx < pControl->GetMinWidth()) sz.cx = pControl->GetMinWidth();
                if (sz.cx > pControl->GetMaxWidth()) sz.cx = pControl->GetMaxWidth();
            }
            else {
                if (sz.cx < pControl->GetMinWidth()) sz.cx = pControl->GetMinWidth();
                if (sz.cx > pControl->GetMaxWidth()) sz.cx = pControl->GetMaxWidth();
            }

            sz.cy = pControl->GetFixedHeight();
            if (sz.cy == 0) sz.cy = rc.bottom - rc.top - rcPadding.top - rcPadding.bottom;
            if (sz.cy < 0) sz.cy = 0;
            if (sz.cy < pControl->GetMinHeight()) sz.cy = pControl->GetMinHeight();
            if (sz.cy > pControl->GetMaxHeight()) sz.cy = pControl->GetMaxHeight();

            //padding不算控件宽度//2012/09/12
            //RECT rcCtrl = { iPosX + rcPadding.left, rc.top + rcPadding.top, iPosX + sz.cx + rcPadding.left + rcPadding.right, rc.top + rcPadding.top + sz.cy};
            if (m_mapBookWidth.find(pControl) != m_mapBookWidth.end() &&
                m_mapBookWidth[pControl] != sz.cx) {
                sz.cx = m_mapBookWidth[pControl];
            }
            RECT rcCtrl = { iPosX + rcPadding.left, rc.top + rcPadding.top, min(iPosX + sz.cx + rcPadding.left, rc.right) , rc.top + rcPadding.top + sz.cy };
            pControl->SetPos(rcCtrl);
            iPosX += sz.cx + m_iChildPadding + rcPadding.left + rcPadding.right;
            szRemaining.cx -= sz.cx + m_iChildPadding + rcPadding.right;
            if (m_bWholeDisplay && rcCtrl.right > rc.left + szAvailable.cx)
                m_arrWholeDisplayControl.push_back(pControl);
        }

        // Process the scrollbar
        ProcessScrollBar(rc, 0, 0);
    }

以上代码主要是在试算过程中,在剩余的空间宽度不足时隐藏一下最小的权重控件,再次试算一遍

需要留心的是当Hide时不能够直接调用子控件的SetVisible接口,这样会改变控件的基础状态,而应使用SetInternVisible,并且在再次SetPos时,清除这样的记录,并将状态重置,这样,不影响再次的试算过程。基本以上实现了控制栏的自定义隐藏顺序。而我们只需要在xml文件中配置一下各个控件的权重即可。

实现效果:

时间: 2024-10-05 15:56:08

Duilib中带有权重的灵活控件排列实现(一)的相关文章

分享个Duilib中基于wke的浏览器控件

概述 wke是基于谷歌chrome浏览器源代码的裁剪版本,大小仅仅只有10M左右,无需依赖其他的扩展库(跟CEF的一大堆大约40M的DLL来比简直爽呆了),就可以在本地使用谷歌内核快速加载网页.网上也有基于wke在Duilib 上扩展的控件代码,其实wke头文件挺清楚的了,接口一目了然,特别是JS与C++交互的函数更是容易看懂,也没什么难的,你也可以做到的. 代码 毕竟是裁剪库,有的功能还是没有接口来处理的(比如网页加载前.页面跳转.菜单消息--),头文件代码: #ifndef __UIWKEW

在WebBrowser中通过模拟键盘鼠标操控网页中的文件上传控件

在WebBrowser中通过模拟键盘鼠标操控网页中的文件上传控件 引言 这两天沉迷了Google SketchUp,刚刚玩够,一时兴起,研究了一下WebBrowser. 我在<WebBrowser控件使用技巧分享>一文中曾谈到过"我现在可以通过WebBrowser实现对各种Html元素的操控,唯独无法控制Html的上传控件",出于安全原因,IE没有对上传控件提供操控支持,这使得我们没法像控制其他控件一样用简单的代码进行赋值. 比较实际的解决方案就是模拟操作了,下面我就将演示

在web浏览器窗口中编辑报表的报表控件Stimulsoft Reports.Web

Stimulsoft Reports.Web是一个报表工具,适用于Web的报表生成器控件.其设计的目的在于通过Web浏览器创建和渲染报表.您可以创建报表,显示报表,打印报表,导出报表. Stimulsoft Reports.Web将提供完整的报表创建周期,从报表模板开始到在浏览器中显示报表为止.这一过程可在web浏览器未被关闭时完成.Stimulsoft Reports.Web是第一款可以让您直接在Web中编辑报表的报表工具.在您的客户端的机器里不需要安装.Net框架.ActiveX控件或其他特

Duilib学习笔记《03》— 控件使用

在前面已经对duilib有个一个基本的了解,并且创建了简单的空白窗体.这仅仅只是一个开始,如何去创建一个绚丽多彩的界面呢?这就需要一些控件元素(按钮.文本框.列表框等等)来完善. 一. Duilib控件简介 在之前空白窗体的基础上,在界面上添加了一些控件,让大家先对这些控件效果有个基本的认识.如下图所示: 基本控件 高级控件 一些控件的基本显示效果就如同上面两幅图所示.实际上,在Duilib学习笔记<01>—duilib整体框架认识中我们就已经提到过Duilib这个库的组成,其中就提到了控件这

WPF中不规则窗体与WindowsFormsHost控件的兼容问题完美解决方案

首先先得瑟一下,有关WPF中不规则窗体与WindowsFormsHost控件不兼容的问题,网上给出的解决方案不能满足所有的情况,是有特定条件的,比如  WPF中不规则窗体与WebBrowser控件的兼容问题解决办法.该网友的解决办法也是别出心裁的,为什么这样说呢,你下载了他的程序认真读一下就便知道,他的webBrowser控件的是单独放在一个Form中,让这个Form与WPF中的一个Bord控件进行关联,进行同步移动,但是在移动的时候会出现闪烁,并且还会出现运动的白点,用户体验肯定不好. OK,

FineUI之使用SQL脚本从数据库表中生成相应的输入控件

在WEB开发时,经常需要依据数据库表中的字段建立相应的输入控件,来获取输入的数据.每次都需要按字段来敲,显然太低效,而且容易出错.这里提供一个SQL脚本生成相应输入控件的方法. USE DBDemo DECLARE @TEMP_TABLE_NAME NVARCHAR(512) DECLARE @WIDTH NVARCHAR(50) SET @TEMP_TABLE_NAME='Stuff' SET @WIDTH='200' SELECT '<f:'+TOKEN+' runat="server

解决Android中,禁止ScrollView内的控件改变之后自动滚动

问题: 最近在写一个程序界面,有一个scrollVIew,其中有一段内容是需要在线加载的. 当内容加载完成后,ScrollView中内容的长度会发生改变,这时ScrollView会自动下滚,如下图所示: 滚动的那一下体验特别不好,所以要防止这种情况.即不论Scrollview中内容如何,都要保持在最上. 解决办法: 先简单写一下我的xml文件的结构: [html] view plaincopy <ScrollView android:id="@+id/scrollView1" a

VB6.0中,日期、时间控件不允许为空时,采用文本框与日期、时间控件相互替换赋值(解决方案)

VB6.0中,日期.时间控件不允许为空时,采用文本框与日期.时间控件相互替换赋值,或许是一个不错的选择. 实现效果如下图: 代码如下: 文本框txtStopTime1 时间框DTStopTime1 格式3 - dtpCustom  HH:mm:ss Private Sub Form_Load()       txtStopTime1.ZOrder       DTStopTime1.Top = txtStopTime1.Top       DTStopTime1.Left = txtStopTi

ASP.NET MVC中加载WebForms用户控件(.ascx)

原文:ASP.NET MVC中加载WebForms用户控件(.ascx) 问题背景 博客园博客中的日历用的是ASP.NET WebForms的日历控件(System.Web.UI.WebControls.Calendar),它会为“上一月”.“下一月”的链接生成"__doPostBack()"的js调用,如下图: 目前发现它会带来两个问题: 1. 不支持IE10: 2. 某些电脑不允许执行__doPostBack. 问题提炼 前提: 我们想以最低的成本解决这个问题,也就是对当前代码尽可