更好的工具栏停靠





4.00/5 (4投票s)
VC6 应用程序中工具栏停靠问题的解决方法
引言
我一直在使用 VC6 开发一个应用程序,已经有一段时间了。在这个应用程序中,我有 20 多个工具栏。但任何时候,并非所有的工具栏都会显示给用户,因为有些工具栏始终是隐藏的,而有些则根据操作模式是否可用而向用户提供。所有不适用于当前操作模式的工具栏都被设置为隐藏模式。当我只有几个工具栏时,这工作得很好。但随着工具栏数量的增加,我发现工具栏不会保留它们的状态,或者当我尝试将它们从一行移动到另一行时,它们会跳到另一行并移动其他工具栏。
背景
为了解决这个问题,我搜索了 CodeProject 和其他网站,看看是否有人遇到过这个问题。搜索时,我发现了一些很好的链接。其中一些如下所示:
- 并排停靠工具栏 [toolbar_docking.aspx],作者:Kirk Stowell。这个链接在我开始开发应用程序时很有用……但开始之后,如果我尝试移动应用程序的工具栏,我仍然会遇到移动时位置未保留的问题。此外,这会使工具栏始终按照预定的顺序放置,而不是用户在关闭应用程序之前放置的顺序。
- 如何将栏并排停靠 [http://www.datamekanix.com/articles/side-by-side/],作者:Cristi Posea。这个链接,与第一个链接类似,会按照预定义的格式进行工具栏的停靠。
- 通过进一步搜索,我发现在 Microsoft 的 bug 记录站点上有一个已记录的问题,它描述了我遇到的问题。[http://connect.microsoft.com/VisualStudio/feedback/details/100915/cdockbar-insert-bug]。如本链接末尾所述,Visual Studio 2005 SP1 提供了修复。这对我来说帮助不大,因为我仍然在使用 VC6。
由于我想要一个针对 VC6 的“工具栏跳转且不保留其位置”问题的解决方案,该问题已由 Microsoft 在 Visual Studio 2005 SP1 中修复,我决定编写一个自定义工具栏类,然后使用该类来实现自定义 DockBar
和 DockContext
类,以便让框架允许我处理工具栏停靠。这样,我认为我可以在 VC6 中实现 Microsoft 为 VC8 所做的修复。但按照文章中提到的所有步骤操作后,我仍然发现工具栏跳转的问题依然存在。因此,我进行了修改,使我能够应用 Microsoft 的修复(如 VC8 的 CDockBar
源代码中所找到的),并添加了我自己的更改,使工具栏停止跳转。
有了这个想法,我搜索了我最喜欢的 CodeProject 网站上关于自定义工具栏的文章,并找到了一些不错的文章。它们列在下面:
- “具有自定义和控件的工具栏” [toolbarex.aspx],作者:Deepak Khajuria。使用这篇文章的概念,我修改了代码以修复工具栏停靠问题。
- “
CSizingControlBar
- 可调整大小的控件栏” [sizecbar.aspx],作者:Cristi Posea。这帮助我定制了CCustomDockContext
类。
Using the Code
我遵循了以下步骤:
- 在 Mainfrm.h 中,将工具栏对象类型从标准的
CToolBar
对象替换为CCustomToolbar
(这将允许您在需要时为工具栏添加一些自定义功能,并且也是重写 MFC 的CDockBar
和CDockContext
类所必需的)。 - 在 MainFrm.cpp 中,在
CMainFrame::OnCreate()
函数中,创建工具栏对象后,您需要调用: EnableCustomToolbarDocking(CBRS_ALIGN_TOP);
此调用确保CCustomDockContext
类对象被设置为工具栏的停靠上下文对象。EnableCustomFrameDocking(this, CBRS_ALIGN_ANY);
此调用会将CDockBar
类对象替换为CCustomDockBar
类对象。
代码片段如下所示:
int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
if (CFrameWnd::OnCreate(lpCreateStruct) == -1)
return -1;
//Create elements of the application, like status bar here...
//create the toolbar...
if (!m_wndToolBar.CreateEx(this, TBSTYLE_FLAT, WS_CHILD | WS_VISIBLE | CBRS_TOP
| CBRS_GRIPPER | CBRS_TOOLTIPS | CBRS_FLYBY | CBRS_SIZE_DYNAMIC) ||
!m_wndToolBar.LoadToolBar(IDR_MAINFRAME))
{
TRACE0("Failed to create toolbar\n");
return -1; // fail to create
}
m_wndToolBar.LoadBitmap(IDB_BITMAP1);
//Set the font, window text and other attributes for your toolbar...
//Set the docking for the toolbar. I have used only CBRS_ALIGN_TOP here...
m_wndToolBar.EnableCustomToolbarDocking(CBRS_ALIGN_TOP);
//Setup the custom frame docking for the application.
//I have used the function code from the toolbarex article...
EnableCustomFrameDocking(this, CBRS_ALIGN_ANY);
//This custom dock function will allow us to use
//our CCustomDockBar class implementation...
DockCustomControlBar(&m_wndToolBar);
// Please note: here only one toolbar creation is shown,
//but you will have to do it for all toolbars that are created for the application.
return 0;
}
在您的自定义工具栏类中,实现 EnableCustomToolbarDocking()
函数。在此函数中,CCustomDockContext
对象被设置为工具栏的停靠上下文。实现如下:
void CCustomToolsControlBar::EnableCustomToolbarDocking(DWORD dwDockStyle)
{
// must be CBRS_ALIGN_XXX or CBRS_FLOAT_MULTI only
ASSERT((dwDockStyle & ~(CBRS_ALIGN_ANY | CBRS_FLOAT_MULTI)) == 0);
// cannot have the CBRS_FLOAT_MULTI style
ASSERT((dwDockStyle & CBRS_FLOAT_MULTI) == 0);
// the bar must have CBRS_SIZE_DYNAMIC style
ASSERT((m_dwStyle & CBRS_SIZE_DYNAMIC) != 0);
m_dwDockStyle = dwDockStyle;
if(m_pDockContext == NULL)
m_pDockContext = new CCustomDockContext(this);
// permanently wire the bar's owner to its current parent
if(m_hWndOwner == NULL)
m_hWndOwner = ::GetParent(m_hWnd);
}
EnableCustomFrameDocking();
是一个全局函数,我们将框架设置的现有 CDockBar
对象替换为我们的 CCustomDockBar
对象。此函数的实现如下:
const DWORD customDockBarMappingInfo[4][2] =
{
{ AFX_IDW_DOCKBAR_TOP, CBRS_TOP },
{ AFX_IDW_DOCKBAR_BOTTOM, CBRS_BOTTOM },
{ AFX_IDW_DOCKBAR_LEFT, CBRS_LEFT },
{ AFX_IDW_DOCKBAR_RIGHT, CBRS_RIGHT },
};
void EnableCustomFrameDocking(CFrameWnd* pFrame, DWORD dwDockStyle)
{
ASSERT_VALID(pFrame);
// must be CBRS_ALIGN_XXX or CBRS_FLOAT_MULTI only
ASSERT((dwDockStyle & ~(CBRS_ALIGN_ANY | CBRS_FLOAT_MULTI)) == 0);
pFrame->EnableDocking(dwDockStyle);
for (int i = 0; i < 4; i++)
{
if (customDockBarMappingInfo[i][1] & dwDockStyle & CBRS_ALIGN_ANY)
{
CDockBar* pDock = (CDockBar*)pFrame->GetControlBar
(customDockBarMappingInfo[i][0]);
// make sure the dock bar is of correct type
if( pDock == NULL || ! pDock->IsKindOf(RUNTIME_CLASS(CCustomDockBar)) )
{
BOOL bNeedDelete = ! pDock->m_bAutoDelete;
pDock->m_pDockSite->RemoveControlBar(pDock);
pDock->m_pDockSite = 0; // avoid problems in destroying the dockbar
pDock->DestroyWindow();
if(bNeedDelete)
delete pDock;
pDock = NULL;
}
if(pDock == NULL)
{
pDock = new CCustomDockBar;
ASSERT_VALID(pDock);
if ((!pDock) || (!pDock->Create(pFrame,
WS_CLIPSIBLINGS | WS_CLIPCHILDREN |
WS_CHILD | WS_VISIBLE | customDockBarMappingInfo[i][1],
customDockBarMappingInfo[i][0])))
{
AfxThrowResourceException();
}
}
}
}
}
现在我们已经用 CCustomDockBar
和 CCustomDockContext
类对象重新连接了 CDockBar
和 CDockContext
类对象,我们需要确保调用自定义功能来执行停靠操作,而不是 MFC 的。为此,请在您的 MainFrm.h 文件中定义以下函数:
void DockCustomControlBar(CControlBar* pBar, UINT nDockBarID = 0, LPCRECT lpRect = NULL);
void DockCustomControlBar(CControlBar* pBar, CCustomDockBar* pDockBar, LPCRECT lpRect);
这些函数的实现如下:
void CMainFrame::DockCustomControlBar
(CControlBar* pBar, UINT nDockBarID, LPCRECT lpRect)
{
CCustomDockBar* pDockBar =
(nDockBarID == 0) ? NULL : (CCustomDockBar*)GetControlBar(nDockBarID);
DockCustomControlBar(pBar, pDockBar, lpRect);
}
void CMainFrame::DockCustomControlBar
(CControlBar* pBar, CCustomDockBar* pDockBar, LPCRECT lpRect)
{
ASSERT(pBar != NULL);
// make sure CControlBar::EnableDocking has been called
ASSERT(pBar->m_pDockContext != NULL);
if (pDockBar == NULL)
{
for (int i = 0; i < 4; i++)
{
if ((dwDockBarMap[i][1] & CBRS_ALIGN_ANY) ==
(pBar->m_dwStyle & CBRS_ALIGN_ANY))
{
pDockBar = (CCustomDockBar*)GetControlBar(dwDockBarMap[i][0]);
ASSERT(pDockBar != NULL);
// assert fails when initial CBRS_ of bar does not
// match available docking sites, as set by EnableDocking()
break;
}
}
}
ASSERT(pDockBar != NULL);
ASSERT(m_listControlBars.Find(pBar) != NULL);
ASSERT(pBar->m_pDockSite == this);
// if this assertion occurred it is because the parent of pBar was not initially
// this CFrameWnd when pBar's OnCreate was called
// i.e. this control bar should have been created
// with a different parent initially
pDockBar->DockControlBar(pBar, lpRect);
}
关注点
用自定义停靠上下文和停靠栏替换整个过程是为了让我们能够控制 CDockBar::Insert()
中用于将工具栏插入到我们期望的位置/感兴趣的位置的代码,无论是在移动工具栏时还是在重新打开应用程序时。这主要在我们在拥有大量工具栏但只有少数几个显示,而其他工具栏因不适用于当前操作模式或用户为了客户端可用性而关闭时非常有用。
下面我将展示 CDockBar::Insert()
函数的代码,因为它存在于:
- VC6 的 MFC\SRC\BarDock.cpp 文件中
int CDockBar::Insert(CControlBar* pBarIns, CRect rect, CPoint ptMid) { ASSERT_VALID(this); ASSERT(pBarIns != NULL); int nPos = 0; int nPosInsAfter = 0; int nWidth = 0; int nTotalWidth = 0; BOOL bHorz = m_dwStyle & CBRS_ORIENT_HORZ; for (nPos = 0; nPos < m_arrBars.GetSize(); nPos++) { CControlBar* pBar = GetDockedControlBar(nPos); if (pBar != NULL && pBar->IsVisible()) { CRect rectBar; pBar->GetWindowRect(&rectBar); ScreenToClient(&rectBar); nWidth = max(nWidth, bHorz ? rectBar.Size().cy : rectBar.Size().cx - 1); if (bHorz ? rect.left > rectBar.left : rect.top > rectBar.top) nPosInsAfter = nPos; } else // end of row because pBar == NULL { nTotalWidth += nWidth - afxData.cyBorder2; nWidth = 0; if ((bHorz ? ptMid.y : ptMid.x) < nTotalWidth) { if (nPos == 0) // first section m_arrBars.InsertAt(nPosInsAfter+1, (CObject*)NULL); m_arrBars.InsertAt(nPosInsAfter+1, pBarIns); return nPosInsAfter+1; } nPosInsAfter = nPos; } } // create a new row m_arrBars.InsertAt(nPosInsAfter+1, (CObject*)NULL); m_arrBars.InsertAt(nPosInsAfter+1, pBarIns); return nPosInsAfter+1; }
- VC8 的 Microsoft Visual Studio 8\VC\atlmfc\src\mfc\BarDock.cpp 中
int CDockBar::Insert(CControlBar* pBarIns, CRect rect, CPoint ptMid) { ENSURE_VALID(this); ENSURE_VALID(pBarIns); int nPos = 0; int nPosInsAfter = 0; int nWidth = 0; int nTotalWidth = 0; BOOL bHorz = m_dwStyle & CBRS_ORIENT_HORZ; for (nPos = 0; nPos < m_arrBars.GetSize(); nPos++) { CControlBar* pBar = GetDockedControlBar(nPos); if (pBar != NULL && pBar->IsVisible()) { CRect rectBar; pBar->GetWindowRect(&rectBar); ScreenToClient(&rectBar); nWidth = max(nWidth, bHorz ? rectBar.Size().cy : rectBar.Size().cx - 1); if (bHorz ? rect.left > rectBar.left : rect.top > rectBar.top) nPosInsAfter = nPos; } else { if (pBar == NULL) // end of row { nTotalWidth += nWidth - afxData.cyBorder2; nWidth = 0; if ((bHorz ? ptMid.y : ptMid.x) < nTotalWidth) { if (nPos == 0) // first section m_arrBars.InsertAt(nPosInsAfter+1, (CObject*)NULL); m_arrBars.InsertAt(nPosInsAfter+1, pBarIns); return nPosInsAfter+1; } nPosInsAfter = nPos; } // invisible toolbars are ignored } } // create a new row m_arrBars.InsertAt(nPosInsAfter+1, (CObject*)NULL); m_arrBars.InsertAt(nPosInsAfter+1, pBarIns); return nPosInsAfter+1; }
CCustomDockBar::Insert()
函数int CCustomDockBar::Insert(CControlBar* pBarIns, CRect rect, CPoint ptMid) { ASSERT_VALID(this); ASSERT(pBarIns != NULL); int nPos = 0; int nPosInsAfter = 0; int nWidth = 0; int nTotalWidth = 0; BOOL bHorz = m_dwStyle & CBRS_ORIENT_HORZ; for (nPos = 0; nPos < m_arrBars.GetSize(); nPos++) { CControlBar* pBar = GetDockedControlBar(nPos); if (pBar != NULL && pBar->IsVisible()) { CRect rectBar; pBar->GetWindowRect(&rectBar); ScreenToClient(&rectBar); nWidth = max(nWidth, bHorz ? rectBar.Size().cy : rectBar.Size().cx - 1); // are we dealing with toolbars placed at the top or bottom? if(bHorz) { if (rect.left > rectBar.left) { nPosInsAfter = nPos; if(nPos + 1 < m_arrBars.GetSize()) { CControlBar* pNextBar = GetDockedControlBar(nPos + 1); if(pNextBar == NULL) { int toolbarRectMidValue = rect.top + (rect.bottom - rect.top) / 2; int currentToolbarFirstQuarter = rectBar.top + (rectBar.bottom - rectBar.top) / 4; int currentToolbarThirdQuarter = rectBar.top + ((rectBar.bottom - rectBar.top) / 4) * 3; if(toolbarRectMidValue >= currentToolbarFirstQuarter && toolbarRectMidValue <= currentToolbarThirdQuarter) { m_arrBars.InsertAt(nPosInsAfter + 1, pBarIns); return (nPosInsAfter + 1); } } } } else { int toolbarRectMidValue = rect.top + (rect.bottom - rect.top) / 2; int currentToolbarFirstQuarter = rectBar.top + (rectBar.bottom - rectBar.top) / 4; int currentToolbarThirdQuarter = rectBar.top + ((rectBar.bottom - rectBar.top) / 4) * 3; if(toolbarRectMidValue >= currentToolbarFirstQuarter && toolbarRectMidValue <= currentToolbarThirdQuarter) { m_arrBars.InsertAt(nPosInsAfter + 1, pBarIns); return (nPosInsAfter + 1); } } } else { if (rect.top > rectBar.top) { nPosInsAfter = nPos; if(nPos + 1 < m_arrBars.GetSize()) { CControlBar* pNextBar = GetDockedControlBar(nPos + 1); if(pNextBar == NULL) { int toolbarRectMidValue = rect.left + (rect.right - rect.left) / 2; int currentToolbarFirstQuarter = rectBar.left + (rectBar.right - rectBar.left) / 4; int currentToolbarThirdQuarter = rectBar.left + ((rectBar.right - rectBar.left) / 4) * 3; if(toolbarRectMidValue >= currentToolbarFirstQuarter && toolbarRectMidValue <= currentToolbarThirdQuarter) { m_arrBars.InsertAt(nPosInsAfter + 1, pBarIns); return (nPosInsAfter + 1); } } } } else { int toolbarRectMidValue = rect.left + (rect.right - rect.left) / 2; int currentToolbarFirstQuarter = rectBar.left + (rectBar.right - rectBar.left) / 4; int currentToolbarThirdQuarter = rectBar.left + ((rectBar.right - rectBar.left) / 4) * 3; if(toolbarRectMidValue >= currentToolbarFirstQuarter && toolbarRectMidValue <= currentToolbarThirdQuarter) { m_arrBars.InsertAt(nPosInsAfter + 1, pBarIns); return (nPosInsAfter + 1); } } } } else { if (pBar == NULL) // end of row { nTotalWidth += nWidth - 2; nWidth = 0; if ((bHorz ? ptMid.y : ptMid.x) < nTotalWidth) { if (nPos == 0) // first section m_arrBars.InsertAt(nPosInsAfter+1, (CObject*)NULL); m_arrBars.InsertAt(nPosInsAfter+1, pBarIns); return nPosInsAfter+1; } nPosInsAfter = nPos; } // invisible toolbars are ignored } } // create a new row m_arrBars.InsertAt(nPosInsAfter+1, (CObject*)NULL); m_arrBars.InsertAt(nPosInsAfter+1, pBarIns); return nPosInsAfter+1; }
希望这有帮助!