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 }
请问是否有更优的解决方案?我的实现是否存在错误?
优化解决方案与问题根源分析
问题根源
- CFormView的重绘消息传递异常:CFormView基于对话框实现,其WM_PAINT消息处理逻辑可能拦截或延迟子控件的重绘请求,导致CMFCTabCtrl及其内部子窗口(标签内容、滚动按钮)无法收到完整的重绘通知。
- 动态布局与控件创建时机不匹配:在
Create函数中直接创建CMFCTabCtrl并绑定动态布局,可能因CFormView初始化流程未完成,导致控件的大小调整通知未正确传递,窗口移动/resize时控件未触发自身重绘。 - 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); }
实现中的潜在错误
- 在
Create函数中直接创建CMFCTabCtrl时,CFormView的对话框模板初始化可能未完成,导致控件的初始位置/大小绑定存在偏差,后续动态布局调整时无法正确触发重绘。 - 隐藏原对话框控件后仅调用
SetDlgCtrlID(0)而未销毁,可能导致窗口消息传递出现冲突,建议调用DestroyWindow()彻底移除原控件。
内容的提问来源于stack exchange,提问作者Pavel Celba
相关产品推荐
相关产品推荐

