Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

WTL Splitters and Pane Containers

0.00/5 (No votes)
9 Jun 2002 1  
The basics of using WTL's CSplitterWindow and CPaneContainer controls to create a multipane application

WTL Splitters and Panes Image

Introduction

This article describes the basic use of WTL's CSplitterWindow as an application window divider and CPaneContainer as a host for child windows. The included demonstration project is 3-pane SDI application using vertical and horizontal splitters. Each splitter pane holds a pane container and one of the pane containers holds an edit control.

Splitters

Splitters can be either vertical or horizontal and are used in popular multipane applications such as Microsoft Outlook to divide the application's main window into functional sections. The splitter window template is found in atlsplit.h. This header file offers two ready to use splitter implementations. CSplitterWindow is a standard vertical splitter while CHorSplitterWindow provides a horizontal splitter.

3-pane Layout

A Microsoft Outlook style 3-pane layout requires a vertical splitter, which divides the screen into left and right segments, and a horizontal splitter, which divides the right segment of the vertical splitter into top and bottom. The main frame of the application is parent to the vertical splitter while the vertical splitter is parent to the horizontal. Basic setup coding, using the m_vSplit and m_hzSplit member variables, is as follows:

// client rect for vertical splitter

CRect rcVert;
GetClientRect(&rcVert);

// create the vertical splitter

m_vSplit.Create(m_hWnd, rcVert, NULL,
  WS_CHILD | WS_VISIBLE | WS_CLIPSIBLINGS | WS_CLIPCHILDREN);

// client rect for horizontal splitter

CRect rcHorz;
GetClientRect(&rcHorz);

// create the horizontal splitter. Note that vSplit is parent of hzSplit

m_hzSplit.Create(m_vSplit.m_hWnd, rcHorz, NULL,
  WS_CHILD | WS_VISIBLE | WS_CLIPSIBLINGS | WS_CLIPCHILDREN);

// add the horizontal splitter to the right pane (1) of vertical splitter

m_vSplit.SetSplitterPane(1, m_hzSplit);

In addition, several options were set for both splitters. The code snippet below shows how to set a minimum split size of 35 pixels. This limits how close to the left or right edge of the main window the splitter can be moved. Also, the initial splitter position is set 85 pixels from the left and the "ghost bar" effect is enabled. In ghost bar mode, a dark gray bar is displayed while the splitter is being dragged. In normal mode, the entire splitter and pane contents are dragged.

// set the vertical splitter parameters

m_vSplit.m_cxyMin = 35; // minimum size

m_vSplit.SetSplitterPos(85); // from left

m_vSplit.m_bFullDrag = false; // ghost bar enabled

At this point, the framework of a 3-pane application is in place. The next section of this article describes how to add content to the splitters. Splitter panes can contain controls, such as a treelist or listview, or a child window, such as a dialog or pane container. In our sample project we add a pane container to each section of the application, setting various options, and then add an edit control to one of the pane containers.

Pane Containers

Pane Containers provide an area where child windows and controls can be hosted. They also provide a header bar with a title and close button. A common use for pane containers is to provide helpful titles and a logical grouping of related program elements. The CPaneContainer class is found in the atlctrlx.h header file.

Initializing a Pane Container

There are three basic steps needed to use a pane container. First, create the container using the appropriate splitter handle as parent. Second, add the container to the appropriate splitter section. Third, set the container title. These steps are shown in the code below. This code sets up the pane container for the left section of the vertical split.

// create the left container

m_lPane.Create(m_vSplit.m_hWnd);

// add container to left pane (0) of vertical splitter

m_vSplit.SetSplitterPane(0, m_lPane);

// set the left pane title

m_lPane.SetTitle("Left Pane");

If desired, the title can be set when the pane container is created by supplying the title text or a resource ID for the title string as the second parameter to the pane container create statement.

After the container is created and assigned to a splitter, you may set extended options. The extended options are PANECNT_NOCLOSEBUTTON and PANECNT_VERTICAL. The first option controls whether or not a Close button is displayed in the container header and the second controls whether the header is layed out horizontally, at the top of the container, or vertically, on the left side of the container. Note that header titles are not displayed when the header is set to vertical orientation. Extended options are set as follows:

// remove the close button from the top container

m_tPane.SetPaneContainerExtendedStyle(PANECNT_NOCLOSEBUTTON);

Adding a Container Child

As mentioned earlier, a pane container can host a child window or a child control, such as the edit control in the sample program. The steps for adding a child control to a pane container are:

  1. Create the control as usual
  2. Configure the control per your needs
  3. Add the control to the pane container

You would use similar steps to add any kind of window to the container. This code shows how an edit control is added to the bottom pane container of the sample project:

// create and configure an edit control. Note that m_bPane is the parent

m_edit.Create(m_bPane.m_hWnd, rcDefault, NULL,
  WS_CHILD | WS_VISIBLE | WS_CLIPSIBLINGS | WS_CLIPCHILDREN, WS_EX_CLIENTEDGE);
m_edit.SetFont((HFONT)GetStockObject(DEFAULT_GUI_FONT), TRUE);
m_edit.SetWindowText(" Bottom Pane -- with vertical header and edit as child");

// assign the edit to the bottom container

m_bPane.SetClient(m_edit.m_hWnd);

Handling the Close Button

When a user click on the pane container's Close button (marked with an X), the button sends an ID_PANE_CLOSE notification. The sample project catches the notification in the main frame's message map and uses the following routine to process it:

LRESULT OnPaneClose(WORD, WORD, HWND hWndCtl, BOOL&)
{ // hide the container whose Close button was clicked

  ::ShowWindow(hWndCtl, SW_HIDE);

  // find the container's parent splitter

  HWND hWnd = ::GetParent(hWndCtl);
  CSplitterWindow* pWnd;
  pWnd = (CSplitterWindow*)::GetWindowLong(hWnd, GWL_ID);

  // take the container that was Closed out of the splitter

  int nCount = pWnd->m_nPanesCount;
  for(int nPane = 0; nPane < nCount; nPane++)
  { if (hWndCtl == pWnd->m_hWndPane[nPane])
    { pWnd->SetSinglePaneMode(nCount - nPane - 1);
      break; } }

  return 0; }

Use DestroyWindow(hWndCtl) instead of ShowWindow if you want to totally remove the container instead of just hiding it. You may also want to use SetSplitterPane(nPane, NULL) instead of SetSinglePaneMode if you want to stay in multipane mode instead of changing to single pane mode.

In addition, you might want to replace an existing child window or control with another. Do so by creating it and adding it to the container after the old one is removed. If you do implement that as a feature, you may also want to override the container's DrawButtonImage method to provide an appropriate button image.

Terms Of Use

The sample project available with this article is free. Use the code however you wish.

THIS SOFTWARE IS DISTRIBUTED AS-IS, WITHOUT WARRANTIES OF ANY KIND.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here