This article contains two demos. Both utilize a concept proposed by Mircea Puiu, but the implementations are different. I had a business problem to solve, and Mircea suggested this idea of a scrollable multi-dialog container, and took the time to whip up a barebones demo for me (thanks Mircea!). This was enough for me to run with to solve my problem.
In parallel, Mircea developed the ManageMore class suite, a more complete demonstration of the concept, with more features, suitable for a CodeProject article. In the end we had two implementations each with their own strengths. So we present them both here. It should be possible for the readers to combine and utilize the strengths of each to suit their own needs.
See the Maximized Container Demo below.
Gathering information from a business network is more than marketing approaches and the philosophy behind, and sometimes eats important time resources when it comes to monitoring different activity areas, even when the monitored items are located on the same floor.
Common ways of dealing with the topic range from server-client architectures for complex projects down to using multi-tab or property pages for simple, dedicated applications.
Businesses grow while a great deal of the supporting software gets old. Keeping the things on course with no further software development leads to running more applications on a workstation. "So what?" will ask some people, that is what Windows is good for, isn't it? Right, but what about getting the big picture?
Monitoring data of the same type coming from different sources and taking "just-in-time" decisions (just to paraphrase our .NET fathers) is very well served by plenty of database approaches. Are they cost effective every time, especially when speaking of small businesses? What about just graphically "panning" the data and letting your experienced eye see the solution?
Imagine you have developed a bunch of software tools necessary to create, maintain and reuse data related to some project, and you need to run those programs in parallel on your machine. As the technical areas involved may range from mechanical design to pure computations and material resource management, you would like to have your tools integrated into some wonderful, big and fancy application dealing in a unitary way with your data. But you have neither the time to develop such software, nor the money to buy or outsource it. Probably you would be more than satisfied to find a way of just using the already developed resources of your tools and making them come and go over the screen, obedient to your mouse wheel or listening to events.
All that made a good reason for developing the ManageMore class suite to help developers in bringing together the graphical resources belonging to different applications and making them share the client area of a single dialog based application.
Working with the class set
The ManageMore class suite consists in fact of three classes:
Let us assume we are thinking of three different applications (the number itself has no importance). The idea is to use the dialog based graphical interface of each one within a single dialog based application, where they share a limited allocated display surface (i.e. the client area of the main window).
Each of those interfaces is described by a dialog template resource and its associated class. Let those classes be:
CSubWork3. Each of them may be used to create one or more instances. Such an instance is seen as a managed dialog object within a dialog base container responsible for panning the objects within the desired display area.
Change the style of your managed dialogs to
WS_CHILD, remove their title bar and system menu if any, and dismiss their
OnCancel action (to prevent the killing of the managed dialogs when pressing ESC.
Include the header files for ManageMore and your
CSubWorkX classes in the header file of your main dialog:
#include <span class="code-string">"ManageMore.h"</span>
Create a data member of
CManageMore type in the main dialog class:
Call repeatedly the
m_MM.AddManagedDialog() function within
OnInitDialog() to add your dialogs to the list of managed dialogs maintained by
m_MM. The function takes as parameters:
m_MM.AddManagedDialog(new CSubWorkX(), "Name", IDD_DIALOG_SUBSPACE_X, ptAt);
- a pointer to the instance of the managed dialog
- a name identifying the managed instance
- the dialog resource ID
CPoint object for the top-left position within the container
Make sure you use different names for different managed dialog instances, as those names are used by
ManageMore to retrieve and bring in to view the dialogs.
The function returns the size and position of the added dialog within the container.
m_MM object within the
PostNcDestroy() function of the main dialog's class by calling
m_MM.Destroy() in order to avoid memory leaks.
All you have to do now is to call the
m_MM.ScrollManagedArea() function whenever you want to pan the managed dialog interfaces. The handler related to the mouse wheel event was chosen to implement this functionality for the demo:
- mouse wheel: pan vertically
- mouse wheel + CTRL key: pan horizontally
Any of the managed dialogs can be brought in view either directly by invoking the
m_MM.BringDialogInView(char *dialogname) function, with the name of the dialog passed as parameter, or by posting / sending the
WM_MM_BRING_IN_VIEW message to
m_MM with the
POSITION of the desired dialog in the managed list as parameter. This position in the list can be obtained through a call to
The list of the managed dialogs can be made available to the user for the "bring-in-view" functionality, by calling
m_MM.SelectBringDialogInView(). This pops up a combobox interface for dialog selection and brings in to view the selected dialog automatically.
Should you need a pointer to one of the managed dialogs, call
GetManagedDialog(char *chName) with the name of the dialog passed as parameter.
The code behind
The CManageMore container class
CDialog based container class is created in two steps: first the constructor is called and then the
Create(CDialog *pMainDlg, CRect rcArea) function is invoked. A local embedded template:
static WORD ContainerTemplate =
LOWORD(WS_BORDER | WS_CHILD),
HIWORD(WS_BORDER | WS_CHILD),
0, 0, 0, 0, 0
is used to create a modeless dialog box as the layout for the managed dialogs. The size of the container is adjusted according to the
rcArea parameter used to define the part of the client area of the main dialog to be used for the displaying purpose.
The CManagedObject supporting class
The managed dialogs are manipulated through a
CList collection of
CManagedObject objects, each of them encapsulating a pointer to a modeless dialog box created from a template resource and the name of the dialog.
Panning the dialogs
The panning functionality is a very straight process: a call to the
ScrollWindow() function inherited by
CManageMore from its
CDialog base class.
The CMMQueryDlg query interface
CMMQueryDlg is derived from
CDialog, too. Within its constructor, the following embedded template:
static WORD Template =
LOWORD (WS_POPUP | WS_CAPTION | WS_SYSMENU | DS_3DLOOK | DS_SETFONT),
HIWORD (WS_POPUP | WS_CAPTION | WS_SYSMENU | DS_3DLOOK | DS_SETFONT),
0, 0, 152, 17,
0, 0, 0,
'M','S',' ','S','a','n','s',' ','S','e','r','i','f', 0, 0,
LOWORD (WS_CHILD | WS_VISIBLE | WS_TABSTOP |
WS_VSCROLL | CBS_DROPDOWNLIST | CBS_SORT),
HIWORD (WS_CHILD | WS_VISIBLE | WS_TABSTOP |
WS_VSCROLL | CBS_DROPDOWNLIST | CBS_SORT),
2, 2, 148, 80,
is used to indirectly create a modal dialog box hosting a
CComboBox control populated with the names of the currently managed dialogs.
On the event of selecting a name from the drop-down list, the selection is stored in the
CString m_strSelection data member of the
CMMQueryDlg object, and a
WM_CLOSE message is posted, so that the query dialog closes automatically after selecting a name.
CComboBox *pCB = (CComboBox *)GetDlgItem(MM_IDC_COMBOBOX);
if ( pCB )
nSel = pCB->GetCurSel();
if ( nSel != CB_ERR )
CMMQueryDlg modal dialog box is invoked within the
CManageMore::SelectBringDialogInView() function. The combobox of the pop-up interface can be initialized a priori with a managed dialog name by directly storing that name within the
CString m_strPreselection member of the
In case a selection was made, the execution is continued with the posting of the
WM_MM_BRING_IN_VIEW message to the queue of the
POSITION pos, posCrt;
strSel = dlg.m_strSelection;
if ( strSel != "" )
pos = m_listDialogs.GetHeadPosition();
while ( pos )
posCrt = pos;
MD = m_listDialogs.GetNext(pos);
if ( MD.strName == strSel )
PostMessage(WM_MM_BRING_IN_VIEW, 0, (LPARAM)posCrt);
As a result, the top-left position of the window rectangle of the selected object is used to scroll the managed area so that the desired dialog is brought in view:
int x, y;
x = rcDlg.left;
y = rcDlg.top;
ScrollManagedArea(-x + 4, -y + 4);
Maximized Container Demo
I have an application that runs on many stations in parallel, all reporting back to a separate centralized application. A manager uses the central application to (among other things) view the different station operations in real time. Until now, the manager either had to view a single station at a time in a dialog, or view them in a list control using Report view. The problem with representing each station as a single line in a list is that it cannot easily or efficiently give you the functionality and visual aspects afforded by a dialog.
Ideally, the manager would like to take full advantage of his monitor's resolution, i.e., see as many stations as possible at a glance. The list of dialogs also must be easily and intuitively panned. Realize that the monitor resolution and number of stations are variable.
What I needed, conceptually at least, was a list control using Icon view, only with dialogs instead of icons. Also, the control needed to size automatically to the maximum allowable area given the monitor's resolution.
Mircea's scrollable multi-dialog container was the ticket.
Some key differences in the Maximized Container demo:
- The main app dialog is forced to be maximized.
- Implements scrollbars, and limits scrolling as required.
- Contained dialogs are homogenous.
- Number of columns is configurable.
Working with the classes
The Maximized Container demo uses:
I've used a manufacturing shop floor as the basis for this demo. Thinking of the container as a list control, the elements of the list are
CElementDlg instances. Referred to above as stations, each element dialog depicts a machine on the shop floor.
CElementDlg dialog has
WS_CHILD style, no title bar or system menu, and dismisses
OnCancel to prevent the killing of the contained dialogs when pressing ESC.
Add these header files to the header file of your main dialog:
#include <span class="code-string">"ContainerDlg.h"</span>
Create data members for
CContainerDlg and the list of
CElementDlg instances in the main dialog class. Also add support for container and scrolling management:
CList <CElementDlg CElementDlg*, CElementDlg*> m_listDialogs;
void ScrollHorizontally(short zDelta);
void ScrollVertically(short zDelta);
Add code to
OnInitDialog() to maximize the main dialog, create the container dialog, and fill the container with element dialogs:
rcMain.left += 100;
rcMain.top += 60;
rcMain.bottom -= 4;
rcMain.right -= 4;
This implementation requires the main dialog to remain maximized. So override
PreTranslateMessage in order to trap the titlebar double-click message and prevent the window from being restored down:
BOOL CMaximizedContainerDlg::PreTranslateMessage(MSG* pMsg)
if (pMsg->message == WM_NCLBUTTONDBLCLK)
PostNcDestroy and call
EmptyContainer() to free up resources:
Implement the scrolling prototypes as in the demo to handle scrolling appropriately.
The main dialog's
FillContainer() function creates the elements. The number of elements can be set in the demo, and I simply loop through the number of elements creating a
CElementDlg instance for each. I pass the 1-based element sequence number into each
CElementDlg::SetData() call so that they can initialize and show varying demo data.
CElementDlg::SetData() is then called (with 0) from a timer for each instance at varying intervals in order to simulate real-time activity.
CElementDlg::SetData() in a real application:
In a real application, the number of elements must be determined and a
CElementDlg instance created for each.
CElementDlg::SetData() will contain the appropriate business logic for the application. This function would be used in 1 of 2 ways:
- Pulling data from elements: Called on a timer to solicit data from the element.
- Pushing data from elements: Element's responsibility to call this function when it needs to.
I've used a manufacturing shop floor as the basis for this demo. However, this concept fits well for any application that monitors multiple stations. For example, point of sale applications where elements could be cash registers, gas pumps, kiosks, etc; banking applications that monitor ATM machines; pre-sort mail houses that monitor mailing machines and inserters; and so on.
I've also dressed up the element dialogs quite a bit in my real application. There are lots of articles around that show you how to work with color, bitmaps, and transparency on various controls (buttons, static text, etc.) that allow you to spiff up your dialogs and provide lots of visual clues (managers like pictures) to convey information on the real-time operations of your elements.
- Created: September, 2005.