duilib的list控件的键盘上下键激活并获取子项名失败的bug

本文介绍Duilib中CListUI类键盘事件处理的优化方案,解决了使用键盘上下键选择列表项时出现的问题。通过调整DoEvent方法内对UIEVENT_KEYDOWN事件的处理流程,确保了节点值获取的准确性。

这个主要是解决在属性列表中,按键盘的上下键获取节点的值出错的问题

duilib的源码

void CListUI::DoEvent(TEventUI& event)
{
    if( !IsMouseEnabled() && event.Type > UIEVENT__MOUSEBEGIN && event.Type < UIEVENT__MOUSEEND ) {
        if( m_pParent != NULL ) m_pParent->DoEvent(event);
        else CVerticalLayoutUI::DoEvent(event);
        return;
    }

    if( event.Type == UIEVENT_SETFOCUS ) 
    {
        m_bFocused = true;
        return;
    }
    if( event.Type == UIEVENT_KILLFOCUS ) 
    {
        m_bFocused = false;
        return;
    }

    if( event.Type == UIEVENT_KEYDOWN )
    {
        if (IsKeyboardEnabled() && IsEnabled()) {
            switch( event.chKey ) {
            case VK_UP:
                SelectItem(FindSelectable(m_iCurSel - 1, false), true);
            case VK_DOWN:
                SelectItem(FindSelectable(m_iCurSel + 1, true), true);
            case VK_PRIOR:
                PageUp();
            case VK_NEXT:
                PageDown();
            case VK_HOME:
                SelectItem(FindSelectable(0, false), true);
            case VK_END:
                SelectItem(FindSelectable(GetCount() - 1, true), true);
            case VK_RETURN:
                if( m_iCurSel != -1 ) GetItemAt(m_iCurSel)->Activate();
            }
            return;
        }
    }

    if( event.Type == UIEVENT_SCROLLWHEEL )
    {
        if (IsEnabled()) {
            switch( LOWORD(event.wParam) ) {
            case SB_LINEUP:
                if( m_bScrollSelect ) SelectItem(FindSelectable(m_iCurSel - 1, false), true);
                else LineUp();
                return;
            case SB_LINEDOWN:
                if( m_bScrollSelect ) SelectItem(FindSelectable(m_iCurSel + 1, true), true);
                else LineDown();
                return;
            }
        }
    }

    CVerticalLayoutUI::DoEvent(event);
}

改进之后

void CListUI::DoEvent(TEventUI& event)
{
    if( !IsMouseEnabled() && event.Type > UIEVENT__MOUSEBEGIN && event.Type < UIEVENT__MOUSEEND ) {
        if( m_pParent != NULL ) m_pParent->DoEvent(event);
        else CVerticalLayoutUI::DoEvent(event);
        return;
    }

    if( event.Type == UIEVENT_SETFOCUS ) 
    {
        m_bFocused = true;
        return;
    }
    if( event.Type == UIEVENT_KILLFOCUS ) 
    {
        m_bFocused = false;
        return;
    }

    if( event.Type == UIEVENT_KEYDOWN )
    {
        if (IsKeyboardEnabled() && IsEnabled()) {
            *switch( event.chKey ) {
            case VK_UP:
                SelectItem(FindSelectable(m_iCurSel - 1, false), true);
                if (m_iCurSel != -1) GetItemAt(m_iCurSel)->Activate();
                break;
            case VK_DOWN:
                SelectItem(FindSelectable(m_iCurSel + 1, true), true);
                if (m_iCurSel != -1) GetItemAt(m_iCurSel)->Activate();
                break;
            case VK_PRIOR:
                PageUp();
                if (m_iCurSel != -1) GetItemAt(m_iCurSel)->Activate();
                break;
            case VK_NEXT:
                PageDown();
                if (m_iCurSel != -1) GetItemAt(m_iCurSel)->Activate();
                break;
            case VK_HOME:
                SelectItem(FindSelectable(0, false), true);
                if (m_iCurSel != -1) GetItemAt(m_iCurSel)->Activate();
                break;
            case VK_END:
                SelectItem(FindSelectable(GetCount() - 1, true), true);
                if (m_iCurSel != -1) GetItemAt(m_iCurSel)->Activate();
                break;
            case VK_RETURN:
                if( m_iCurSel != -1 ) GetItemAt(m_iCurSel)->Activate();
                break;*
            }
            return;
        }
    }

    if( event.Type == UIEVENT_SCROLLWHEEL )
    {
        if (IsEnabled()) {
            switch( LOWORD(event.wParam) ) {
            case SB_LINEUP:
                if( m_bScrollSelect ) SelectItem(FindSelectable(m_iCurSel - 1, false), true);
                else LineUp();
                return;
            case SB_LINEDOWN:
                if( m_bScrollSelect ) SelectItem(FindSelectable(m_iCurSel + 1, true), true);
                else LineDown();
                return;
            }
        }
    }

    CVerticalLayoutUI::DoEvent(event);
}
<think>我们正在处理一个DuiLib项目中的内存泄漏问题,特别是使用DuiLib_Ultimate库时引入头文件或自定义类后出现的泄漏。根据之前的讨论,我们已经集成了VLD工具,现在需要快速定位泄漏点。 步骤: 1. 确认VLD已正确集成:确保在Debug模式下包含vld.h链接vld.lib(如引用[3]所示,在stdafx.h中配置)。 2. 运行程序,观察VLD输出的泄漏报告。 3. 分析报告,重点关注泄漏点是否在自定义类中或DuiLib内部。 由于用户提到在引入DuiLib/UIlib.h或添加自定义类后出现泄漏,我们可以采取以下策略: 策略一:隔离自定义代码 - 注释掉所有自定义类相关的代码,运行程序,观察泄漏是否消失。 - 如果消失,则逐个取消注释,每次运行观察VLD报告,直到找到引入泄漏的代码块。 策略二:分析VLD报告 - 查看泄漏报告中分配内存的调用堆栈,注意堆栈中是否包含自定义类或DuiLib控件的创建代码。 - 例如,如果泄漏报告中出现类似下面的堆栈: TestApp.exe!MyCustomClass::Create (myclass.cpp:20) TestApp.exe!CMainFrame::OnCreate (mainframe.cpp:30) 则说明在CMainFrame::OnCreate中创建的MyCustomClass对象没有释放。 策略三:检查DuiLib控件的释放 - DuiLib中的控件通常使用delete释放,但要注意控件是否被添加到窗口上(窗口关闭时会自动释放子控件)。如果创建了控件但没有添加到窗口上,则需要手动delete。 - 特别注意定时器中的操作:如引用[1]中提到的定时清除List控件子项重新添加,如果每次清除时没有正确释放子项,则会导致内存泄漏(以及GDI泄漏)。 策略四:检查资源释放 - DuiLib中的资源(如图片、字体等)需要手动释放,例如在程序退出时调用资源管理器的释放函数(如引用[1]中提到的CResourceManager::ReleaseAll)。 - 自定义类中如果使用了DuiLib资源,也要确保在析构时释放。 策略五:检查跨DLL边界释放问题 - 如果自定义类在DLL中分配内存,而在EXE中释放,可能会因为不同的堆导致问题。应该使用一致的分配和释放方式(如DuiLib提供的Delete接口)。 具体操作步骤: 1. 在stdafx.h中配置VLD(如之前所述)。 2. 在程序入口启用VLD,在程序退出时报告泄漏(如之前所述)。 3. 运行程序,执行可能导致泄漏的操作(如反复操作List控件)。 4. 关闭程序,查看VLD报告。 示例:定位List控件子项泄漏 - 问题描述:每次清除List控件子项时,没有正确释放子项控件,导致内存和GDI句柄泄漏。 - 修复方法:在清除子项时,必须遍历删除每个子项控件DuiLib中,可以使用`CContainerUI::Remove`或`CContainerUI::RemoveAll`,但注意这些函数只是从容器中移除控件不会释放控件对象。因此,需要先获取子项,然后删除。 错误示例: ```cpp m_pList->RemoveAll(); // 只是移除,没有释放内存 ``` 正确做法: ```cpp for (int i = 0; i < m_pList->GetCount(); i++) { CControlUI* pItem = m_pList->GetItemAt(i); if (pItem) { delete pItem; } } m_pList->RemoveAll(); // 或者直接使用RemoveAll(true)来同时删除子项 ``` 注意:DuiLib的`RemoveAll`函数有一个参数,设置为true可以自动删除子项: ```cpp m_pList->RemoveAll(true); // 删除所有子项释放内存 ``` 5. 如果VLD报告指向DuiLib内部(如DuiLib_Ultimate.dll中的分配),则考虑是否是DuiLib本身的泄漏。可以尝试更新到最新版本(如引用[2]提到的PC_UIKit,它修复了若干Bug)。 6. 对于自定义类,确保遵循谁创建谁释放的原则。如果自定义类继承自DuiLib控件类,则通常使用delete释放。 7. 如果泄漏报告中有ucrtbased.dll相关的分配(如malloc),但调用堆栈指向自定义代码,则说明是自定义代码中分配的内存没有释放。 高级技巧:使用VLD的标记功能 - 在开始执行一段可能泄漏的代码前,调用`VLDMarkAllLeaksAsReported()`标记当前状态。 - 执行完这段代码后,调用`VLDReportLeaks()`报告新产生的泄漏。 示例: ```cpp void TestListLeak() { VLDMarkAllLeaksAsReported(); // 忽略之前的泄漏 // 执行测试:添加1000个列表项 for (int i = 0; i < 1000; i++) { CListContainerElementUI* pElement = new CListContainerElementUI; // ... 设置元素属性 m_pList->Add(pElement); } // 清除列表(不释放内存) m_pList->RemoveAll(false); // 这里应该报告1000个泄漏 VLDReportLeaks(); // 报告从Mark之后新产生的泄漏 } ``` 通过这种方法,可以快速定位到具体哪段代码产生了泄漏。 最后,如果问题依然存在,建议检查DuiLib的版本,参考开源社区(如PC_UIKit)的修复,或者尝试使用最新的DuiLib_Ultimate版本。 注意:GDI泄漏(如引用[1]中提到的GDI句柄数一直增加)通常是由于没有删除GDI对象(如HBITMAP, HBRUSH等)造成的。在DuiLib中,如果使用了自定义绘制,确保在控件销毁时释放GDI对象。 例如,在自定义控件中: ```cpp class MyControl : public CControlUI { public: MyControl() { m_hBmp = ::LoadBitmap(hInstance, _T("test.bmp")); } ~MyControl() { if (m_hBmp) ::DeleteObject(m_hBmp); } private: HBITMAP m_hBmp; }; ``` 如果没有在析构函数中释放,则每次创建销毁该控件都会泄漏一个位图句柄。 总结步骤: 1. 配置VLD。 2. 运行程序,复现问题。 3. 分析VLD报告,定位泄漏点。 4. 检查相关代码,确保分配的资源被正确释放。 5. 使用VLD的标记功能隔离泄漏代码段。 6. 关注GDI泄漏,确保GDI对象被删除。 如果按照上述步骤,应该能够快速定位解决泄漏问题。</think>### 在 DuiLib_Ultimate 库中快速定位内存泄漏的方法 #### 一、核心排查步骤(基于 VLD 报告分析) 1. **隔离泄漏场景** ```cpp // 在测试函数前后添加标记(stdafx.h 已配置 VLD) void TestLeakScenario() { VLDMarkAllLeaksAsReported(); // 标记当前状态 // 引入可疑代码(如自定义类实例化) MyCustomClass* pObj = new MyCustomClass(); // 测试点 pObj->Init(); // 故意不释放:模拟泄漏 VLDReportLeaks(); // 仅报告新产生的泄漏[^1] } ``` 2. **解读 VLD 报告关信息** 当报告出现: ``` Leak: 48 bytes at 0x00C1A2F0 Call Stack: ucrtbased.dll!malloc() Duilib_Ultimate.dll!CDuiString::Alloc() (uistring.cpp:120) TestApp.exe!MyCustomClass::Init() (myclass.cpp:45) <-- 自定义类泄漏点 ``` 重点检查: - 最后出现的自定义文件(如 `myclass.cpp:45`) - DuiLib 内部调用栈(如 `uistring.cpp`) - 泄漏大小是否与自定义类成员匹配 3. **DuiLib 控件泄漏专项检测** ```cpp // 检查控件释放(引用[1]的 List 控件案例) void TestListLeak() { CListUI* pList = new CListUI; pList->RemoveAll(); // 清除子项 // 模拟重新添加 for(int i=0; i<100; i++) { CListContainerElementUI* pItem = new CListContainerElementUI; pList->Add(pItem); // 可能泄漏点 } // 必须显式删除(DuiLib 不会自动释放) delete pList; } ``` #### 二、自定义类泄漏排查技巧 1. **资源释放验证模板** ```cpp class MyCustomClass : public CControlUI { public: MyCustomClass() { m_pBitmap = new CDuiBitmap; // 可疑点 m_hFont = ::CreateFont(/*...*/); // GDI 泄漏源[^1] } ~MyCustomClass() { delete m_pBitmap; // 必须实现 ::DeleteObject(m_hFont); // 易遗漏点 } private: CDuiBitmap* m_pBitmap; HFONT m_hFont; }; ``` 2. **跨模块泄漏检测** 当泄漏报告显示 `ucrtbased.dll` 时: ```cpp // 正确做法:使用 DuiLib 的删除接口 void ReleaseControl(CControlUI* pCtrl) { if(pCtrl) { // delete pCtrl; // 错误!跨模块边界 pCtrl->Delete(); // 正确方式[^1] } } ``` #### 三、DuiLib 特定场景泄漏定位 1. **静态资源泄漏**(引用[4]的字体特效) ```cpp // 检查全局资源管理器 void AppExit() { // 必须手动释放(常遗漏) CResourceManager::GetInstance()->ReleaseAll(); TFontInfo::Free(); // 字体特效资源[^4] } ``` 2. **DPI 适配泄漏**(PC_UIKit 新增功能) ```cpp // 检查高DPI缩放资源 void OnDpiChanged() { CDuiBitmap* pNewBmp = LoadScaledBitmap(); // 新分配 if(m_pOldBmp) delete m_pOldBmp; // 必须释放旧资源 m_pOldBmp = pNewBmp; } ``` #### 四、高效调试技巧 1. **泄漏点标记法** ```cpp void SuspectedFunction() { VLDEnable(); VLDSnapshotState(); // 记录初始状态 // 可疑操作... CreateCustomControls(); // 对比快照 VLDReportLeaks(); } ``` 2. **VLD 配置文件优化** 创建 `vld.ini`: ```ini [Options] ReportTo = debugger // 输出到VS调试器 TrackStackWalk = Fast // 加速检测 SkipDuilibRelease = true // 忽略Release版误报 ``` > **定位关**:当泄漏出现在 `DuiLib/UIlib.h` 包含后,重点检查: > 1. 继承 `CControlUI` 的自定义类析构函数 > 2. 使用 `new` 创建的控件是否调用 `->Delete()` > 3. 全局静态对象(如 `CResourceManager`)[^2] > 4. GDI 对象(`HFONT`/`HBITMAP`)的手动释放[^1]
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值