禁用浮动工具栏上的关闭按钮






4.48/5 (15投票s)
如何禁用浮动工具栏上的关闭按钮
引言
除了标准的停靠工具栏外,我还在我的应用程序中添加了一个浮动工具栏。这个浮动工具栏不能停靠。因此它总是看起来像这样:
看到右上角那个小小的“x”了吗?我只想禁用它,这样用户就无法关闭工具栏。正如你所料,禁用它并不是一件很明显的事情。
背景
MFC中有多种工具栏。我们有CToolBar
、CDialogBar
等。我选择CToolBar
作为我的基类。在MFC SDI应用程序中,你在MainFrame的OnCreate()
成员函数中创建工具栏,并进行相应的操作来停靠或浮动它们。在我的应用程序中,它看起来像这样:if (!m_wndToolBar.CreateEx(this) || !m_wndToolBar.LoadToolBar(IDR_TOOLBAR))
{
TRACE0("Failed to create toolbar\n");
return -1; // fail to create
}
CPoint pt(0, 25);
ClientToScreen(&pt);
m_wndToolBar.EnableDocking(0);
FloatControlBar(&m_wndToolBar, pt);
这段代码创建了工具栏,禁用了该特定工具栏的停靠功能,并调用FloatControlBar()
,指定了工具栏的初始屏幕坐标。我们知道,我们CToolBar
对象暴露的窗口句柄是Win32工具栏控件的句柄,我们也知道这样的控件没有标题栏、粗边框或系统菜单/关闭按钮。显然,我们的工具栏控件托管在另一个窗口中。
使用Spy++在窗口层次结构中进行挖掘,会发现m_wndToolBar
对象中的窗口句柄是AfxControlBar
(确切的类名会有所不同)的一个子窗口,而AfxControlBar
又是一个顶层窗口的子窗口。
是时候转向MFC源代码了。
长话短说,在创建工具栏控件窗口后,你可以选择停靠它或浮动它。在这两种情况下,都会创建一个AfxControlBar
并使其成为我们工具栏的父窗口。然后,如果你停靠窗口,AfxControlBar
将成为你的MainFrame窗口的子窗口。但是,如果你浮动工具栏,则会创建另一个窗口来作为AfxControlBar
的父窗口。那个其他窗口是CMiniDockFrameWnd
,而那个窗口就是带有关闭按钮的窗口。
CMiniDockFrameWnd
这是MFC中一个未文档化的辅助类,它派生自CMiniFrameWnd
。当框架需要CMiniDockFrameWnd
时,它会创建一个CMiniDockFrameWnd
,并进行相应的魔法操作,将我们的工具栏控件与其连接,并将它与我们的MainFrame窗口连接。
解决这个问题的标准方法可能是从CMiniDockFrameWnd
派生我们自己的类,要么为关闭按钮替换新的消息处理程序,要么在创建时修改窗口样式以防止关闭按钮出现。
不幸的是,这种方法不起作用。问题在于,除非你准备创建自己的MFC私有版本,否则没有办法从CMiniFrameWnd
派生一个新类并让框架使用该类。特别是,EnableDocking()
和FloatControlBar()
都不是虚函数,所以你无法用一个重写来替换框架的这一小部分,以便创建CMiniDockFrameWnd
的派生类。我不知道你是否愿意,但我绝对不愿意创建自己的MFC私有版本。同样,我也不会复制代码粘贴大量MFC到我自己的类定义中,以便创建和使用CMiniDockFrameWnd
的派生类。
所以我们必须找到另一种方法。如何子类化CMiniDockFrameWnd
?不行,那也行不通。窗口已经被MFC子类化了,如果你尝试这样做,你会遇到ASSERT
。(我这里说的是MFC风格的子类化。直接子类化可能有效——我没试过。)
我的最终解决方案
可能有点“粗糙”,但它效果很好。解决方案是从工具栏控件窗口向上导航到CMiniDockFrameWnd
,获取系统菜单并禁用“关闭”项。这样就禁用了关闭按钮。我还从菜单中删除了“关闭”项。后者是因为你可以右键单击工具栏标题栏并访问系统菜单。代码如下:void CMyToolBar::DisableCloseButton()
{
// Now remove the Hide menuitem so obligingly
// installed for us by the CMiniDockFrameWnd
// class. We do this by navigating 2 levels
// up from our toolbar to the enclosing
// frame window and modifying its system menu.
CWnd *pWnd = GetParent();
if (pWnd != (CWnd *) NULL)
{
ASSERT_KINDOF(CWnd, pWnd);
pWnd = pWnd->GetParent();
}
if (pWnd != (CWnd *) NULL)
{
ASSERT_KINDOF(CWnd, pWnd);
// Make sure the window we found isn't our MainFrame
// window.
if (pWnd->GetSafeHwnd() != AfxGetMainWnd()->GetSafeHwnd())
{
CMenu *pSysMenu = pWnd->GetSystemMenu(FALSE);
if (pSysMenu != (CMenu *) NULL)
{
ASSERT_KINDOF(CMenu, pSysMenu);
pSysMenu->DeleteMenu(1, MF_BYPOSITION);
pSysMenu->EnableMenuItem(SC_CLOSE, MF_BYCOMMAND | MF_DISABLED);
pSysMenu->DeleteMenu(SC_CLOSE, MF_BYCOMMAND);
}
}
}
}
这段代码基于一个假设,即在我们的工具栏窗口的祖父窗口上附加了一个菜单。在MFC7的当前版本中,无论是停靠窗口还是浮动窗口,这都是成立的。作为安全检查,我们确保我们没有修改MainFrame的系统菜单。我们还通过验证我们获得的指针是否有效来在每一步检查我们假设的有效性。使用代码
将源下载中的两个文件添加到你的项目中。你可能想给类起一个比CMyToolBar
更好的名字。在你的CMainFrm
类中,将CToolBar
更改为CMyToolBar
。然后,在工具栏的早期生命周期中,但**在MainFrame窗口创建之后**,**并且**在调用FloatControlBar()
函数之后,调用DisableCloseButton()
函数。演示项目展示了如何完成此操作。瞧!其他注意事项
有趣的是,在CMiniDockFrameWnd
深处有一些代码可以修改系统菜单。‘关闭’菜单项被移除,然后重新添加,文本为‘隐藏’。WM_CLOSE
处理程序实际上并没有关闭CMiniDockFrameWnd
,它只是隐藏它。工具栏可以通过调用CFrameWnd::ShowControlBar()
再次显示。