Dialog Switcher Using a Tree Control

Environment: Visual C++ 6

So you want to use the new style of dialog where you pick an option from a tree on the left and the appropriate form shows up on the right, but you have no idea where to start.

The easiest way to set up the right side is to create a dialog for each form that you might use there. The trick is making it seamless with the overall dialog, and switching out the dialogs (the forms) that you create for that right-hand side.

Step 1: Create the Main Dialog.

Easy enough. Use the Resource Editor to create the dialog, and stretch it out a bit. Add a Tree View control to the left-hand side, making it tall and skinny so that it's readable but there's plenty of room for whatever is going to go on the right. Use Class Wizard to add a Member Variable for the control.

Step 2: Create the Right Side forms.

In the Resource Editor, select Insert>>Resource (or hit ctrl-R), select Dialog, and click New. Design as you please. Run Class Wizard (ctrl-W) to create a class for the dialog. At some point set these properties on the dialog itself:

  • Style must be Child (this is on the Styles tab)
  • Border (Styles tab again) should be none, but you can play with this. Keep in mind that if you do give it a border, you'll probably want to make sure all the dialogs you create for the right side are the same size so that the border doesn't shift about.
  • Speaking of size, you'll want to size the right-hand dialogs carefully so that they'll fit in the Main Dialog with room left over for the tree control. There are rulers at the top and left when designing dialogs to help you accomplish this.
Create as many right-side dialogs as you want.

Step 3: Initialize the dialogs

Ah, here we get to some code! In the Dlg class for your application, add include's for each of your right-side dialog classes. For ease (and because it's what they are), I'm going to call those Child Dialogs. Add a member variable to your Dlg header for each of your Child Dialogs. In the OnInitDialog function of your Dlg class, Create and Hide the Child Dialogs:

// Create the Child Dialogs
m_dlgOption1.Create(IDD_DIALOG_OPTION1, NULL);
m_dlgOption1.ShowWindow(SW_HIDE);
m_dlgOption2.Create(IDD_DIALOG_OPTION2, NULL);
m_dlgOption2.ShowWindow(SW_HIDE);
m_iActiveOption = 1;

// Create the tree.
HTREEITEM hItem;
hItem = m_tMyTree.InsertItem("Option 1", 
                             TVI_ROOT,
                             TVI_LAST);
m_tMyTree.SelectItem(hItem);
m_hCurrentItem = hItem;
m_tMyTree.InsertItem("Option 2", 
                     TVI_ROOT,
                     TVI_LAST);
m_tMyTree.SetFocus();

return FALSE;  // returning FALSE so the focus is
               //   set to the tree.
}

Note that I have also created a member variable m_hCurrentItem to track the currently selected item. This is of type HTREEITEM.

Step 4: Handle a Change In The Tree's Selection

Using Class Wizard, create a handler for your tree control to handle the TVN_SELCHANGED message. The function should look something like this:

void CDialogSwitcherByTreeDlg::OnSelchangedTree1(NMHDR* pNMHDR,
                                                 LRESULT* pResult) 
{
  NM_TREEVIEW*  pNMTreeView = (NM_TREEVIEW*)pNMHDR;
  HTREEITEM     hItem;
  CString       szOptionChosen;

  hItem = m_tMyTree.GetSelectedItem(); // Get a Handle to the 
                                       // newly selected item.
  m_hCurrentItem = hItem;  // Set the current item to the one
                          // just selected.
  szOptionChosen = m_tMyTree.GetItemText(hItem); // Get the text of 
                                                 // the chosen option.
  if (szOptionChosen == "Option 1") // If option 1 was chosen...
  {
    m_iActiveOption = 1;  // Set the active option to 1, 
                          //   hide option 2, repaint.
    m_dlgOption2.ShowWindow(SW_HIDE);
    OnPaint();
  }
  else if (szOptionChosen == "Option 2") // If option 2 chosen...
  {
    m_iActiveOption = 2;  // Set the active option to 2, 
                          //  hide option 1, repaint.
    m_dlgOption1.ShowWindow(SW_HIDE);
    OnPaint();
  }

  *pResult = 0;
}

Of course, my example here uses only 2 options and it's pretty simple to set up an if-else if structure. For more than a few options you might want to get creative and figure a more efficient way of doing changing the current selection.

Notice that I call OnPaint() as soon as my option is chosen. This is because I have put some code into the OnPaint() function to complete the transition from one Child Dialog to another:

RECT  rPositionMain;   // Main Dialog rectangle
RECT  rPositionChild;  // Child Dialog rectangle
int   iXDiff = 0;      // Left-Right change
int   iYDiff = 0;      // Up-Down change

GetWindowRect(&rPositionMain); // Get the rectangle 
                               // the dialog occupies
switch (m_iActiveOption) // Based on the button pushed...
{
case 1:
  m_dlgOption1.GetWindowRect(&rPositionChild); // Get child
                                                   // rectangle
  if (rPositionChild.top != 0) // If top side of child
                            // is not correctly positioned...
  {
    iYDiff = 0 - rPositionChild.top; // ...move top and bottom
                               // sides of child appropriately
    rPositionChild.top += iYDiff;
    rPositionChild.bottom += iYDiff;
  }
  if (rPositionChild.left != 150) // If left side of child
                         // is not correctly positioned...
  {
    iXDiff = 150 - rPositionChild.left; // ...move left and right
                                  // sides of child appropriately
    rPositionChild.left += iXDiff;
    rPositionChild.right += iXDiff;
  }
  if (iXDiff != 0 || iYDiff != 0)  // If we moved  child...
    m_dlgOption1.MoveWindow(&rPositionChild,
                            TRUE); // ...make the move!

  m_dlgOption1.ShowWindow(SW_SHOW); // Show the child dialog
  break;
case 2:
  m_dlgOption2.GetWindowRect(&rPositionChild); // Get child
                                                   // rectangle
  if (rPositionChild.top != 0) // If top side of child is
                               // not correctly positioned...
  {
    iYDiff = 0 - rPositionChild.top; // ...move top and bottom
                               // sides of child appropriately
    rPositionChild.top += iYDiff;
    rPositionChild.bottom += iYDiff;
  }
  if (rPositionChild.left != 150) // If left side of child 
                            // is not correctly positioned...
  {
    iXDiff = 150 - rPositionChild.left; // ...move left and right
                                    // sides of child appropriately
    rPositionChild.left += iXDiff;
    rPositionChild.right += iXDiff;
  }
  if (iXDiff != 0 || iYDiff != 0)  // If we moved the child...
    m_dlgOption2.MoveWindow(&rPositionChild,
                            TRUE);  // ...make the move!
  m_dlgOption2.ShowWindow(SW_SHOW); // Show the child dialog
  break;
}

// Bold the current item, turn Bold off for the rest
//  of the tree items.
HTREEITEM hItem = m_tMyTree.GetFirstVisibleItem();
while (hItem != NULL)
{
  if (hItem == m_hCurrentItem)
    m_tMyTree.SetItemState( hItem, TVIS_BOLD, TVIS_BOLD );
  else
    m_tMyTree.SetItemState( hItem, 0, TVIS_BOLD );
  hItem = m_tMyTree.GetNextVisibleItem(hItem);
}

Again, if you have more than a very few options you might want to work out a more graceful way of parsing them besides if-else if.

The important part of what I do in OnPaint() is moving the Child Dialog around. Since we set the style of each of the Child Dialogs to be Child, the coordinates used will be relative to their parent window, which is our main dialog. Initially, these coordinates are going to be wrong (Windows sets them relative to the screen), but after one trip through OnPaint() they should be fine. Theoretically, the window positions should only be adjusted once, but it's better to be safe than sorry.

Note that I offset the left side of the Child Dialogs by 150 pixels to allow room for the tree control. Depending on the width of your tree control, you may have to adjust this.



So there you have it: A tree control that changes the forms that show up on the dialog. Since each of the Child Dialogs are their own classes, the Main Dialog doesn't have to worry about what happens in them.

Downloads

Download demo project - 14 Kb


Comments

  • Finally Improved

    Posted by cjpm100 on 04/03/2005 07:56pm

    4 Years On: An excellent article by Andrew Walker. I had to make some modifications to make it work for me tho :)) This code works fine IF you have ONLY one dialog in your project. Calling CSettingsDlg from a parent dialog using this code, overwrites the dialog resource of the parent window with the (correct) dialog for Option 1/2 etc.. 1. You can safely remove all lines containing rPositionMain including initialisation, as it is not actually used anywhere. 2. To get this code working in a multi-dialog application, I documented my steps here: http://www.chrismawer.netfirms.com/code/ctreectrl-dialogs.html Comments welcome.

    Reply
  • Great idea! but improvable

    Posted by Legacy on 11/21/2001 12:00am

    Originally posted by: SHILIANG ZHAO

    You are really innovative to teach me this,
    
    VC is a field still new to me.

    But your code can be improved in that same
    code structures used twice and if the options
    increase to a large number the same code
    structures will use a large number times.

    So the code segment to locate child-dialog
    can be abbreviated.

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

Top White Papers and Webcasts

  • You probably have several goals for your patient portal of choice. Is "community" one of them? With a bevy of vendors offering portal solutions, it can be challenging for a hospital to know where to start. Fortunately, YourCareCommunity helps ease the decision-making process. Read this white paper to learn more. "3 Ways Clinicians can Leverage a Patient Portal to Craft a Healthcare Community" is a published document owned by www.medhost.com

  • The latest release of SugarCRM's flagship product gives users new tools to build extraordinary customer relationships. Read an in-depth analysis of SugarCRM's enhanced ability to help companies execute their customer-facing initiatives from Ovum, a leading technology research firm.

Most Popular Programming Stories

More for Developers

Latest Developer Headlines

RSS Feeds