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

CFormView中CMFCTabCtrl重绘异常:窗口移动/调整后按钮及标签内容不刷新

问题

在CFormView中使用CMFCTabCtrl时遇到重绘异常:窗口移动或调整大小后,标签控件的按钮及标签内容无法正常重绘。以下是相关代码片段,已找到临时修复方案,但不清楚问题根源,希望得到更优解法。

框架窗口中视图的创建代码

BOOL CustomFormulasFrame::OnCreateClient(LPCREATESTRUCT lpcs, CCreateContext * pContext)
{
    pContext->m_pNewViewClass = RUNTIME_CLASS(CustomFormulasView);

    m_pCustomFormulasView = new CustomFormulasView(
        IDD_CUSTOM_FORMULAS_VIEW,
        m_pCustomFormulasFrameController->GetCustomFormulasViewController());
    if (!m_pCustomFormulasView
             ->Create(nullptr, nullptr, WS_VISIBLE | WS_CHILD, CRect(0, 0, 500, 500), this, AFX_IDW_PANE_FIRST, pContext))
    {
        TRACE(traceAppMsg, 0, "Warning: Could not create custom formulas view.\n");
        return false;
    }

    SetActiveView(m_pCustomFormulasView);
    return true;
}

视图构造函数(CCswFormView非问题根源,已尝试纯CFormView)

CustomFormulasView::CustomFormulasView(
    UINT nIDTemplate,
    const std::shared_ptr<ICustomFormulasViewController> & pCustomFormulasViewController)
    : CCswFormView(nIDTemplate),
      m_pController(pCustomFormulasViewController)
{
    ASSERT(pCustomFormulasViewController);
}

替换目标区域、添加动态布局及标签部件的代码

BOOL CustomFormulasView::Create(
    LPCTSTR lpszClassName,
    LPCTSTR lpszWindowName,
    DWORD dwStyle,
    const RECT & rect,
    CWnd * pParentWnd,
    UINT nID,
    CCreateContext * pContext)
{
    if (!__super::Create(lpszClassName, lpszWindowName, dwStyle, rect, pParentWnd, nID, pContext))
        return FALSE;

    // Create good tab control on tab control target
    auto pTabControlTarget = GetDlgItem(IDC_CUSTOM_FORMULAS_TABLE_TABS);
    CHECKRF(pTabControlTarget);
    pTabControlTarget->ShowWindow(SW_HIDE);
    pTabControlTarget->SetDlgCtrlID(0);
    CRect tabControlRect;
    pTabControlTarget->GetWindowRect(&tabControlRect);
    ScreenToClient(tabControlRect);
    m_tabControl.Create(nullptr, nullptr, WS_CHILD | WS_VISIBLE, tabControlRect, this, 0);
    m_tabControl.SetLocation(CCswMFCTabCtrl::LOCATION_TOP);
    m_tabControl.SetDlgCtrlID(IDC_CUSTOM_FORMULAS_TABLE_TABS);
    auto pDynLayout = GetDynamicLayout();
    pDynLayout->AddItem(m_tabControl, CMFCDynamicLayout::MoveNone(), CMFCDynamicLayout::SizeHorizontalAndVertical(100, 100));

    // Add tabs
    m_pResultTableTabWindow = CreateCustomFormulasListWidget(ECustomFormulaTable::Results, &m_tabControl);
    m_pSummaryTableTabWindow = CreateCustomFormulasListWidget(ECustomFormulaTable::Summary, &m_tabControl);

    m_tabControl.AddTab(m_pResultTableTabWindow.get(), IDS_CUSTOM_FORMULA_TARGET_RESULT_TABLE, 0, false);
    m_tabControl.AddTab(m_pSummaryTableTabWindow.get(), IDS_CUSTOM_FORMULA_TARGET_SUMMARY_TABLE, 0, false);

    return TRUE;
}

std::shared_ptr<CustomFormulasListWidget> CustomFormulasView::CreateCustomFormulasListWidget(
    ECustomFormulaTable eCustomFormulaTable,
    CWnd * pParentWnd)
{
    auto tabControllers = m_pController->GetTabControllersForTab(eCustomFormulaTable);
    CHECKRX(tabControllers.m_pListWidgetController != nullptr, {});
    auto pListWidget = std::make_shared<CustomFormulasListWidget>(tabControllers.m_pListWidgetController, pParentWnd);
    auto pRedrawble = std::dynamic_pointer_cast<IRedrawableController>(tabControllers.m_pListWidgetController);
    CHECKRX(pRedrawble != nullptr, {});
    pRedrawble->SetRedrawable(pListWidget);
    pListWidget->Create(CustomFormulasListWidget::IDD, pParentWnd);

    return pListWidget;
}

注:CustomFormulasListWidget派生自CDialog,尝试改为CFormView后问题仍存在。

临时修复代码

void CustomFormulasView::OnPaint()
{
    __super::OnPaint();
    // CFormView with combination of CMFCTabCtrl doesn't redraw inside of the tab after window move or resize
    auto pActiveWindow = m_tabControl.GetActiveWnd();
    if (pActiveWindow)
        pActiveWindow->RedrawWindow();
}

void CCswMFCTabCtrl::OnPaint()
{
    __super::OnPaint();
    // For some reason drop-down button is not repainted when using this control in CFormView after view window is
    // resized or moved. This fixes it.
    m_btnScrollRight.RedrawWindow();

   // Similarly redraw other buttons from CMFCTabCtrl implementation, we use the layout with top-right drop-down button
}

请问是否有更优的解决方案?我的实现是否存在错误?


优化解决方案与问题根源分析

问题根源

  1. CFormView的重绘消息传递异常:CFormView基于对话框实现,其WM_PAINT消息处理逻辑可能拦截或延迟子控件的重绘请求,导致CMFCTabCtrl及其内部子窗口(标签内容、滚动按钮)无法收到完整的重绘通知。
  2. 动态布局与控件创建时机不匹配:在Create函数中直接创建CMFCTabCtrl并绑定动态布局,可能因CFormView初始化流程未完成,导致控件的大小调整通知未正确传递,窗口移动/resize时控件未触发自身重绘。
  3. CMFCTabCtrl内部控件的消息响应缺失:CMFCTabCtrl的滚动按钮等子控件依赖父控件传递的WM_SIZE、WM_MOVE消息触发重绘,若CFormView的消息传递链存在中断,这些控件会遗漏重绘信号。

优化解决方案

方案1:修正控件创建与布局时机

将CMFCTabCtrl的创建逻辑移至OnInitialUpdate而非Create函数中,确保CFormView初始化完成后再创建子控件:

void CustomFormulasView::OnInitialUpdate()
{
    __super::OnInitialUpdate();

    // 原Create函数中的CMFCTabCtrl创建、布局绑定、标签添加逻辑全部移到这里
    auto pTabControlTarget = GetDlgItem(IDC_CUSTOM_FORMULAS_TABLE_TABS);
    CHECKRF(pTabControlTarget);
    pTabControlTarget->ShowWindow(SW_HIDE);
    pTabControlTarget->DestroyWindow(); // 彻底移除原控件,避免消息冲突
    CRect tabControlRect;
    GetDlgItem(IDC_CUSTOM_FORMULAS_TABLE_TABS)->GetWindowRect(&tabControlRect);
    ScreenToClient(tabControlRect);
    m_tabControl.Create(nullptr, nullptr, WS_CHILD | WS_VISIBLE, tabControlRect, this, 0);
    m_tabControl.SetLocation(CCswMFCTabCtrl::LOCATION_TOP);
    m_tabControl.SetDlgCtrlID(IDC_CUSTOM_FORMULAS_TABLE_TABS);
    auto pDynLayout = GetDynamicLayout();
    pDynLayout->AddItem(m_tabControl, CMFCDynamicLayout::MoveNone(), CMFCDynamicLayout::SizeHorizontalAndVertical(100, 100));

    m_pResultTableTabWindow = CreateCustomFormulasListWidget(ECustomFormulaTable::Results, &m_tabControl);
    m_pSummaryTableTabWindow = CreateCustomFormulasListWidget(ECustomFormulaTable::Summary, &m_tabControl);

    m_tabControl.AddTab(m_pResultTableTabWindow.get(), IDS_CUSTOM_FORMULA_TARGET_RESULT_TABLE, 0, false);
    m_tabControl.AddTab(m_pSummaryTableTabWindow.get(), IDS_CUSTOM_FORMULA_TARGET_SUMMARY_TABLE, 0, false);
}

方案2:重写CFormView的WM_SIZE/WM_MOVE消息处理,主动传递重绘信号

在CustomFormulasView中添加消息响应函数,确保窗口大小变化或移动时,强制CMFCTabCtrl及其内部控件重绘:

void CustomFormulasView::OnSize(UINT nType, int cx, int cy)
{
    __super::OnSize(nType, cx, cy);
    if (m_tabControl.GetSafeHwnd())
    {
        m_tabControl.RedrawWindow(nullptr, nullptr, RDW_INVALIDATE | RDW_ALLCHILDREN | RDW_UPDATENOW);
    }
}

void CustomFormulasView::OnMove(int x, int y)
{
    __super::OnMove(x, y);
    if (m_tabControl.GetSafeHwnd())
    {
        m_tabControl.RedrawWindow(nullptr, nullptr, RDW_INVALIDATE | RDW_ALLCHILDREN | RDW_UPDATENOW);
    }
}

同时移除临时修复中的OnPaint重写代码,避免重复重绘导致性能损耗。

方案3:确保CMFCTabCtrl的子窗口正确注册重绘通知

在CustomFormulasListWidget中添加OnSize消息响应,主动触发自身重绘:

void CustomFormulasListWidget::OnSize(UINT nType, int cx, int cy)
{
    __super::OnSize(nType, cx, cy);
    RedrawWindow(nullptr, nullptr, RDW_INVALIDATE | RDW_UPDATENOW);
}

实现中的潜在错误

  1. 在Create函数中直接创建CMFCTabCtrl时,CFormView的对话框模板初始化可能未完成,导致控件的初始位置/大小绑定存在偏差,后续动态布局调整时无法正确触发重绘。
  2. 隐藏原对话框控件后仅调用SetDlgCtrlID(0)而未销毁,可能导致窗口消息传递出现冲突,建议调用DestroyWindow()彻底移除原控件。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.18 13:15:53