Duilib自定义布局开发:实现复杂界面的高级技巧
【免费下载链接】duilib 项目地址: https://gitcode.com/gh_mirrors/du/duilib
引言:告别界面布局困境
你是否还在为Duilib默认布局无法满足复杂界面需求而烦恼?是否经历过因布局嵌套过深导致的性能问题?本文将系统讲解Duilib布局系统的底层原理与高级应用技巧,通过12个实战案例带你掌握自定义布局开发的核心方法,最终能够构建出响应式强、性能优异的复杂桌面界面。
读完本文你将获得:
- 精通Duilib五大核心布局管理器的实现原理
- 掌握自定义布局的完整开发流程与调试技巧
- 学会性能优化的7个实用策略
- 获取10+可直接复用的布局组件代码模板
一、Duilib布局系统架构解析
1.1 布局管理器核心类图
1.2 布局渲染流程
二、核心布局管理器深度剖析
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 与水平布局的核心差异
垂直布局与水平布局共享相似的设计理念,但在以下方面有所不同:
| 特性 | 水平布局 | 垂直布局 |
|---|---|---|
| 调整方向 | 左右拖动分隔条 | 上下拖动分隔条 |
| 关键属性 | sepwidth | sepheight |
| 尺寸计算 | 宽度分配 | 高度分配 |
| 滚动方向 | 水平滚动 | 垂直滚动 |
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 开发步骤
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 避免过度嵌套
4.1.2 嵌套布局性能对比
| 嵌套层级 | 控件数量 | 重绘时间(ms) | CPU占用率 | 适用场景 |
|---|---|---|---|---|
| 1-2层 | <50个 | 5-10ms | <5% | 简单界面,如对话框 |
| 3-4层 | 50-200个 | 10-30ms | 5-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 界面规划
设计一个包含侧边栏、工具栏、内容区和状态栏的完整应用界面:
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 关键知识点回顾
- 布局核心类:掌握CContainerUI派生类的使用场景与方法
- 自定义布局:继承CContainerUI并重写SetPos实现自定义排列逻辑
- 性能优化:使用虚拟列表、布局缓存减少计算量
- 响应式设计:根据容器尺寸动态调整布局结构
- 调试技巧:可视化边界、打印布局信息定位问题
6.2 进阶学习路径
- 深入源码:研究Duilib布局管理器的实现细节
- 自定义控件:结合自定义布局开发复杂控件
- 动画效果:实现布局切换时的过渡动画
- 主题系统:设计可切换的皮肤系统
- 跨平台适配:将Duilib应用移植到其他平台
6.3 实用资源
- 官方文档:Duilib自带的入门文档和示例
- 开源社区:Duilib开发者社区和论坛
- 第三方库:Duilib扩展控件库和工具集
- 调试工具:Duilib布局分析器和性能探查器
6.4 下期预告
下一篇文章将介绍《Duilib动画系统开发:从基础到高级特效》,将深入讲解如何为自定义布局添加平滑过渡动画,敬请期待!
如果本文对你有帮助,请点赞、收藏并关注作者,获取更多Duilib开发技巧和实战经验。
【免费下载链接】duilib 项目地址: https://gitcode.com/gh_mirrors/du/duilib
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考



