How to Create an Option Configuration Sheet Manager

Environment: Mozilla or Netscape Navigator

Introduction:

Check out the option (preferences) configuration dialogs of Mozilla or Netscape Navigator; it’s a tree or list control on the left and configuration sets on the right, according to choice in tree or list. I personally liked that organization from the very beginning.

The configuration is supposed to look like this:

Source: Mozilla 0.9.9

By using MFC, you can achieve something like this:

All controls on the right are actually not members of our main (dialog) window but are members of a child window which is displayed on top of the main dialog. This makes it very easy to transport data to and from the dialog fields and also very easy to display the controls and to hide them if the next configuration sheet is about to be displayed.

How to Create the Project

For details and complete source on the how-to, see the Example project PwOptions.

(Note: I use my own coding style and naming convention because I hate the Hungarian convention. I also define class attributes by a lower case “a” in front of the variable name to differntiate “my” attributes from the default added controls of MFC (which have “m_” as the header).)

Follow these steps:

  1. Create an option configuration dialog with a list or tree control on the left, insert a static control on this dialog’s right side, and define a recognizable name for it (overwrite the IDC_STATIC with a new value). Choose the size of the static control in the size you need your configuration sheets to be (later on). When displaying child windows or when moving this option dialog, we will compute the coordinates of the child windows and their position by this static. Define the static control as not visible.
  2. Define as many windows (dialogs) in your ResourceEditor as you need for your configuration (as you have configuration topics). The example uses three different configuration topics that are created (not shown) from within the OnInitDialog of the manager dialog window. Define each window as not visible.
    You don’t need to define the size of all child windows exactly the same; thus, we will compute the real coordinates automatically. You are more free here but you still need to check whether your controls do fit in the size of the static you have defined for your control dialog. I usually do it by trial-and-error.
    Your child configuration windows should not have a title bar or system menu.
  3. Define member variables for all of your configuration sheets at your manager dialog. (Note: if you have a lot of configuration topics to deal with, it is most probably more adequate to use an object array and create the windows on demand (on first usage).) Also, create a CStatic object control for your dummy coordinate static control.

      BOOL CPwOptionsDlg::OnInitDialog()
    {
        CDialog::OnInitDialog();
    ...
        SetIcon(m_hIcon, FALSE);    // Set small icon
    
    
        //Add as many tree/list items as you need to identify
        //your config sheets:
        mc_tree.InsertItem("Open sheet 1", 0, 0);
        mc_tree.InsertItem("Open sheet 2", 0, 0);
        mc_tree.InsertItem("Open sheet 3", 0, 0);
    
    
        //->STEP:
        //Create the child windows.
        //Note: You can create the child windows on demand, if you like.
        //I find it easier to do it at once when initializing the dialog.
        //If you have too many (possible) child windows, you may find it
        //easier to use a COject array.
    
        //Create the sheets as modeless inlay:
        aoSheet1.Create(IDD_SHEET_1, this);
        aoSheet2.Create(IDD_SHEET_2, this);
        aoSheet3.Create(IDD_SHEET_3, this);
    
        //Do NOT call "PositionChild" from here because the window is
        //not created/shown yet and the retrieved positions will be
        //different from the positions the window is actually shown!
    
        //I use an identifier to keep track of which window is currently
        //displayed.
        //This is especially useful if you use a CObject array for
        //your child windows.
        anCurrentChildWindow = 1;
    
        return TRUE;  //Return TRUE unless you set the focus to a control.
    }  //OnInitDialog
    
  4. Generate a method that is able to (re)compute the child window size and position.
  5. void CPwOptionsDlg::PositionChild()
    {
      CRect  mRec;
    
        //The sheet position should be right of the tree control; we use
        //the standard member (a CStatic) we defined at the position
        //which the upper, left corner should be identical to the inlay
        //window position. (We could use the tree control to compute
        //the position, but it is easier to reuse a dummy window's
        //coordinates.)
        mc_dummy.GetWindowRect(&mRec);
    
        //Recompute coordinates relative to parent window.
        //Note: You need to define the style of the child windows as "Child".
        //If you leave it as "popup", you will need to remove this
        //ScreenToClient recomputing.
        //But, if you do so, you will see the parent window affected by
        //the title bar's getting disabled once the "child" window has
        //the focus.
        ScreenToClient(&mRec);
    
        //Now move the child window to the correct position.
        //Note: If you do not want your child window to become the
        //same size as your dummy control, you need to use this code.
        aoSheet1.MoveWindow(mRec.left,
                            mRec.top,
                            mRec.Width(),
                            mRec.Height(),
                            TRUE);
    
        //Now move the child window to the correct position.
        aoSheet2.MoveWindow(mRec.left,
                            mRec.top,
                            mRec.Width(),
                            mRec.Height(),
                            TRUE);
    
        //Now move the child window to the correct position.
        aoSheet3.MoveWindow(mRec.left,
                            mRec.top,
                            mRec.Width(),
                            mRec.Height(),
                            TRUE);
    }  //PositionChild
    
  6. Add a message handler for OnMove and OnShowWindow on your configuration manager dialog. In both, call the function that is computing the position of the child windows according to their current size and status.
  7. void CPwOptionsDlg::OnMove(int x, int y)
    {
        //Call super.
        CDialog::OnMove(x, y);
    
        //Avoid the call on window pre-creation (which is also calling OnMove).
        if ( IsWindowVisible() )
        {
           PositionChild();
        }
    }  //OnMove
    
    void CPwOptionsDlg::OnShowWindow(BOOL bShow, UINT nStatus)
    {
        CDialog::OnShowWindow(bShow, nStatus);
    
        //No need to recompute if the window
        //is going to be hidden or minimized.
        if ( bShow )
        {
            PositionChild();
        }
    }
    
  8. Generate a display method that is able to determine which window should be shown.
  9. void CPwOptionsDlg::ShowChild(  int   nToShow
      //in: the window pos to show
                                    )
    {
        //First hide them all.
        //This is actually not required; try it by removing the
        //follwing three lines of code.
        aoSheet1.ShowWindow(SW_HIDE);
        aoSheet2.ShowWindow(SW_HIDE);
        aoSheet3.ShowWindow(SW_HIDE);
    
        //If you have a lot of configuration sheets, this code is
        //not appropriate.
        //Generate an object array and use "nToShow" as an index
        //in your array.
    
        switch ( nToShow )
        {
            default:
                //Declare 1 as my default window.
                aoSheet1.ShowWindow(SW_SHOWNORMAL);
                break;
    
            case 2:
                //Declare 2 as my default window.
                aoSheet2.ShowWindow(SW_SHOWNORMAL);
                break;
    
            case 3:
                //Declare 3 as my default window.
                aoSheet3.ShowWindow(SW_SHOWNORMAL);
                break;
        }  //switch
    }  //ShowChild
    
  10. Add a handler for your tree or list control. I recommend that you use a single left mouse click as an activation signal; you may find it more appropriate to use a double click.
  11. void CPwOptionsDlg::OnSelchangedTree1(NMHDR* pNMHDR, LRESULT* pResult)
    {
      HTREEITEM hBase;
    
        hBase = mc_tree.GetSelectedItem();
    
        if ( stricmp(mc_tree.GetItemText(hBase), "Open sheet 1") == 0 )
        {
            ShowChild(1);
        }
        else
    
        if ( stricmp(mc_tree.GetItemText(hBase), "Open sheet 2") == 0 )
        {
            ShowChild(2);
        }
        else
    
        if ( stricmp(mc_tree.GetItemText(hBase), "Open sheet 3") == 0 )
        {
            ShowChild(3);
        }
    
        *pResult = 0;
    }
    
  12. This is it. In your child windows you will handle all data and controls as usual. Thus, the windows are available through the class attributes (see #3). It is quite easy to acquire all data by calling UpdateData on each of the child windows as a reaction to a “OK” or “Apply” button at your configuration manager dialog window.

Addition Information

  • See project “PwOptions” for sources.
  • You can select any type of window style; try the code in PwOptions by using DialogFrame. Some like that style more than no-frame windows.
  • I recommend that you overwrite OnCancel and OnOk in your child (configuration sheet) windows; otherwise, the shortcut references (ENTER, ESC) would be handled by the child window if it has currently the focus and by the main manager window if the child does not have the cursor. I found that a bit distracting; therefore, I switch it off.

Downloads

Download demo project – 32 Kb

More by Author

Get the Free Newsletter!

Subscribe to Developer Insider for top news, trends & analysis

Must Read