Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C++

Multi-dialog management through client area sharing

4.95/5 (30 votes)
30 Sep 2005CPOL10 min read 1   1.7K  
An article on multi-dialog management through client area sharing.

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

Sample Image

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.

The container

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).

Bring in view

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.

Select

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,
 
     // DLGTEMPLATE extra data
     0, 0, 0,
     8,
     'M','S',' ','S','a','n','s',' ','S','e','r','i','f', 0, 0,

     // DLGITEMTEMPLATE for the combobox
     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, // class ordinal for combobox
     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); // close the query interface
        }
    }
}

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

Sample Image

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:

// for this demo we require the main app dialog to be maximized
ShowWindow(SW_SHOWMAXIMIZED);

// create the container dialog
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);

// fill the container dialog with the element dialogs
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) 
{
   // force this dialog to stay maximized by ignoring WM_NCLBUTTONDBLCLK
   if (pMsg->message == WM_NCLBUTTONDBLCLK)
      return TRUE;   // message handled (ignored actually)
   
   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.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)