Introduction
I wanted a floating toolbar in my application in addition to the standard docked toolbar. The floating toolbar isn't dockable.
Thus it always looks like this:
See that little 'x' in the top right corner? All I wanted was to disable it so that the user couldn't dismiss the
toolbar. As you'd guess, it turned out to not be terribly obvious how to disable it.
Background
There are a number of variations on the Toolbar in MFC. We have
CToolBar
,
CDialogBar
and so on. I chose
CToolBar
as my base class. In MFC SDI applications you create your toolbars in the MainFrame's
OnCreate()
member function and do the appropriate voodoo to dock or float them. In my application it looks like this.
if (!m_wndToolBar.CreateEx(this) || !m_wndToolBar.LoadToolBar(IDR_TOOLBAR))
{
TRACE0("Failed to create toolbar\n");
return -1;
}
CPoint pt(0, 25);
ClientToScreen(&pt);
m_wndToolBar.EnableDocking(0);
FloatControlBar(&m_wndToolBar, pt);
which creates the toolbar, disables docking for that particular toolbar and calls
FloatControlBar()
specifying the
initial screen coordinates for the toolbar.
We know that the window handle exposed in our CToolBar
object is a handle to a Win32 Toolbar Control and we also
know that such a control doesn't have a caption, a thick frame or a system menu/close button. Obviously, then, our Toolbar Control
is hosted within another window.
Digging around in the window heirarchy using Spy++ reveals that the window handle contained in the m_wndToolBar
object is a child of an AfxControlBar
(the exact class name varies) which in turn is a child of a top level window.
Time to turn to the MFC source code.
Cutting a long story short, after creating the Toolbar Control Window you choose either to dock it or float it. In either
case an AfxControlBar
is created and made the parent of our toolbar. Then, if you're docking the window, the
AfxControlBar
is made a child of your MainFrame window. If, however, you're floating the toolbar, another window is
created to parent the AfxControlBar
. That other window is a CMiniDockFrameWnd
and that's the window
which has the close button.
CMiniDockFrameWnd
This is an undocumented helper class in MFC derived from
CMiniFrameWnd
. The framework creates a
CMiniDockFrameWnd
whenever it needs one and does the appropriate magic to wire our Toolbar Control to it, and to
wire it to our MainFrame window.
The standard approach to this problem might be to derive our own class from CMiniDockFrameWnd
either substituting
a new message handler for the close button or modifying the window styles at creation time to prevent the close button even
appearing.
Unfortunately this approach won't work. The problem is there's no way to derive a new class from CMiniFrameWnd
and
get the framework to use that class unless you're prepared to create your own private version of MFC. In particular, neither
EnableDocking()
nor FloatControlBar()
are virtual so you can't replace that small portion of the
framework with an override that would create your derivative of CMiniDockFrameWnd
. Dunno about you but there's no way
I'm prepared to create my own private version of MFC. Nor, for that matter, am I prepared to cut and paste a significant
proportion of MFC into my own class definitions to allow the creation and use of a derivation of CMiniDockFrameWnd
.
So we have to find another way. How about subclassing the CMiniDockFrameWnd
? Nope, that won't work either. The
window is already subclassed by MFC and you'll hit ASSERT
s if you try. (I'm talking about MFC style sub-classing
here. Direct subclassing may work - I didn't try it.)
My final solution
is maybe somewhat 'quick-and-dirty' but it works quite well. The solution is to navigate up the window heirarchy from the ToolBar
Control Window to the
CMiniDockFrameWnd
, get the system menu and disable the 'Close' item. This disables the close
button. I also remove the 'Close' item from the menu. The latter is done because you can right click on the toolbar caption and
get access to the system menu. The code looks like this.
void CMyToolBar::DisableCloseButton()
{
CWnd *pWnd = GetParent();
if (pWnd != (CWnd *) NULL)
{
ASSERT_KINDOF(CWnd, pWnd);
pWnd = pWnd->GetParent();
}
if (pWnd != (CWnd *) NULL)
{
ASSERT_KINDOF(CWnd, pWnd);
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);
}
}
}
}
The code is based on the assumption that there's a menu attached to the grandparent of our ToolBar window. That'll be true both
for docked and floating windows in the current incarnation of MFC7. As a safety check we ensure that we're not modifying
the system menu for our MainFrame. We also check the validity of our assumptions at each step by verifying that the pointers
we get back are valid.
Using the code
Add the two files in the source download to your project. You probably want to give the class a better name than
CMyToolBar
.
In your
CMainFrm
class change the
CToolBar
to be a
CMyToolBar
. Then, sometime early in the
life of the Toolbar but after the MainFrame window has been created
and after you've called the
FloatControlBar()
function you call the
DisableCloseButton()
function. The demo project shows how this is done. Voila!
Other notes
Interestingly enough, deep within the bowels of
CMiniDockFrameWnd
is some code to modify the system menu. The 'Close'
menuitem is removed and added back in with 'Hide' as the text. The
WM_CLOSE
handler doesn't actually close a
CMiniDockFrameWnd
, it merely hides it. The Toolbar can be made visible again by calling
CFrameWnd::ShowControlBar()
.
History
11 April 2004 - Initial version