You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

MFC自绘CListBox嵌入CLinkCtrl滚动错位及显示异常问题

MFC自绘CListBox链接控件错位及显示问题排查与解决

我在C++ MFC环境中实现了一个自绘CListBox,每个列表项包含自绘文本和CLinkCtrl控件。当前存在两个问题:

  • 上下滚动时CLinkCtrl控件显示错位,但自绘文本始终正常
  • 底部的一个列表项几乎总是无法显示链接控件,选中该控件时UI偶尔会更新为正确状态

相关配置与代码

1. ListBox配置

已设置为自绘模式

2. 填充ListBox代码(已取消线程,确保主线程更新UI)

//std::thread t([=]() {
  //myMutex.lock();  // 获取互斥锁

  // 禁用控件重绘...
  calListBox.EnableWindow(FALSE);
  calListBox.SetRedraw(FALSE);

  // 清空所有项
  for (int i = 0; i < calListBox.GetCount(); i++)
  {
    LPARAM lpData = calListBox.GetItemData(i);
    CalListBoxItem* item = reinterpret_cast<CalListBoxItem*>(lpData);
    delete item;
  }
  calListBox.ResetContent();

  // 循环添加项
    int index = calListBox.SendMessage(LB_ADDSTRING, 0, 0);
    if (index == LB_ERR) {
        DWORD dwError = GetLastError();
    }
    else {
        CalListBoxItem* pItem = new CalListBoxItem(description, htmllink, index);
        calListBox.SendMessage(LB_SETITEMDATA, index, reinterpret_cast<DWORD_PTR>(pItem));
        calListBox.SendMessage(LB_SETITEMHEIGHT, index, 100);
    }
  // 循环结束

    calListBox.SetRedraw(TRUE); // 重新启用重绘
    calListBox.EnableWindow(TRUE);      

    //myMutex.unlock();  // 释放互斥锁
//});
//t.detach();

CalListBoxItem定义

// CalListBoxItem用于存储描述、索引和链接控件
public:
    ~CalListBoxItem();
    CalListBoxItem(const std::string& desc, const std::string& link, int index)
        : m_desc(desc), m_link(link), originalIndex(index)
    {
        linkCtrl = new CCustLinkCtrl();
    }

public:
    int originalIndex;
    CCustLinkCtrl* linkCtrl;

    std::string m_desc;
    std::string m_link;

3. 自定义CalListBox自绘逻辑(关键部分)

AddLinkControl函数

// 添加链接控件函数
void CalListBox::AddLinkControl(CLinkCtrl* linkCtrl, CRect& rect, int nItemIndex, CString text, CString htmlLink) {

    if (!linkCtrl) {
        return;
    }

    if (::IsWindow(linkCtrl->m_hWnd)) { 
        CString strIndex;
        strIndex.Format(_T(" u:%d"), nItemIndex);  // 将索引转为字符串
        CString strText = text + strIndex;      // 拼接文本和索引

        linkCtrl->SetWindowTextW(strText);
        linkCtrl->Invalidate();
        linkCtrl->UpdateWindow();
        return; // 控件已创建,直接更新
    }

    // 创建列表框的子控件
    linkCtrl->Create(_T(""), WS_CHILD | WS_VISIBLE | DT_VCENTER | SS_NOTIFY, rect, this, IDC_LINK_CTRL_LIST_ITEM + nItemIndex);

    CString strIndex;
    strIndex.Format(_T(" %d"), nItemIndex);  // 将索引转为字符串
    CString strText = text + strIndex;      // 拼接文本和索引

    linkCtrl->SetWindowTextW(strText);

    linkCtrl->SetItemUrl(0, htmlLink); // 必须在设置文本后调用,否则链接无效

    linkCtrl->SetFont(linkFont);
    linkCtrl->Invalidate();
    linkCtrl->UpdateWindow();
}

DrawItem函数(部分代码)

void CalListBox::DrawItem(LPDRAWITEMSTRUCT lpDrawItemStruct)
{
    if (lpDrawItemStruct->itemData == 0) {
        return;
    }

    CalListBoxItem* pItem = reinterpret_cast<CalListBoxItem*>(lpDrawItemStruct->itemData);
    if (pItem == nullptr) {
        return;
    }

    CDC* pDC = CDC::FromHandle(lpDrawItemStruct->hDC);
    if (pDC == nullptr) {
        return;
    }

    CRect rect(lpDrawItemStruct->rcItem);

    // 省略其他绘制逻辑...

    AddLinkControl(pItem->linkCtrl, leftRect2, pItem->originalIndex, L"<a>Link</a>", leftText2);
}

问题分析与解决方案

问题1:滚动时链接控件错位

原因:自绘ListBox滚动时,可视区域内的项会被重新绘制,但AddLinkControl中仅更新控件文本,未同步更新控件位置。控件初始位置是第一次绘制时的坐标,滚动后项位置变化,控件位置未跟随调整,导致错位。

解决:在AddLinkControl的控件已存在分支中,添加位置更新代码:

if (::IsWindow(linkCtrl->m_hWnd)) { 
    // 新增:同步控件位置
    linkCtrl->MoveWindow(rect);
    // 原有文本更新逻辑
    CString strIndex;
    strIndex.Format(_T(" u:%d"), nItemIndex);
    CString strText = text + strIndex;
    linkCtrl->SetWindowTextW(strText);
    linkCtrl->Invalidate();
    linkCtrl->UpdateWindow();
    return;
}

问题2:底部项链接控件不显示,选中时偶尔恢复

原因:

  1. ListBox默认仅绘制可视区域内的项,底部项初始未进入可视区时DrawItem未触发,导致控件未创建;
  2. 控件创建时可见性未强制设置,选中项触发重绘才重新处理。

解决:

  1. 在AddLinkControl中,无论控件是否已创建,都强制设置可见性:
// 控件创建完成或更新后添加
linkCtrl->ShowWindow(SW_SHOW);
  1. 处理ListBox滚动消息,强制同步可视项的控件位置:
void CalListBox::OnVScroll(UINT nSBCode, UINT nPos, CScrollBar* pScrollBar)
{
    CListBox::OnVScroll(nSBCode, nPos, pScrollBar);
    // 刷新所有可视项的链接控件位置
    int top = GetTopIndex();
    int count = GetCount();
    int visibleCount = GetVisibleCount();
    for (int i = top; i < min(top + visibleCount, count); i++)
    {
        CalListBoxItem* pItem = reinterpret_cast<CalListBoxItem*>(GetItemData(i));
        if (pItem && pItem->linkCtrl && ::IsWindow(pItem->linkCtrl->m_hWnd))
        {
            CRect itemRect;
            GetItemRect(i, &itemRect);
            // 根据自身布局调整链接控件的相对位置,示例为偏移调整
            CRect linkRect = itemRect;
            linkRect.DeflateRect(10, 50, 10, 10); 
            pItem->linkCtrl->MoveWindow(linkRect);
        }
    }
}

额外注意事项

  • 清理内存:在清空ListBox项时,需销毁链接控件避免泄漏:
for (int i = 0; i < calListBox.GetCount(); i++)
{
    LPARAM lpData = calListBox.GetItemData(i);
    CalListBoxItem* item = reinterpret_cast<CalListBoxItem*>(lpData);
    if (item->linkCtrl && ::IsWindow(item->linkCtrl->m_hWnd))
    {
        item->linkCtrl->DestroyWindow();
        delete item->linkCtrl;
    }
    delete item;
}
  • 避免在DrawItem中重复创建控件,确保每个项的控件仅初始化一次。

内容的提问来源于stack exchange,提问作者Wayne

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.22 17:59:57