Recently we came across a problem where we needed to have multiple dialogs within a single property page or dialog. This article will explain how we did it.
From the internal thread that went around the company, this feature was the source of endless confusion and rewrites. The various sample on the Internet did not help matters much as they required custom derivations for both the
CDialog classes as well as not providing a good keyboard user experience.
This article documents the steps required to provide an embedded dialog(s) within a dialog using MFC. There are no extension classes provided as this provides the technical insight to modify your code to support embedded dialogs.
Embedding a dialog within a property sheet or another dialog appears to be the source of much confusion and various attempts over the years. Sadly I was unable to find a solution that worked transparently and cleanly. There are various flavour of solutions on the net, but not one that had the look and feel of a correct solution. I then came across article “Q131283 - PRB: Cannot Use TAB to Move from Standard Controls to Custom” on Microsoft’s website and it all became clear.
The solution we required was to be simple (it was only on one dialog), easy to use (For end users), simple to maintain (not everyone in the company is a guru). The solution chosen was to have a tab strip and then
CDialogs for each page. As the current page was selected, the previous page was hidden and the new page shown.
Whilst this appears to be simple enough to document, implementing it proved to be more of a challenge, especially with regards to using the keyboard to tab which is an integral feature of the product. Users need to be able to tab in and out of the dialog and the dialog should at the same time look integral to the main window.
This type of design also allows the developer to set the tab control at compile time and runtime by specifying options such as images and tab name, using the common controls API.
Using the code
In the code section I have given an example that shows how a programmer would go about implementing the two embedded dialogs within a new dialog. These dialogs are selectable by clicking or navigating onto the tab control. Note that you can set the focus to the tab control and then use the left and right arrows to select the appropriate tab, then using the tab key, tab into and out of the dialog.
The first thing to do is to create the dialogs that will be embedded in the main dialog. These are just two straight forward
CDialog classes. You can put what ever controls you like in them. The only requirements are that the dialog style is set to Child and Control, on the "More styles" tab on the dialog properties in the resource editor
It is recommended that the Frame type be set to Dialog Frame. Additionally you can set the X and Y positions of the dialog relative to its parent. The dialog to be embedded should be offset so that the dialog frame top appears as the tab frame bottom. This is of course a matter of personal preference and aesthetics.
In the sample there are two dialogs that have been created.
CDialog1 ion files Dialog1.h and Dialog1.cpp and
CDialog2 in Dialog2.h and Dialog2.cpp.
Now we have built the two dialogs to be embedded in the main dialog, we can create the main dialog.
We create a new Dialog class called
CEmbeddedDialogDlg implemented in two files, EmbeddedDialogDlg.h and EmbeddedDialogDlg.cpp. This is where the implementation of the embedded dialogs will go.
Having created the dialogs, we need to add includes for the two
CDialog classes we created earlier and add them as members to the
CEmbeddedDialogDlg class. These are the modeless dialogs that will be inserted into the CEmbeddedDialogDlg.h class.
Next, we insert a new tab control resource into the dialog resource for the
CEmbeddedDialog class and subclass this control, just to make it easier to work with. This is added as a member
m_ctlTab1 of the
The only function we need in the
CEmbeddedDialogDlg class is the
UpdateVisibleWindow method which shows the dialog associated with the current tab and hides all the other dialogs from being shown. When a user changes the tab in the tab control, because it is a windows common control this generates a
WM_NOTIFY message with a submessage of
TCN_SELCHANGE, indicating the current tab selection has changed. We can get the current tab selection using the
CTabCtrl::GetCurSel(). This is equivalent to the
TCM_GETCURSEL message that would be sent to a native control. MFC just makes it easier.
The final piece of the jigsaw is the
CEmbeddedDialogDlg::OnInitDialog member. This is the method that is first called after the dialog is first constructed but before it is displayed. This is where the major portion of the changes occur.
The first step in this method is to create the two tabs that we will be using.
m_ctlTab1.InsertItem(TCIF_TEXT, 0, _T("Dialog1"), 0,0,0,0);
m_ctlTab1.InsertItem(TCIF_TEXT, 1, _T("Dialog2"), 0,0,0,0);
Next, we create modeless instances of the two dialogs as indicated below.
Note that we make the two dialog instances' parents the
CEmbeddedDialogDlg dialog so all the offsets will be relative to the
Now, if you call
UpdateVisibleWindow() and run the project, you will see the project works and you can select different tabs as well as use the tab control to switch between dialogs. However you will see that the tab order appears to be incorrect because tabbing will take you to the tab control, then to the ok and cancel buttons and then to the embedded controls. Obviously this is incorrect and confusing, so we need to alter the tab order of the newly inserted controls so they appear to correctly tab after the tab control.
m_dlg2.SetWindowPos(GetDlgItem(IDC_TAB1), 0, 0, 0, 0,
m_dlg1.SetWindowPos(GetDlgItem(IDC_TAB1), 0, 0, 0, 0,
To do this we use the SetWindowPos function and tell the window where it should be in the tab order. Note that we have reversed the order of the
SetWindowPos calls, the first call is
CDialog2 and then the next call is to
CDialog1. This make the code easier to understand, there is no reason why other implementations could not be followed.
One final point to note that even though it is not in the code, from within
CEmbeddedDialogDlg::DoDataExchange you should call both the
Points of Interest
I was amazed at how easy it was to implement embedded dialogs once the
DS_CONTROL style is understood. The whole model integrates simply and easily within the Windows framework, giving the look and feel that shows its Microsoft's intended route to embedded dialogs.
Following the discussion on the thread I have shown how to add support for XP Themes.
To add theme support is a two step operation. The first step is to create a manifest file either embedded as a resource or a separate file with a yourapp.exe.manifest filename. If either are present and you are running XP then the application will take on the theme of the current XP theme.
The second step is to make the tabs appear the same color as the background. This is achieved by calling
EnableThemeDialogTexture with a flag of
ETDT_ENABLETAB. Obviously this executable will now only work on XP.
I have added two additional configurations in the .dsp file, one called Win32 XP Debug and Win32 XP Release. For a good article on theming see Using Windows XP Visual Styles on the Microsoft web site.
I make computers talk to other computers - well thats that I tell everyone I do. What I really do is connectivity and syncronization software for Windows Ce, PalmOS, Symbian on client devices and MFC/ATL/.NET on the Windows Servers.
I am also an expert with Microsoft Exchange and Databases and pretty good with Lotus Notes as well.