miniframe类CPaneFrameWnd包含智能对接算法中的错误!
此类在MFC中用作 float Pane 的微型框架,可以将其停靠到父框架停靠站点或选项卡式 Pane 。当所有 Pane 只能停靠在主框架上时,它工作正常,但是在MDI应用程序中将 Pane 停靠在子框架上时,此类有一个错误。重现该错误的步骤:

  • 取消对某些 Pane 的 float 状态。
  • 将对接状态保存在MDI子框架中:GetDockingManager()->SaveState(...)
  • 恢复此MDI子框架的停靠状态:GetDockingManager()->LoadState(...); GetDockingManager()->SetDockState();
  • 并尝试用鼠标将该 Pane 停靠在同一框架侧。
  • 您不能这样做。 Pane 通过鼠标移动到框架侧,但没有固定。

  • CPaneFrameWnd类源中的错误。在许多地方,类使用代码m_pDockManager != NULL ? m_pDockManager : afxGlobalUtils.GetDockingManager(GetParent());来访问其dockmanager。
    但是在某些地方,该代码看起来像m_pDockManager != NULL ? m_pDockManager : afxGlobalUtils.GetDockingManager(this);。这就是该错误的原因-全局函数afxGlobalUtils.GetDockingManager()无法从this指针获取停靠管理器,并尝试从this指针的父窗口获取它。看起来像pManager != NULL ? pManager : GetDockingManager(pWnd->GetParent());。但是类CPaneFrameWnd具有inline NONVIRTUAL GetParent()方法,afxGlobalUtils.GetDockingManager()无法访问该方法。因此,经过一些递归后,afxGlobalUtils.GetDockingManager()返回MAIN应用程序框架的dockmanager!当然,该DockManager与用于MDI子框架的docmanager不同。
    唯一的解决方法是将CPaneFrameWnd源文件(afxpaneframewnd.cpp文件)中的所有m_pDockManager != NULL ? m_pDockManager : afxGlobalUtils.GetDockingManager(this);更改为m_pDockManager != NULL ? m_pDockManager : afxGlobalUtils.GetDockingManager(GetParent());
    但这需要对MFC代码进行修补。我们所有人都知道微软是多么懒惰。
    可能有人知道如何在当前的MFC版本中修复此错误?

    最佳答案

    我找到了解决错误的解决方法。如所提到的,主要问题是小帧类CPaneFrameWnd具有未初始化的m_pDockManager属性(具有nullptr值)。因此,在某些情况下,CPaneFrameWnd类无法从父级找到正确的DockManager。该错误的解决方法是强制初始化所有微型框架的m_pDockManager属性。这样做的好地方是从注册表中还原对接状态(有问题的步骤3)。

    正确保存和加载子框架对接状态的示例:

    // Save docking state for CChildFrame class (inherited from CMDIChildWndEx)
    void CChildFrame::SaveBarState(LPCTSTR lpszProfileName) const
    {
        const_cast<CChildFrame*>(this)->GetDockingManager()->SaveState(lpszProfileName);
    
        CObList list;
        const_cast<CChildFrame*>(this)->GetDockingManager()->GetPaneList(list, FALSE, NULL, FALSE);
        if (list.GetCount() > 0) {
            POSITION pos = list.GetTailPosition();
            while (pos != NULL) {
                CMFCToolBar* pToolBar = DYNAMIC_DOWNCAST(CMFCToolBar, list.GetPrev(pos));
                if (pToolBar != nullptr) {
                    pToolBar->SaveState(lpszProfileName);
                }
            }
        }
    }
    
    // Restore docking state for CChildFrame class (inherited from CMDIChildWndEx)
    void CChildFrame::LoadBarState(LPCTSTR lpszProfileName)
    {
        CObList list;
        GetDockingManager()->GetPaneList(list, FALSE, NULL, FALSE);
        if (list.GetCount() > 0) {
            POSITION pos = list.GetTailPosition();
            while (pos != NULL) {
                CMFCToolBar* pToolBar = DYNAMIC_DOWNCAST(CMFCToolBar, list.GetPrev(pos));
                if (pToolBar != nullptr) {
                    pToolBar->LoadState(lpszProfileName);
                }
            }
        }
    
        GetDockingManager()->LoadState(lpszProfileName);
        GetDockingManager()->SetDockState();
        GetDockingManager()->ShowDelayShowMiniFrames(TRUE);
    
        // MFC BUGFIX: force assigning the child frame docking manager to all miniframes.
        for (POSITION pos = GetDockingManager()->GetMiniFrames().GetHeadPosition(); pos != NULL;)
        {
            CWnd* pWndNext = (CWnd*)GetDockingManager()->GetMiniFrames().GetNext(pos);
            if (pWndNext != nullptr && pWndNext->IsKindOf(RUNTIME_CLASS(CPaneFrameWnd))) {
                STATIC_DOWNCAST(CPaneFrameWnd, pWndNext)->SetDockingManager(GetDockingManager());
            }
        }
    }
    

    如何使用此代码:
    // creating child frame and its panes, loading the saved panes docking state.
    int CChildFrame::OnCreate(LPCREATESTRUCT lpCreateStruct)
    {
        bool bRes = TBase::OnCreate(lpCreateStruct) == 0;
        if (bRes)
        {
            // enable docking
            EnableDocking(CBRS_ALIGN_ANY);
    
            // enable Visual Studio 2005 style docking window behavior
            CDockingManager::SetDockingMode(DT_SMART);
    
            // Creating toolbar, statusbar and panes. Dock them to default places.
            {
                // ....
            }
        }
    
        if (bRes) {
           LoadBarState(theApp.GetRegSectionPath(_T("ChildFrame")));
        }
        return bRes ? 0 : 1;
    }
    
    
    // destroy child frame and save panes docking state.
    void CChildFrame::OnDestroy()
    {
        SaveBarState(theApp.GetRegSectionPath(_T("ChildFrame")));
        TBase::OnDestroy();
    }
    

    Full sample source code

    10-08 19:18