Toolbars and Statusbars on Dialogs

By default, MFC only allows you to add Toolbars and Statusbars on CFrameWnd subclassed
objects. This is great, but if you want to add toolbars on dialogs, you’re like an idiot,
because you can’t do this easily.

The first problem is that every handler function expect the parent to be a CFrameWnd
subclassed object, and another one is that you can only show tooltips or statusbar message
when the dialog is in IDLE state. In fact, for the control bars to function properly,
you’ll have to catch some of the messages that the frame window uses.

I’ll have to dig a lot in the documentation to find something that helps me doing that.
One issue was shown me by Mihai Filimon (see articles posted on Monday 16 march 1998),
which was fine, but not very "clean" since you have to make room for the bar
with an invisible static object. (I thanks a lot him for showing the light at the end of
the tunnel).

Let me expose you what to do if you want a toolbar (fixed, there is no other way
possible) and a menu which can talk with any StatusBar in the application (even in the
dialog but I think it will be the subject of another article).

I’ll only expose you how to add a Toolbar on a dialog (the menu is obvious since
you only have to define one and add it on the resource editor).

First, you’ll have to develop your dialog (with its object) without bothering the
place of the ToolBar, and subclass the CDialog class to use this dialog template (nothing
new here).

Then, add this code on the CDialog::OnInitDialog function (the m_wndToolBar variable is
of type CToolBarEx) :


BOOL CMyDlg::OnInitDialog()
{
	// TODO: Add extra initialization here
	CDialog::OnInitDialog();

	// Add the ToolBar.
	if (!m_wndToolBar.Create( this ) ||
		!m_wndToolBar.LoadToolBar(IDR_CORPS_EMIS) )
	{
		TRACE0("Failed to create toolbarn");
		return -1;      // fail to create
	}

	// TODO: Remove this if you don't want tool tips or a resizeable toolbar
	m_wndToolBar.SetBarStyle(m_wndToolBar.GetBarStyle() |
		CBRS_TOOLTIPS | CBRS_FLYBY  );

// We need to resize the dialog to make room for control bars.
	// First, figure out how big the control bars are.
	CRect rcClientStart;
	CRect rcClientNow;
	GetClientRect(rcClientStart);
	RepositionBars(AFX_IDW_CONTROLBAR_FIRST, AFX_IDW_CONTROLBAR_LAST,
				   0, reposQuery, rcClientNow);

	// Now move all the controls so they are in the same relative
	// position within the remaining client area as they would be
	// with no control bars.
	CPoint ptOffset(rcClientNow.left - rcClientStart.left,
					rcClientNow.top - rcClientStart.top);

	CRect  rcChild;
	CWnd* pwndChild = GetWindow(GW_CHILD);
	while (pwndChild)
	{
		pwndChild->GetWindowRect(rcChild);
		ScreenToClient(rcChild);
		rcChild.OffsetRect(ptOffset);
		pwndChild->MoveWindow(rcChild, FALSE);
		pwndChild = pwndChild->GetNextWindow();
	}

	// Adjust the dialog window dimensions
	CRect rcWindow;
	GetWindowRect(rcWindow);
	rcWindow.right += rcClientStart.Width() - rcClientNow.Width();
	rcWindow.bottom += rcClientStart.Height() - rcClientNow.Height();
	MoveWindow(rcWindow, FALSE);

	// And position the control bars
	RepositionBars(AFX_IDW_CONTROLBAR_FIRST, AFX_IDW_CONTROLBAR_LAST, 0);

	return TRUE;  // return TRUE unless you set the focus to a control
	              // EXCEPTION: OCX Property Pages should return FALSE
}

You can see that it is not obvious (I found it in a more complicated sample called
DLGCBR32, and I work a lot to find out the functions that are compulsory, and the others)

At that point, you have only a Toolbar in top of your dialog (you see that any toolbar
can fit the requirements to be shown in a dialog, since the preceding code is generic
enough). You can have many toolbars in your dialog, but you must manage the positions by
yourself.

Now, if you want tooltips on your Toolbar, you must handle the following messages
TTN_NEEDTEXTA and TTN_NEEDTEXTW (for both ANSI and Unicode characters sets), in the
following way :

1. Add the message handlers declaration…


BEGIN_MESSAGE_MAP(CMyDlg, CDialog)
	...
	ON_NOTIFY_EX_RANGE(TTN_NEEDTEXTW, 0, 0xFFFF, OnToolTipText)
	ON_NOTIFY_EX_RANGE(TTN_NEEDTEXTA, 0, 0xFFFF, OnToolTipText)
END_MESSAGE_MAP()

2. Declare the function in your header file that way :


// Generated message map functions
//{{AFX_MSG(CMyDlg)
afx_msg BOOL OnToolTipText(UINT, NMHDR* pNMHDR, LRESULT* pResult);
//}}AFX_MSG

3. And finally code your OnToolTipText function that way (comes from MFC Sample) :


BOOL CMyDlg::OnToolTipText(UINT, NMHDR* pNMHDR, LRESULT* pResult)
{
	ASSERT(pNMHDR->code == TTN_NEEDTEXTA || pNMHDR->code == TTN_NEEDTEXTW);

	// allow top level routing frame to handle the message
	if (GetRoutingFrame() != NULL)
		return FALSE;

	// need to handle both ANSI and UNICODE versions of the message
	TOOLTIPTEXTA* pTTTA = (TOOLTIPTEXTA*)pNMHDR;
	TOOLTIPTEXTW* pTTTW = (TOOLTIPTEXTW*)pNMHDR;
	TCHAR szFullText[256];
	CString cstTipText;
	CString cstStatusText;

	UINT nID = pNMHDR->idFrom;
	if (pNMHDR->code == TTN_NEEDTEXTA && (pTTTA->uFlags & TTF_IDISHWND) ||
		pNMHDR->code == TTN_NEEDTEXTW && (pTTTW->uFlags & TTF_IDISHWND))
	{
		// idFrom is actually the HWND of the tool
		nID = ((UINT)(WORD)::GetDlgCtrlID((HWND)nID));
	}

	if (nID != 0) // will be zero on a separator
	{
		AfxLoadString(nID, szFullText);
			// this is the command id, not the button index
		AfxExtractSubString(cstTipText, szFullText, 1, 'n');
		AfxExtractSubString(cstStatusText, szFullText, 0, 'n');
	}

	// Non-UNICODE Strings only are shown in the tooltip window...
	if (pNMHDR->code == TTN_NEEDTEXTA)
		lstrcpyn(pTTTA->szText, cstTipText,
            (sizeof(pTTTA->szText)/sizeof(pTTTA->szText[0])));
	else
		_mbstowcsz(pTTTW->szText, cstTipText,
            (sizeof(pTTTW->szText)/sizeof(pTTTW->szText[0])));
	*pResult = 0;

	// bring the tooltip window above other popup windows
	::SetWindowPos(pNMHDR->hwndFrom, HWND_TOP, 0, 0, 0, 0,
		SWP_NOACTIVATE|SWP_NOSIZE|SWP_NOMOVE);


	return TRUE;    // message was handled
}

Now you have a toolbar with tooltips (the text shown in the tooltip box is defined the
classic way, with the resource editor). (pheeew 😉 ).

Now, if you want the mainframe’s status bar to display the text that would normally be
prompted, the only line (right, the ONLY one) of code you have to add is in the above
function (it is useful for System modal dialog boxes since they are usually small), just
before the return statement (it also assume that m_wndStatusBar is public) :


// Display the text in the mainframe's status bar (assumes the Help pane text
// is at the index zero.
	((CMainFrame*)GetParent())->m_wndStatusBar.SetPaneText(0, cstStatusText);

That’s all for today.

No, let me tell you just one more thing, if you want your dialog menu to display text
in the mainframe’s status bar (on in any other status bar you have created) you can add
the following code to the handler of WM_MENUSELECT (window-type handler) :


void CMyDlg::OnMenuSelect(UINT nItemID, UINT nFlags, HMENU hSysMenu)
{
	CDialog::OnMenuSelect(nItemID, nFlags, hSysMenu);

	TCHAR szFullText[256];
	CString cstStatusText;
	// TODO: Add your message handler code here
	// Displays in the mainframe's status bar
	if (nItemID != 0) // will be zero on a separator
	{
		AfxLoadString(nItemID, szFullText);
			// this is the command id, not the button index
		AfxExtractSubString(cstStatusText, szFullText, 0, 'n');
		((CMainFrame*)GetParent())->m_wndStatusBar.SetPaneText(0,cstStatusText);
}
}

Note that you can’t use the ON_UPDATE_COMMAND_UI message on this toolbar, since it can
only be refresh when the dialog enters in idle state. To do so, you must subclass the
CToolBar (or CStatusBar) class – in CMyToolBar for instance -, add the message handler for
WM_IDLEUPDATECMDUI, and write the following code in this function :


/////////////////////////////////////////////////////////////////////////////
// CMyToolBar::OnIdleUpdateCmdUI
//	OnIdleUpdateCmdUI handles the WM_IDLEUPDATECMDUI message, which is
//	used to update the status of user-interface elements within the MFC
//	framework.
//
// 	We have to get a little tricky here: CToolBar::OnUpdateCmdUI
//	expects a CFrameWnd pointer as its first parameter.  However, it
//	doesn't do anything but pass the parameter on to another function
//	which only requires a CCmdTarget pointer.  We can get a CWnd pointer
//	to the parent window, which is a CCmdTarget, but may not be a
//	CFrameWnd.  So, to make CToolBar::OnUpdateCmdUI happy, we will call
//	our CWnd pointer a CFrameWnd pointer temporarily.

LRESULT CMyToolBar::OnIdleUpdateCmdUI(WPARAM wParam, LPARAM)
{
	if (IsWindowVisible())
	{
		CFrameWnd *pParent = (CFrameWnd *)GetParent();
		if (pParent)
			OnUpdateCmdUI(pParent, (BOOL)wParam);
	}
	return 0L;
}

In order to have the toolbar updated you MUST override the ContinueModal() function that
way:


BOOL CMyDlg::ContinueModal()
{
    m_wndToolbar.SendMessage( WM_IDLEUPDATECMDUI, WPARAM(TRUE), 0);


    return CDialog::ContinueModal();
}

For AfxLoadString to compile you must Include the file "Afxpriv.h" at the
beginning of the CPP file.

More by Author

Get the Free Newsletter!

Subscribe to Developer Insider for top news, trends & analysis

Must Read