CPaneFrameWnd 中的 BUG(MFC 功能包 VS 2015)
BUG in CPaneFrameWnd (MFC Feature Pack VS 2015)
miniframe class CPaneFrameWnd 包含智能对接算法错误!
此 class 在 MFC 中用作浮动窗格的迷你框架,并且可以将其停靠到父框架停靠站点或选项卡式窗格。当所有窗格只能停靠到主框架时,它工作正常,但是当窗格停靠到 MDI 应用程序中的子框架时,这个 class 有一个错误。重现错误的步骤:
- 取消停靠某些窗格以使其处于浮动状态。
- 在 MDI 子框架中保存停靠状态:
GetDockingManager()->SaveState(...)
- 恢复此 MDI 子框架的停靠状态:
GetDockingManager()->LoadState(...); GetDockingManager()->SetDockState();
- 并尝试通过鼠标将此窗格停靠到同一框架侧。
- 你不能这样做。窗格被鼠标移动到框架侧但未固定。
CPaneFrameWnd class 源中的错误。在许多地方 class 使用代码 m_pDockManager != NULL ? m_pDockManager : afxGlobalUtils.GetDockingManager(GetParent());
访问其码头管理器。
但在 class 的某些地方,此代码看起来像 m_pDockManager != NULL ? m_pDockManager : afxGlobalUtils.GetDockingManager(this);
。这就是错误的原因 - 全局函数 afxGlobalUtils.GetDockingManager() 无法从 this
指针获取停靠管理器并尝试从 this
指针的父 window 获取它。看起来像 pManager != NULL ? pManager : GetDockingManager(pWnd->GetParent());
。但是 class CPaneFrameWnd 有 INLINE NONVIRTUAL GetParent() 方法不能被 afxGlobalUtils.GetDockingManager() 访问。因此,经过一些递归 afxGlobalUtils.GetDockingManager() returns 主应用程序框架的码头管理器!当然,这个 dockmanager 与 MDI 子框架的 docmanager 不同。
唯一正确的解决方案是将 CPaneFrameWnd 源(afxpaneframewnd.cpp 文件)中的所有 m_pDockManager != NULL ? m_pDockManager : afxGlobalUtils.GetDockingManager(this);
更改为 m_pDockManager != NULL ? m_pDockManager : afxGlobalUtils.GetDockingManager(GetParent());
。
但这需要对 MFC 代码进行修补。我们都知道微软有多懒。
可能有人知道如何修复当前 MFC 版本中的这个错误?
我找到了错误修复的解决方法。如问题所述,主要问题是迷你框架 class CPaneFrameWnd
具有未初始化的 m_pDockManager
属性(它具有 nullptr
值)。因此,在某些情况下 class 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();
}
miniframe class CPaneFrameWnd 包含智能对接算法错误! 此 class 在 MFC 中用作浮动窗格的迷你框架,并且可以将其停靠到父框架停靠站点或选项卡式窗格。当所有窗格只能停靠到主框架时,它工作正常,但是当窗格停靠到 MDI 应用程序中的子框架时,这个 class 有一个错误。重现错误的步骤:
- 取消停靠某些窗格以使其处于浮动状态。
- 在 MDI 子框架中保存停靠状态:
GetDockingManager()->SaveState(...)
- 恢复此 MDI 子框架的停靠状态:
GetDockingManager()->LoadState(...); GetDockingManager()->SetDockState();
- 并尝试通过鼠标将此窗格停靠到同一框架侧。
- 你不能这样做。窗格被鼠标移动到框架侧但未固定。
CPaneFrameWnd class 源中的错误。在许多地方 class 使用代码 m_pDockManager != NULL ? m_pDockManager : afxGlobalUtils.GetDockingManager(GetParent());
访问其码头管理器。
但在 class 的某些地方,此代码看起来像 m_pDockManager != NULL ? m_pDockManager : afxGlobalUtils.GetDockingManager(this);
。这就是错误的原因 - 全局函数 afxGlobalUtils.GetDockingManager() 无法从 this
指针获取停靠管理器并尝试从 this
指针的父 window 获取它。看起来像 pManager != NULL ? pManager : GetDockingManager(pWnd->GetParent());
。但是 class CPaneFrameWnd 有 INLINE NONVIRTUAL GetParent() 方法不能被 afxGlobalUtils.GetDockingManager() 访问。因此,经过一些递归 afxGlobalUtils.GetDockingManager() returns 主应用程序框架的码头管理器!当然,这个 dockmanager 与 MDI 子框架的 docmanager 不同。
唯一正确的解决方案是将 CPaneFrameWnd 源(afxpaneframewnd.cpp 文件)中的所有 m_pDockManager != NULL ? m_pDockManager : afxGlobalUtils.GetDockingManager(this);
更改为 m_pDockManager != NULL ? m_pDockManager : afxGlobalUtils.GetDockingManager(GetParent());
。
但这需要对 MFC 代码进行修补。我们都知道微软有多懒。
可能有人知道如何修复当前 MFC 版本中的这个错误?
我找到了错误修复的解决方法。如问题所述,主要问题是迷你框架 class CPaneFrameWnd
具有未初始化的 m_pDockManager
属性(它具有 nullptr
值)。因此,在某些情况下 class 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();
}