Duilib自定义布局开发:实现复杂界面的高级技巧

Duilib自定义布局开发:实现复杂界面的高级技巧

【免费下载链接】duilib 【免费下载链接】duilib 项目地址: https://gitcode.com/gh_mirrors/du/duilib

引言:告别界面布局困境

你是否还在为Duilib默认布局无法满足复杂界面需求而烦恼?是否经历过因布局嵌套过深导致的性能问题?本文将系统讲解Duilib布局系统的底层原理与高级应用技巧,通过12个实战案例带你掌握自定义布局开发的核心方法,最终能够构建出响应式强、性能优异的复杂桌面界面。

读完本文你将获得:

  • 精通Duilib五大核心布局管理器的实现原理
  • 掌握自定义布局的完整开发流程与调试技巧
  • 学会性能优化的7个实用策略
  • 获取10+可直接复用的布局组件代码模板

一、Duilib布局系统架构解析

1.1 布局管理器核心类图

mermaid

1.2 布局渲染流程

mermaid

二、核心布局管理器深度剖析

2.1 水平布局(CHorizontalLayoutUI)

2.1.1 关键属性与方法
属性/方法说明默认值应用场景
SetSepWidth(int iWidth)设置分隔条宽度4px需要用户调整控件宽度时
SetSepImmMode(bool bImmediately)设置即时调整模式false性能优化,大型界面建议设为false
GetThumbRect()获取分隔条矩形区域-自定义分隔条样式时
SetAttribute()设置XML属性-从XML配置布局
2.1.2 实战代码:可调整宽度的水平布局
// 1. 创建水平布局
CHorizontalLayoutUI* pHorizontalLayout = new CHorizontalLayoutUI();
pHorizontalLayout->SetSepWidth(6);  // 设置分隔条宽度为6像素
pHorizontalLayout->SetSepImmMode(false);  // 关闭即时调整模式提升性能

// 2. 添加子控件
CButtonUI* pBtn1 = new CButtonUI();
pBtn1->SetText(_T("左侧按钮"));
pBtn1->SetFixedWidth(100);  // 固定宽度

CListUI* pList = new CListUI();
pList->SetFloat(true);  // 浮动模式,将占据剩余空间
pList->SetAttribute(_T("width"), _T("100%"));

pHorizontalLayout->Add(pBtn1);
pHorizontalLayout->Add(pList);

// 3. 响应分隔条拖动事件
class MyHorizontalLayout : public CHorizontalLayoutUI {
    void DoEvent(TEventUI& event) override {
        if (event.Type == UIEVENT_SEPARATOR) {
            // 分隔条拖动事件处理
            OutputDebugString(_T("分隔条位置改变\n"));
        }
        CHorizontalLayoutUI::DoEvent(event);
    }
};
2.1.3 XML配置示例
<HorizontalLayout sepwidth="6" sepimmode="false">
    <Button text="功能按钮" width="100" height="30" />
    <ListUI width="100%" float="true">
        <!-- 列表内容 -->
    </ListUI>
</HorizontalLayout>

2.2 垂直布局(CVerticalLayoutUI)

2.2.1 与水平布局的核心差异

垂直布局与水平布局共享相似的设计理念,但在以下方面有所不同:

特性水平布局垂直布局
调整方向左右拖动分隔条上下拖动分隔条
关键属性sepwidthsepheight
尺寸计算宽度分配高度分配
滚动方向水平滚动垂直滚动
2.2.2 高级应用:动态添加垂直项
CVerticalLayoutUI* pVerticalLayout = new CVerticalLayoutUI();
pVerticalLayout->SetSepHeight(3);  // 设置垂直分隔条高度

// 动态添加10个项目
for (int i = 0; i < 10; i++) {
    CContainerUI* pItem = new CContainerUI();
    pItem->SetFixedHeight(40);
    pItem->SetBkColor(0xFFEEEEEE);
    
    CLabelUI* pLabel = new CLabelUI();
    TCHAR szText[32];
    _stprintf_s(szText, _T("项目 %d"), i+1);
    pLabel->SetText(szText);
    pItem->Add(pLabel);
    
    pVerticalLayout->Add(pItem);
    
    // 添加分隔线
    if (i < 9) {
        CControlUI* pSeparator = new CControlUI();
        pSeparator->SetFixedHeight(1);
        pSeparator->SetBkColor(0xFFDDDDDD);
        pVerticalLayout->Add(pSeparator);
    }
}

2.3 标签布局(UITabLayout)

2.3.1 标签切换原理

UITabLayout通过SelectItem方法控制哪个标签页可见,核心实现如下:

void CTabLayoutUI::SelectItem(int iIndex) {
    if (iIndex < 0 || iIndex >= m_items.GetSize()) return;
    
    int iOldSel = m_iCurSel;
    m_iCurSel = iIndex;
    
    for (int i = 0; i < m_items.GetSize(); i++) {
        CControlUI* pControl = static_cast<CControlUI*>(m_items[i]);
        if (i == iIndex) {
            pControl->SetVisible(true);
            pControl->SetPos(m_rcItem);  // 显示并设置位置
        } else {
            pControl->SetVisible(false);  // 隐藏其他标签页
        }
    }
    
    if (m_pTabHeader != NULL) {
        m_pTabHeader->SelectItem(iIndex);  // 同步更新标签头
    }
    
    NeedUpdate();
}
2.3.2 实战:带关闭按钮的标签页
<TabLayout name="main_tab" cursel="0">
    <TabHeader height="30" name="tab_header">
        <TabButton text="首页" normalimage="tab_normal.png" hotimage="tab_hot.png" pushedimage="tab_pushed.png" />
        <TabButton text="设置" normalimage="tab_normal.png" hotimage="tab_hot.png" pushedimage="tab_pushed.png" />
        <TabButton text="帮助" normalimage="tab_normal.png" hotimage="tab_hot.png" pushedimage="tab_pushed.png" />
    </TabHeader>
    
    <VerticalLayout name="page_0" visible="true">
        <Label text="首页内容" />
    </VerticalLayout>
    
    <VerticalLayout name="page_1" visible="false">
        <Label text="设置页面" />
    </VerticalLayout>
    
    <VerticalLayout name="page_2" visible="false">
        <Label text="帮助页面" />
    </VerticalLayout>
</TabLayout>
// 为标签添加关闭功能
class ClosableTabButton : public CTabButtonUI {
public:
    void DoEvent(TEventUI& event) override {
        if (event.Type == UIEVENT_BUTTONDOWN && IsEnabled()) {
            // 判断是否点击了关闭按钮区域
            RECT rcClose = GetCloseBtnRect();
            if (PtInRect(&rcClose, event.ptMouse)) {
                // 发送关闭事件
                TEventUI eventClose;
                eventClose.Type = UIEVENT_TABCLOSE;
                eventClose.pSender = this;
                eventClose.wParam = GetIndex();
                GetManager()->SendEvent(eventClose);
                return;
            }
        }
        CTabButtonUI::DoEvent(event);
    }
    
    RECT GetCloseBtnRect() const {
        RECT rc = m_rcItem;
        rc.left = rc.right - 20;
        rc.top = rc.top + 5;
        rc.bottom = rc.top + 15;
        return rc;
    }
};

2.4 瓦片布局(UITileLayout)

2.4.1 自适应排列算法

UITileLayout根据设置的列数自动排列子控件,核心布局算法如下:

void CTileLayoutUI::SetPos(RECT rc) {
    CContainerUI::SetPos(rc);
    
    if (m_items.IsEmpty()) return;
    
    // 计算每个项的大小
    int iColumns = m_iColumns;
    if (iColumns <= 0) {
        // 自动计算列数
        iColumns = rc.right - rc.left / m_iItemWidth;
        if (iColumns <= 0) iColumns = 1;
    }
    
    int iItemWidth = (rc.right - rc.left - (iColumns - 1)*m_iHGap) / iColumns;
    int iItemHeight = m_iItemHeight;
    
    int iRow = 0, iCol = 0;
    for (int i = 0; i < m_items.GetSize(); i++) {
        CControlUI* pItem = static_cast<CControlUI*>(m_items[i]);
        if (!pItem->IsVisible()) continue;
        
        // 计算位置
        RECT rcItem;
        rcItem.left = rc.left + iCol*(iItemWidth + m_iHGap);
        rcItem.top = rc.top + iRow*(iItemHeight + m_iVGap);
        rcItem.right = rcItem.left + iItemWidth;
        rcItem.bottom = rcItem.top + iItemHeight;
        
        pItem->SetPos(rcItem);
        
        // 下一个位置
        iCol++;
        if (iCol >= iColumns) {
            iCol = 0;
            iRow++;
        }
    }
    
    // 调整容器高度
    if (m_bAutoHeight) {
        int iRows = (m_items.GetSize() + iColumns - 1) / iColumns;
        m_rcItem.bottom = rc.top + iRows*(iItemHeight + m_iVGap) - m_iVGap;
    }
}

三、自定义布局开发实战

3.1 自定义布局的完整流程

3.1.1 开发步骤

mermaid

3.1.2 注册自定义布局
// 1. 定义自定义布局类
class CFlowLayoutUI : public CContainerUI {
    // 实现自定义布局逻辑...
};

// 2. 注册控件
namespace DuiLib {
    CControlUI* CreateFlowLayoutUI() {
        return new CFlowLayoutUI();
    }
    
    void RegisterFlowLayout() {
        CPaintManagerUI::GetInstance()->RegisterControl(_T("FlowLayout"), CreateFlowLayoutUI);
    }
}

// 3. 在应用初始化时调用
DuiLib::RegisterFlowLayout();

// 4. 在XML中使用
<FlowLayout columns="3" hgap="10" vgap="10">
    <!-- 子控件 -->
</FlowLayout>

3.2 实战一:流式布局(FlowLayout)

实现类似CSS的flex-wrap效果,子控件自动换行排列:

class CFlowLayoutUI : public CContainerUI {
public:
    CFlowLayoutUI() : m_iHGap(5), m_iVGap(5), m_iLineHeight(0) {}
    
    LPCTSTR GetClass() const override { return _T("FlowLayout"); }
    LPVOID GetInterface(LPCTSTR pstrName) override {
        if (_tcscmp(pstrName, _T("FlowLayout")) == 0) return static_cast<CFlowLayoutUI*>(this);
        return CContainerUI::GetInterface(pstrName);
    }
    
    void SetAttribute(LPCTSTR pstrName, LPCTSTR pstrValue) override {
        if (_tcscmp(pstrName, _T("hgap")) == 0) {
            m_iHGap = _ttoi(pstrValue);
        } else if (_tcscmp(pstrName, _T("vgap")) == 0) {
            m_iVGap = _ttoi(pstrValue);
        } else {
            CContainerUI::SetAttribute(pstrName, pstrValue);
        }
    }
    
    void SetPos(RECT rc) override {
        CContainerUI::SetPos(rc);
        
        if (m_items.IsEmpty()) return;
        
        int iLeft = rc.left + m_rcInset.left;
        int iTop = rc.top + m_rcInset.top;
        int iRight = rc.right - m_rcInset.right;
        int iBottom = rc.bottom - m_rcInset.bottom;
        
        int iCurrentX = iLeft;
        int iCurrentY = iTop;
        m_iLineHeight = 0;
        
        for (int i = 0; i < m_items.GetSize(); i++) {
            CControlUI* pItem = static_cast<CControlUI*>(m_items[i]);
            if (!pItem->IsVisible()) continue;
            
            RECT rcItem = pItem->GetPos();
            SIZE szDesired = pItem->EstimateSize(SIZE{9999, 9999});
            
            // 如果需要换行
            if (iCurrentX + szDesired.cx > iRight && iCurrentX > iLeft) {
                iCurrentX = iLeft;
                iCurrentY += m_iLineHeight + m_iVGap;
                m_iLineHeight = 0;
            }
            
            // 设置位置
            rcItem.left = iCurrentX;
            rcItem.top = iCurrentY;
            rcItem.right = iCurrentX + szDesired.cx;
            rcItem.bottom = iCurrentY + szDesired.cy;
            pItem->SetPos(rcItem);
            
            // 更新当前位置和行高
            iCurrentX += szDesired.cx + m_iHGap;
            if (szDesired.cy > m_iLineHeight) {
                m_iLineHeight = szDesired.cy;
            }
        }
    }
    
private:
    int m_iHGap;       // 水平间距
    int m_iVGap;       // 垂直间距
    int m_iLineHeight; // 当前行高
};

3.3 实战二:响应式布局(ResponsiveLayout)

根据窗口宽度自动调整布局结构:

class CResponsiveLayoutUI : public CContainerUI {
public:
    enum LayoutMode {
        MODE_MOBILE,    // 移动设备模式 (<= 768px)
        MODE_TABLET,    // 平板模式 (769px - 1024px)
        MODE_DESKTOP    // 桌面模式 (> 1024px)
    };
    
    CResponsiveLayoutUI() : m_iBreakpoint1(768), m_iBreakpoint2(1024), m_eMode(MODE_DESKTOP) {}
    
    void SetPos(RECT rc) override {
        CContainerUI::SetPos(rc);
        
        // 计算当前模式
        int iWidth = rc.right - rc.left;
        LayoutMode eNewMode = MODE_DESKTOP;
        if (iWidth <= m_iBreakpoint1) {
            eNewMode = MODE_MOBILE;
        } else if (iWidth <= m_iBreakpoint2) {
            eNewMode = MODE_TABLET;
        }
        
        // 如果模式没变,不需要重新布局
        if (eNewMode == m_eMode && !m_bFirstLayout) return;
        
        m_eMode = eNewMode;
        m_bFirstLayout = false;
        
        // 根据不同模式重新排列控件
        ArrangeControls(rc, eNewMode);
    }
    
    void ArrangeControls(RECT rc, LayoutMode mode) {
        if (mode == MODE_MOBILE) {
            // 移动模式:垂直排列所有控件,占满宽度
            ArrangeVertical(rc, true);
        } else if (mode == MODE_TABLET) {
            // 平板模式:两列布局
            ArrangeColumns(rc, 2);
        } else {
            // 桌面模式:三列布局
            ArrangeColumns(rc, 3);
        }
    }
    
    void ArrangeVertical(RECT rc, bool bFullWidth) {
        int iTop = rc.top + m_rcInset.top;
        int iLeft = rc.left + m_rcInset.left;
        int iRight = rc.right - m_rcInset.right;
        
        for (int i = 0; i < m_items.GetSize(); i++) {
            CControlUI* pItem = static_cast<CControlUI*>(m_items[i]);
            if (!pItem->IsVisible()) continue;
            
            RECT rcItem = pItem->GetPos();
            SIZE szDesired = pItem->EstimateSize(SIZE{iRight - iLeft, 9999});
            
            rcItem.left = iLeft;
            rcItem.top = iTop;
            rcItem.right = bFullWidth ? iRight : iLeft + szDesired.cx;
            rcItem.bottom = iTop + szDesired.cy;
            
            pItem->SetPos(rcItem);
            iTop = rcItem.bottom + m_iVGap;
        }
    }
    
    void ArrangeColumns(RECT rc, int iColumns) {
        // 实现多列布局逻辑...
    }
    
    // 设置断点
    void SetBreakpoints(int iMobile, int iTablet) {
        m_iBreakpoint1 = iMobile;
        m_iBreakpoint2 = iTablet;
    }
    
private:
    int m_iBreakpoint1;  // 移动设备断点
    int m_iBreakpoint2;  // 平板设备断点
    LayoutMode m_eMode;  // 当前模式
    bool m_bFirstLayout; // 是否首次布局
    int m_iVGap;         // 垂直间距
};

四、高级布局技巧与性能优化

4.1 布局嵌套最佳实践

4.1.1 避免过度嵌套

mermaid

4.1.2 嵌套布局性能对比
嵌套层级控件数量重绘时间(ms)CPU占用率适用场景
1-2层<50个5-10ms<5%简单界面,如对话框
3-4层50-200个10-30ms5-15%中等复杂度界面,如设置面板
5层以上>200个>30ms>15%复杂界面,如主程序窗口(需优化)

4.2 性能优化策略

4.2.1 虚拟列表技术

对于大量数据展示,使用虚拟列表只渲染可见区域的项:

class CVirtualListUI : public CListUI {
public:
    void SetTotalCount(int iCount) { m_iTotalCount = iCount; }
    
    void SetPos(RECT rc) override {
        int iOldHeight = m_cxyItemHeight;
        CListUI::SetPos(rc);
        
        // 只保留可见区域的项
        int iVisibleCount = (rc.bottom - rc.top) / m_cxyItemHeight + 2;
        int iFirstVisible = GetScrollPos() / m_cxyItemHeight;
        
        // 清除超出范围的项
        for (int i = 0; i < m_items.GetSize(); ) {
            int iIndex = (CListContainerElementUI*)m_items[i]->GetInterface(_T("ListContainerElement"))->GetIndex();
            if (iIndex < iFirstVisible || iIndex >= iFirstVisible + iVisibleCount) {
                Remove(m_items[i]);
            } else {
                i++;
            }
        }
        
        // 添加可见区域的项
        for (int i = iFirstVisible; i < iFirstVisible + iVisibleCount && i < m_iTotalCount; i++) {
            if (!IsItemExist(i)) {
                CListContainerElementUI* pItem = CreateVirtualItem(i);
                InsertAt(pItem, i - iFirstVisible);
            }
        }
        
        // 更新滚动条范围
        SetScrollRange(m_iTotalCount * m_cxyItemHeight);
    }
    
private:
    bool IsItemExist(int iIndex) {
        // 检查项是否已存在
        for (int i = 0; i < m_items.GetSize(); i++) {
            CListContainerElementUI* pItem = static_cast<CListContainerElementUI*>(m_items[i]);
            if (pItem->GetIndex() == iIndex) return true;
        }
        return false;
    }
    
    CListContainerElementUI* CreateVirtualItem(int iIndex) {
        // 创建项并填充数据
        CListContainerElementUI* pItem = new CListContainerElementUI();
        pItem->SetIndex(iIndex);
        
        // 从数据提供者获取数据
        if (m_pDataProvider) {
            m_pDataProvider->FillItemData(pItem, iIndex);
        }
        
        return pItem;
    }
    
    int m_iTotalCount = 0;  // 总项数
    IDataProvider* m_pDataProvider = nullptr;  // 数据提供者接口
};
4.2.2 布局计算缓存

缓存布局计算结果,避免重复计算:

class CCacheableLayoutUI : public CHorizontalLayoutUI {
public:
    void SetPos(RECT rc) override {
        // 如果尺寸没变,使用缓存
        if (rc.left == m_rcLastPos.left && rc.right == m_rcLastPos.right &&
            rc.top == m_rcLastPos.top && rc.bottom == m_rcLastPos.bottom) {
            return;
        }
        
        // 保存当前位置用于下次比较
        m_rcLastPos = rc;
        
        // 标记缓存无效
        m_bCacheValid = false;
        CHorizontalLayoutUI::SetPos(rc);
    }
    
    void DoPostPaint(HDC hDC, const RECT& rcPaint) override {
        if (!m_bCacheValid) {
            // 计算并缓存布局结果
            CalculateLayoutCache();
            m_bCacheValid = true;
        }
        
        // 使用缓存绘制
        DrawFromCache(hDC, rcPaint);
    }
    
private:
    void CalculateLayoutCache() {
        // 计算布局并保存结果到缓存
        m_layoutCache.clear();
        for (int i = 0; i < m_items.GetSize(); i++) {
            CControlUI* pItem = static_cast<CControlUI*>(m_items[i]);
            m_layoutCache.push_back(pItem->GetPos());
        }
    }
    
    void DrawFromCache(HDC hDC, const RECT& rcPaint) {
        // 使用缓存的位置信息绘制
        // ...
    }
    
    RECT m_rcLastPos;       // 上次布局的矩形
    bool m_bCacheValid;     // 缓存是否有效
    vector<RECT> m_layoutCache;  // 布局缓存
};

4.3 布局调试工具

4.3.1 可视化布局边界

在开发阶段绘制控件边界,帮助调试布局问题:

void CDebugLayoutUI::DoPostPaint(HDC hDC, const RECT& rcPaint) {
    // 绘制控件边界
    HPEN hPen = CreatePen(PS_DASH, 1, RGB(255, 0, 0)); // 红色虚线
    HGDIOBJ hOldPen = SelectObject(hDC, hPen);
    
    RECT rc = m_rcItem;
    rc.left += m_rcInset.left;
    rc.top += m_rcInset.top;
    rc.right -= m_rcInset.right;
    rc.bottom -= m_rcInset.bottom;
    
    Rectangle(hDC, rc.left, rc.top, rc.right, rc.bottom);
    
    // 绘制子控件边界
    for (int i = 0; i < m_items.GetSize(); i++) {
        CControlUI* pItem = static_cast<CControlUI*>(m_items[i]);
        RECT rcItem = pItem->GetPos();
        Rectangle(hDC, rcItem.left, rcItem.top, rcItem.right, rcItem.bottom);
        
        // 绘制子控件索引
        TCHAR szIndex[16];
        _stprintf_s(szIndex, _T("%d"), i);
        SetTextColor(hDC, RGB(255, 0, 0));
        TextOut(hDC, rcItem.left + 2, rcItem.top + 2, szIndex, _tcslen(szIndex));
    }
    
    SelectObject(hDC, hOldPen);
    DeleteObject(hPen);
}

五、综合案例:构建复杂应用界面

5.1 界面规划

设计一个包含侧边栏、工具栏、内容区和状态栏的完整应用界面:

mermaid

5.2 实现代码

5.2.1 XML布局文件
<Window size="1024,768" mininfo="800,600" caption="0,0,0,30">
    <!-- 主布局:垂直排列工具栏、内容区和状态栏 -->
    <VerticalLayout bkcolor="#F5F5F5">
        <!-- 工具栏 -->
        <HorizontalLayout height="30" bkcolor="#E8E8E8" padding="5,0,5,0">
            <Button name="btn_menu" text="菜单" width="60" />
            <Control width="5" /> <!-- 间隔 -->
            <Button name="btn_refresh" text="刷新" width="60" />
            <Control width="5" />
            <Button name="btn_settings" text="设置" width="60" />
            <Control flex="1" /> <!-- 弹性空间 -->
            <Edit name="edit_search" width="200" hinttext="搜索..." />
            <Control width="10" />
            <Button name="btn_user" text="用户" width="60" />
        </HorizontalLayout>
        
        <!-- 主体内容:水平排列侧边栏和主内容区 -->
        <HorizontalLayout flex="1">
            <!-- 侧边栏 -->
            <VerticalLayout width="200" bkcolor="#FFFFFF" border="1,0,0,0" bordercolor="#DDDDDD">
                <Button text="首页" height="40" align="left" padding="20,0,0,0" bkcolor="#F0F0F0" />
                <Button text="数据管理" height="40" align="left" padding="20,0,0,0" />
                <Button text="报表分析" height="40" align="left" padding="20,0,0,0" />
                <Button text="系统设置" height="40" align="left" padding="20,0,0,0" />
                <Control flex="1" />
                <Button text="帮助" height="40" align="left" padding="20,0,0,0" />
                <Button text="关于" height="40" align="left" padding="20,0,0,0" />
            </VerticalLayout>
            
            <!-- 主内容区 -->
            <TabLayout flex="1" name="main_content" cursel="0">
                <!-- 首页 -->
                <VerticalLayout>
                    <HorizontalLayout height="150" padding="10">
                        <VerticalLayout width="25%" bkcolor="#FFFFFF" rounded="4" padding="10">
                            <Label text="总用户数" font="12" />
                            <Label text="12,580" font="24,bold" />
                            <Label text="↑ 12.5% 本月" font="10" textcolor="#008000" />
                        </VerticalLayout>
                        <Control width="10" />
                        <VerticalLayout width="25%" bkcolor="#FFFFFF" rounded="4" padding="10">
                            <Label text="今日活跃" font="12" />
                            <Label text="862" font="24,bold" />
                            <Label text="↑ 3.2% 昨日" font="10" textcolor="#008000" />
                        </VerticalLayout>
                        <Control width="10" />
                        <VerticalLayout width="25%" bkcolor="#FFFFFF" rounded="4" padding="10">
                            <Label text="新增用户" font="12" />
                            <Label text="156" font="24,bold" />
                            <Label text="↓ 1.8% 昨日" font="10" textcolor="#FF0000" />
                        </VerticalLayout>
                        <Control width="10" />
                        <VerticalLayout width="25%" bkcolor="#FFFFFF" rounded="4" padding="10">
                            <Label text="转化率" font="12" />
                            <Label text="28.7%" font="24,bold" />
                            <Label text="↑ 0.5% 昨日" font="10" textcolor="#008000" />
                        </VerticalLayout>
                    </HorizontalLayout>
                    
                    <!-- 数据表格 -->
                    <VerticalLayout padding="10" flex="1">
                        <Label text="用户列表" font="14,bold" />
                        <ListUI name="user_list" padding="5" bkcolor="#FFFFFF" flex="1">
                            <!-- 列表内容将通过代码动态添加 -->
                        </ListUI>
                    </VerticalLayout>
                </VerticalLayout>
                
                <!-- 其他标签页内容 -->
                <VerticalLayout>
                    <Label text="数据展示页面" align="center" valign="center" flex="1" />
                </VerticalLayout>
                <VerticalLayout>
                    <Label text="设置页面" align="center" valign="center" flex="1" />
                </VerticalLayout>
            </TabLayout>
        </HorizontalLayout>
        
        <!-- 状态栏 -->
        <HorizontalLayout height="25" bkcolor="#E8E8E8" padding="10,0,10,0">
            <Label text="当前用户:管理员" />
            <Control flex="1" />
            <Label text="版本:1.0.0" />
            <Control width="10" />
            <Label text="版权所有 © 2025" />
        </HorizontalLayout>
    </VerticalLayout>
</Window>
5.2.2 C++逻辑代码
class CMainFrame : public CWindowWnd, public INotifyUI {
public:
    CMainFrame() : m_pm(this) {}
    
    LPCTSTR GetWindowClassName() const override { return _T("MainFrame"); }
    
    CDuiString GetSkinFile() override { return _T("main_frame.xml"); }
    
    CDuiString GetSkinFolder() override { return _T("skins/"); }
    
    void Notify(TNotifyUI& msg) override {
        if (msg.sType == _T("click")) {
            // 处理按钮点击事件
            if (msg.pSender->GetName() == _T("btn_menu")) {
                // 菜单按钮点击
            } else if (msg.pSender->GetName() == _T("btn_refresh")) {
                // 刷新数据
                RefreshUserData();
            }
        } else if (msg.sType == _T("selectchanged")) {
            // 处理标签页切换
            if (msg.pSender->GetName() == _T("main_content")) {
                int iIndex = static_cast<CTabLayoutUI*>(msg.pSender)->GetCurSel();
                UpdateTitle(iIndex);
            }
        }
    }
    
    void InitWindow() override {
        // 初始化列表
        m_pUserList = static_cast<CListUI*>(m_pm.FindControl(_T("user_list")));
        if (m_pUserList) {
            // 设置列表项高度
            m_pUserList->SetItemHeight(40);
            // 启用虚拟列表
            m_pUserList->SetTotalCount(500);  // 总数据量
            m_pUserList->SetDataProvider(this);  // 设置数据提供者
        }
        
        // 初始化标签页
        m_pTabLayout = static_cast<CTabLayoutUI*>(m_pm.FindControl(_T("main_content")));
    }
    
    // 实现数据提供者接口
    void FillItemData(CControlUI* pItem, int iIndex) {
        // 填充列表项数据
        TCHAR szText[64];
        _stprintf_s(szText, _T("用户 %05d"), iIndex+1);
        static_cast<CListContainerElementUI*>(pItem)->SetText(szText);
        
        // 设置不同行的背景色
        if (iIndex % 2 == 0) {
            pItem->SetBkColor(0xFFFFFFFF);
        } else {
            pItem->SetBkColor(0xFFF5F5F5);
        }
    }
    
    void RefreshUserData() {
        // 刷新用户数据
        if (m_pUserList) {
            m_pUserList->Invalidate();
        }
        
        // 显示刷新提示
        CLabelUI* pTip = new CLabelUI();
        pTip->SetText(_T("数据已刷新"));
        pTip->SetBkColor(0xAA008000);
        pTip->SetTextColor(0xFFFFFFFF);
        pTip->SetPos(RECT{800, 30, 900, 50});
        pTip->SetVisible(true);
        m_pm.AttachDialog(pTip);
        
        // 3秒后隐藏提示
        m_pm.AddTimer(this, 1000, 3000);
    }
    
    void OnTimer(UINT_PTR idEvent) override {
        if (idEvent == 1000) {
            // 隐藏提示
            CControlUI* pTip = m_pm.FindControl(_T("refresh_tip"));
            if (pTip) {
                m_pm.DetachDialog(pTip);
                delete pTip;
            }
            m_pm.KillTimer(this, 1000);
        }
    }
    
    void UpdateTitle(int iIndex) {
        // 更新窗口标题
        CDuiString sTitle;
        switch (iIndex) {
            case 0: sTitle = _T("首页 - 数据管理系统"); break;
            case 1: sTitle = _T("数据展示 - 数据管理系统"); break;
            case 2: sTitle = _T("设置 - 数据管理系统"); break;
            default: sTitle = _T("数据管理系统");
        }
        SetWindowText(m_hWnd, sTitle);
    }
    
    LRESULT HandleMessage(UINT uMsg, WPARAM wParam, LPARAM lParam) override {
        LRESULT lRes = 0;
        if (uMsg == WM_CREATE) {
            m_pm.Init(m_hWnd);
            CControlUI* pRoot = m_pm.CreateControl(_T("Window"));
            m_pm.AttachDialog(pRoot);
            m_pm.AddNotifier(this);
            InitWindow();
            return lRes;
        } else if (uMsg == WM_DESTROY) {
            PostQuitMessage(0);
        }
        
        if (m_pm.MessageHandler(uMsg, wParam, lParam, lRes)) {
            return lRes;
        }
        return CWindowWnd::HandleMessage(uMsg, wParam, lParam);
    }
    
private:
    CPaintManagerUI m_pm;
    CListUI* m_pUserList = nullptr;
    CTabLayoutUI* m_pTabLayout = nullptr;
};

int APIENTRY _tWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPTSTR lpCmdLine, int nCmdShow) {
    // 初始化DuiLib
    CPaintManagerUI::SetInstance(hInstance);
    
    // 注册自定义控件
    RegisterFlowLayout();
    
    // 创建主窗口
    CMainFrame frame;
    frame.Create(nullptr, _T("数据管理系统"), UI_WNDSTYLE_FRAME, WS_EX_WINDOWEDGE);
    frame.CenterWindow();
    frame.ShowWindow(nCmdShow);
    
    // 消息循环
    MSG msg;
    while (GetMessage(&msg, nullptr, 0, 0)) {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }
    
    return 0;
}

六、总结与进阶

6.1 关键知识点回顾

  1. 布局核心类:掌握CContainerUI派生类的使用场景与方法
  2. 自定义布局:继承CContainerUI并重写SetPos实现自定义排列逻辑
  3. 性能优化:使用虚拟列表、布局缓存减少计算量
  4. 响应式设计:根据容器尺寸动态调整布局结构
  5. 调试技巧:可视化边界、打印布局信息定位问题

6.2 进阶学习路径

  1. 深入源码:研究Duilib布局管理器的实现细节
  2. 自定义控件:结合自定义布局开发复杂控件
  3. 动画效果:实现布局切换时的过渡动画
  4. 主题系统:设计可切换的皮肤系统
  5. 跨平台适配:将Duilib应用移植到其他平台

6.3 实用资源

  • 官方文档:Duilib自带的入门文档和示例
  • 开源社区:Duilib开发者社区和论坛
  • 第三方库:Duilib扩展控件库和工具集
  • 调试工具:Duilib布局分析器和性能探查器

6.4 下期预告

下一篇文章将介绍《Duilib动画系统开发:从基础到高级特效》,将深入讲解如何为自定义布局添加平滑过渡动画,敬请期待!

如果本文对你有帮助,请点赞、收藏并关注作者,获取更多Duilib开发技巧和实战经验。

【免费下载链接】duilib 【免费下载链接】duilib 项目地址: https://gitcode.com/gh_mirrors/du/duilib

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值