Netscape 4.x Preferences Dialog

-->

When designing complex dialog boxes, one uses a property sheet whose functionality is encapsulated by MFC class CPropertySheet. However, there are several cases where alternative approach (dialog box similar to Netscape 4.x Preferences dialog box) is more flexible and user friendly.

  • When there are a lot of property pages in which case it is difficult to navigate thru the pages.
  • When there are several different sets of property pages whose visibility depends on the state of the application.
  • When there is a need for nested property pages.
Of course, there is always a possibility to derive a class from CPropertySheet and adapt it for the task at hand. However, this requires in depth knowledge of MFC implementation and also browsing the MFC source code which is not a trivial thing to do.

Basically, tree control contains a list names where each name is associated with a property page. This list may be (1) a simple list with no root, (2) list with a root which indicates the purpose of the visible set of property pages and (3) a list where one or more items contain one or more child items. When the user selects an item in a list, application automatically makes a property page associated with this item visible while all the other pages are invisible. It is also possible to replace the contents of the tree control with different lists according to the state of the application (for example, different list should be displayed for application configuration).

Implementation

Implementation of this dialog is split into several steps:

Step 1:

Design a dialog box. Put whatever you need on it. Also place a tree control and a picture control which is actually a placeholder for property pages. Let's assume that the ID of the tree control is IDC_TREE and the ID of the picture control is IDC_PLACEHOLDER. Picture control should be invisible.

Step 2:

Design a separate dialog for each of the property pages. Put whatever controls you need on each dialog. Each dialog shoould have the following properties:
  • No titlebar
  • Style Child
  • Disabled
  • No border
  • Invisible
Using class wizard, create a class for each property page dialog. Base class must be CDialog. Each property page must override virtual functions CDialog::OnCancel() and CDialog::OnOK() since their default implementation will close the dialog.

Step 3:

Create the following structure which describes all the data associated with a property page.
struct TItem {
  int OptionId;        // Unique identification of this page
  LPCTSTR Name;        // Property page name displayed in a tree control
  int DialogID;        // Resource ID of the property page dialog
  void *Handle;        // HTREEITEM
  void *Parent;        // HTREEITEM
  CDialog *Dialog;     // Pointer to property page dialog
  BOOL Created;        // Is the dialog create or not
  long HelpID;         // Help ID for a property page
};
Within your source file, create a constant array of these structures and initialize it.
static TItem Options[] = {
    { 1, "General settings",    IDD_PROPPAGE1, NULL, NULL, NULL, FALSE, HELP_GENERAL },
    { 2, "Login settings",      IDD_PROPPAGE2, NULL, NULL, NULL, FALSE, HELP_LOGIN },
    { 3, "Connection settings", IDD_PROPPAGE3, NULL, NULL, NULL, FALSE, HELP_CONNECTION },
    { 4, "About",               IDD_PROPPAGE4, NULL, NULL, NULL, FALSE, HELP_ABOUT },
};
You need also to implement the following function:
TItem *TMainDialog::insertOption(HTREEITEM hroot, int option, BOOL select)
{
    TItem *ptr = &(Options[option]);
    HTREEITEM handle;
    handle = GetDlgItem(IDC_TREE)->InsertItem(ptr->Name, hroot);
    ptr->Handle = (void *)handle;
    ptr->Parent = (void *)hroot;
    GetDlgItem(IDC_TREE)->SetItemData(handle,(DWORD)ptr);
    if (select)
        GetDlgItem(IDC_TREE)->SelectItem(handle);
    CDialog *dialog = createDialog(ptr->OptionId,ptr->DialogID);
    dialog->Create(ptr->DialogID,this);
    CRect rect;
    GetDlgItem(IDC_PLACEHOLDER)->GetWindowRect(&rect);
    ScreenToClient(&rect);
    dialog->SetWindowPos(NULL, rect.left, rect.top, 0, 0, 
      SWP_NOZORDER | SWP_NOSIZE | SWP_NOACTIVATE );
    dialog->EnableWindow(TRUE);
    ptr->Dialog = dialog;
    ptr->Created = TRUE;
    return ptr;
}// TPage1... are classes associated with property pages.
CDialog *TMainDialog::createDialog(int optionId, int dialogId)
{
    CDialog *dialog = NULL;
    switch (optionId) {
    case 1: dialog = new TPage1; break;
    case 2: dialog = new TPage2; break;
    case 3: dialog = new TPage3; break;
    case 4: dialog = new TPage4; break;
    };
    ASSERT(dialog != NULL);
    return dialog;
}
These two functions are responsible for property page creation. Notice that pointer to TItem structure is associated with an item in a tree control.

Step 4:

You need to fill the tree control and display the initial property page. This is done in OnOnitDialog() handler of the main dialog.
...
hroot = GetDlgItem(IDC_TREE)->InsertItem("Configuration");
insertOption(hroot,1,TRUE);    // Make first page visible
insertOption(hroot,2);
insertOption(hroot,3);
insertOption(hroot,4);
...
After this code, the tree control is filled, property pages are created and the first one is visible.

Step 5:

When the user selects another item in a tree control, you need to deactivate the currently visible property page and activate a new one (the one associated with the selected tree item). In order to perform this, following two functions are needed:
void TMainDialog::activateOption(TItem *item)
{
    ASSERT(item != NULL);
    Option = item;    // see below
    CDialog *dialog = Option->Dialog;
    ASSERT(dialog != NULL);
    dialog->ShowWindow(SW_SHOW);
    dialog->InvalidateRect(NULL);
    dialog->UpdateWindow();
}

void TMainDialog::deactivateOption(TItem *item)
{
    ASSERT(item != NULL);
    CDialog *dialog = item->Dialog;
    ASSERT(dialog != NULL);
    dialog->ShowWindow(SW_HIDE);
}
Option is a global variable of type TItem* which contains the currently selected item. In deactivateOption, visible property page is hidden. In activateOption, new property page is shown and updated (redrawn). Pointer to the new item structure is saved to global Option variable. These two functions are used in combination from the tree control handler triggered when the selection is changed (wither by the mouse or the keyboard). The following code handles the property page changes:
HTREEITEM handle = GetDlgItem(IDC_TREE)->GetSelectedItem();
if (handle != NULL) {
    TItem *item = (TItem*)GetDlgItem(IDC_TREE)->GetItemData(handle);
    if (item && (item != Option)) {
        deactivateOption(Option);
        activateOption(item);
    }
}

That's all. In a real world implementation, minor additions to the presented code are welcomed:

  • Create a base class for all property pages and add handlers executed when the page becomes active/inactive.
  • Add help for each page - trivial task since currently active page is accessable via Option variable and it contains a help ID for the page.
  • Presented code will create all the property pages and then only modify the visible flag for each page. It is possible to create/destroy pages when they are activated/deactivated. Which solution is appropriate depends on the application.

Conclusion

This is a very flexible solution for complex dialog boxes. It offers complete control of the creation/destruction of all the pages and allows dynamic modification of available property pages (not presented here). Since the basis is a plain dialog, it is possible to organize it anyway you like. The latest version of this article can be downloaded from my home page: www.scasoftware.com.



Comments

  • Please attach the demo code

    Posted by Legacy on 02/27/2003 12:00am

    Originally posted by: Juli

    Hi,
    It'll realy help if you'll send me the demo code.
    Thanks a lot.
    Juli

    Reply
  • Ooops, most probably I re-invented the wheel:-)

    Posted by Legacy on 07/29/2002 12:00am

    Originally posted by: Christopher Frank

    Onk!

    Sorry man.

    Was searching for the article I have contributed a few weeks ago and found it, but also found yours which does have absolutely the same content (but was first).

    Seems that the codeguru guys don't know exactly what is on the server anymore:-)

    Just wanted to tell that I don't want to compete neither have stolen your idea.

    Anyhow, I like the idea (but I like my implementation more than yours of course;-)

    Tot ziens
    Atlana

    Reply
  • Child dialog is not Tab Stop

    Posted by Legacy on 01/09/2002 12:00am

    Originally posted by: Gary

    Hi,
    
    

    I'm using this technique to implement my own wizard-style dialog. However, it seems you can't TAB to the fields in the child dialogs. Pressing Tab only switches between the controls on the main dialog...

    Am I missing something?

    Thanks!

    Reply
  • pls send the entire code

    Posted by Legacy on 05/31/2001 12:00am

    Originally posted by: Radha

    Sir,
    Pls give me the entire source code.IF u give to me u make a big help to us.pls give me reply.
    thank u
    Radha.

    Reply
  • Please, give the entire project.

    Posted by Legacy on 07/26/1999 12:00am

    Originally posted by: David Betz

    Please, give the entire project.

    Reply
  • Dialog as control

    Posted by Legacy on 07/14/1999 12:00am

    Originally posted by: Yorik

    you can simple add to property dialogs style 'control'
    and you will don't need override virtual functions OnOK () and OnCancel ().

    Reply
  • What the McFuck?

    Posted by Legacy on 05/04/1999 12:00am

    Originally posted by: Pejare LaFonte

    Where is the Botto demo source?

    Reply
Leave a Comment
  • Your email address will not be published. All fields are required.

Top White Papers and Webcasts

  • Protecting business operations means shifting the priorities around availability from disaster recovery to business continuity. Enterprises are shifting their focus from recovery from a disaster to preventing the disaster in the first place. With this change in mindset, disaster recovery is no longer the first line of defense; the organizations with a smarter business continuity practice are less impacted when disasters strike. This SmartSelect will provide insight to help guide your enterprise toward better …

  • Savvy enterprises are discovering that the cloud holds the power to transform IT processes and support business objectives. IT departments can use the cloud to redefine the continuum of development and operations—a process that is becoming known as DevOps. Download the Executive Brief DevOps: Why IT Operations Managers Should Care About the Cloud—prepared by Frost & Sullivan and sponsored by IBM—to learn how IBM SmartCloud Application services provide a robust platform that streamlines …

Most Popular Programming Stories

More for Developers

Latest Developer Headlines

RSS Feeds