Preface
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.
ManageMore Demo
Introduction
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:
CManagedObject
CManageMore
CMMQueryDlg
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: CSubWork1
, CSubWork2
and 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 "ManageMore.h"
#include "SubWork1.h"
#include "SubWork2.h"
#include "SubWork3.h"
Create a data member of CManageMore
type in the main dialog class:
CManageMore m_MM;
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
- a
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.
Destroy the 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 GetManagedDialogPosition(char *chName)
.
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
The 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),
WS_EX_CLIENTEDGE,
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.
class CManagedObject
{
public:
CManagedObject();
~CManagedObject();
CDialog *pManagedDlg;
CString strName;
};
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
The 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),
LOWORD(WS_EX_TOOLWINDOW),
HIWORD(WS_EX_TOOLWINDOW),
1,
0, 0, 152, 17,
0, 0, 0,
8,
'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),
0, 0,
2, 2, 148, 80,
MM_IDC_COMBOBOX,
0xFFFF, 0x0085,
0,
0, 0
};
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.
void CMMQueryDlg::OnSelchangeComboNames()
{
CComboBox *pCB = (CComboBox *)GetDlgItem(MM_IDC_COMBOBOX);
int nSel;
if ( pCB )
{
nSel = pCB->GetCurSel();
if ( nSel != CB_ERR )
{
pCB->GetLBText(nSel, m_strSelection);
PostMessage(WM_CLOSE);
}
}
}
The 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 CMMQueryDlg
object.
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 CManageMore
object:
CMMQueryDlg dlg;
CManagedObject MD;
POSITION pos, posCrt;
CString strSel;
.
.
dlg.DoModal();
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);
break;
}
}
}
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:
CManagedObject MD;
CRect rcDlg;
int x, y;
.
.
MD.pManagedDlg->GetWindowRect(&rcDlg);
ScreenToClient(&rcDlg);
x = rcDlg.left;
y = rcDlg.top;
ScrollManagedArea(-x + 4, -y + 4);
Maximized Container Demo
The Problem
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.
The Solution
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:
CMaximizedContainerDlg
CContainerDlg
CElementDlg
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.
The 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 "ContainerDlg.h"
#include "ElementDlg.h"
Create data members for CContainerDlg
and the list of CElementDlg
instances in the main dialog class. Also add support for container and scrolling management:
CContainerDlg m_dlgContainer;
CList <CElementDlg CElementDlg*, CElementDlg*> m_listDialogs;
void EmptyContainer();
void FillContainer();
void ScrollHorizontally(short zDelta);
void ScrollVertically(short zDelta);
void SetupScrollbars();
void ResetScrollbars();
int m_nRows;
int m_nCols;
int m_nPaddingX;
int m_nPaddingY;
int m_nElementWidth;
int m_nElementHeight;
int m_nHorzInc;
int m_nVertInc;
int m_nVscrollMax;
int m_nHscrollMax;
int m_nVscrollPos;
int m_nHscrollPos;
Add code to OnInitDialog()
to maximize the main dialog, create the container dialog, and fill the container with element dialogs:
ShowWindow(SW_SHOWMAXIMIZED);
CRect rcMain;
m_dlgContainer.Create(IDD_DIALOG_CONTAINER, this);
GetClientRect(&rcMain);
rcMain.left += 100;
rcMain.top += 60;
rcMain.bottom -= 4;
rcMain.right -= 4;
m_dlgContainer.MoveWindow(&rcMain);
m_dlgContainer.ShowWindow(SW_SHOW);
FillContainer();
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)
return TRUE;
return CDialog::PreTranslateMessage(pMsg);
}
Override PostNcDestroy
and call EmptyContainer()
to free up resources:
void CMaximizedContainerDlg::PostNcDestroy()
{
EmptyContainer();
CDialog::PostNcDestroy();
}
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.
Conclusion
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.
History
- Created: September, 2005.