TN029: Splitter Windows
This note describes the MFC CSplitterWnd class, which is used to provide window splits and to manage the resizing of other Pane windows.
Note please be sure to read Technical Note 20 regarding use of IDs and valid ID ranges.
Splitter Styles
A CSplitterWnd supports two different styles of splitting windows.
In “static splitters,” the panes are created when the splitter window is created, and the order and number of panes never change. Splitter bars are used to resize the different panes, and the different panes are usually of different view classes. The Visual C++ graphics editor and the Windows File Manager are examples of programs that use this splitter style. Splitter boxes are not used by this style of splitter.
In “Dynamic splitters,” additional panes are created and destroyed as the user splits and un-splits new views. This splitter starts out with a single view, and splitter boxes are provided to initiate splitting. If the view is split in one direction, an additional view object is dynamically created to represent the new pane. If the view is split in two directions (possible with the keyboard interface), then three new views are created to represent the three new panes. When the split is active, the splitter box is drawn as a splitter bar between the panes. Additional view objects are destroyed when the user removes a split, but the original view (row 0, column 0) remains until the splitter window itself is destroyed. Microsoft Excel or Microsoft Word are examples of the dynamic splitter style.
When creating either kind of splitter window, you must specify the maximum number of rows and columns that the splitter will manage. For a static splitter, panes must be created to fill all the rows and columns. For a dynamic splitter, the first pane is automatically created when the CSplitterWnd is created.
The maximum number of panes you can specify for static splitters is 16 rows by 16 columns. The recommended configurations are:
1 row x 2 columns : usually with dissimilar panes
2 rows x 1 column : usually with dissimilar panes
2 rows x 2 columns : usually with similar panes
The maximum number of panes you can specify for dynamic splitters is 2 rows by 2 columns. The recommended configurations are:
1 row x 2 columns : for columnar data
2 rows x 1 column : for textual or other data
2 rows x 2 columns : for grid or table oriented data
Splitter Examples
Many of the MFC sample programs use splitter windows directly or indirectly. Step 4 of the MFC Tutorial adds a dynamic splitter window to split the CScribView into multiple panes. As shown in this step, adding a new splitter frame window is very easy with the Add Class feature of ClassWizard. This example is a dynamic splitter window by default.
The MFC General sample illustrates several uses of static splitters, including how to place a splitter in a splitter.
ClassWizard will also create a new multiple document interface (MDI) Child frame window class which contains a splitter window. Please refer to Splitter Windows in Visual C++ Programmer's Guide.
Terminology Used by Implementation
Terminology of the parts of a CSplitterWnd and related objects.
CSplitterWnd:
This is a window that provides pane-splitting controls and scroll bars that are shared between all panes on a row or column. Rows and columns are specified with zero-based numbers [that is, the first pane is row = 0 and column = 0]
Pane:
An application-specific window that is managed by a CSplitterWnd. A pane is usually a CView-derived object, but in fact can be any CWnd object that has the appropriate child window ID.
To do so, simply pass the RUNTIME_CLASS of your CWnd derived class as you would if you were using a CView derived class. Your class must use DECLARE_DYNCREATE and IMPLEMENT_DYNCREATE -- the framework uses dynamic creation at runtime. Although there is a lot of code that is CView specific in CSplitterWnd, CObject::IsKindOf is always used before those actions are performed. Certainly, it is much easier to use CSplitterWnd with CView derived classes than CWnd derived classes.
Splitter Bar:
A control that is placed between rows and columns of panes. It may be used to adjust the sizes of rows or columns of Panes.
Splitter Box:
A small control at the top of the vertical scroll bars or to the left of the horizontal scroll bars in a dynamic CSplitterWnd. Used to create new rows or columns of panes.
Splitter Intersection:
The intersection of a vertical splitter bar and a horizontal splitter bar. May be dragged to adjust the size of a row and column of panes simultaneously.
Shared Scroll Bars
The CSplitterWnd class also supports shared scroll bars. These scroll bar controls are children of the CSplitterWnd and are shared with the different panes in the splitter.
For example, in a 1 row x 2 column window, you can specify WS_VSCROLL when creating the CSplitterWnd. A special scroll bar control will be created that is shared between the two panes.
[ ][ ][^]
[pane00][pane01][|]
[ ][ ][v]
When the user moves the scroll bar, WM_VSCROLL messages will be sent to both views. When the views set the scroll bar position, the shared scroll bar will be set.
Note that shared scroll bars are most useful with dynamic or static splits, splitting similar view objects. If you mix views of different types in a splitter, then you may have to write special code to coordinate their scroll positions. Any CView-derived class that uses the CWnd scroll bar APIs will delegate to the shared scroll bar if it exists. The CScrollView implementation is one such example of a CView class that supports shared scroll bars. Non-CView derived classes, classes that rely on noncontrol scroll bars, or classes that use standard Windows implementations (for example, CEditView) will not work with the shared scroll bar feature of CSplitterWnd.
Minimum Sizes
For each row there is a minimum row height, and similarly for each column there is a minimum column width. This minimum is used to decide if the pane is to small to be shown in complete detail.
For a static splitter window, the initial minimum row height and column width is 0. For a dynamic splitter window, the initial minimum row height and column width are set by the sizeMin parameter to the CSplitterWnd::Create function.
These minimum sizes can be changed with the SetRowInfo and SetColumnInfo APIs.
Actual vs. Ideal Sizes
The layout of the panes in the splitter window depends on the size of the containing frame (which in turn resizes the CSplitterWnd. CSplitterWnd repositions and resizes the panes so that they fit as ideally as possible).
The row height and column width sizes set by the user, or through the CSplitterWnd API, represent the ideal size. The actual size can be smaller than that ideal size (if there is not enough room to make that pane the ideal size) or larger than the ideal size (if that pane must be made larger to fill the left-over space on the right or bottom of the splitter window).
Protected Interface
The following describes some of the splitter window implementation overridables that can be used by advanced users of CSplitterWnd to customize the features and user interface of this class. These APIs are officially undocumented and are subject to change in future versions of MFC. Please refer to the implementation source code for more details on these implementation APIs.
Drawing the splitter bars, boxes and trackers:
enum ESplitType
{ splitBox, splitBar, splitIntersection, splitBorder };
virtual void OnDrawSplitter(CDC* pDC, ESplitType nType, const CRect& rect);
virtual void OnInvertTracker(const CRect& rect);
These virtual function can be overridden to provide alternate imagery for the various graphical components of a splitter window. The default imagery is similar to the splitter in Microsoft Works for Windows: only intersections of splitter bars are blended together. The imagery is also quite different when the framework detects Windows 4.0 -- in order to match the visuals in the shell on that (future) operating system.
Creating controls and views:
virtual BOOL CreateScrollBarCtrl(DWORD dwStyle, UINT nID);
This is called to create a shared scroll bar control. It can be overridden to include extra controls next to a scroll bar. The default behavior is to just create normal Windows scroll bar controls.
virtual void DeleteView(int row, int col);
virtual BOOL SplitRow(int cyBefore);
virtual BOOL SplitColumn(int cxBefore);
virtual void DeleteRow(int row);
virtual void DeleteColumn(int row);
These functions are called to implement the logic of the dynamic splitter window (that is, if the splitter window has the SPLS_DYNAMIC_SPLIT style). They can be customized, along with the virtual function CreateView, to implement more advanced dynamic splitters.
The following are high level commands that are used by the CView class to delegate to the CSplitterWnd implementation. They are virtual so that the standard CView implementation will not require the entire CSplitterWnd implementation to be linked in. For applications that use CView but not CSplitterWnd, the CSplitterWnd implementation will not be linked with the application.
virtual BOOL CanActivateNext(BOOL bPrev = FALSE);
Checks to see if the “Next Pane” or “Previous Pane” command is currently possible.
virtual void ActivateNext(BOOL bPrev = FALSE);
Performs the “Next Pane” or “Previous Pane” command.
virtual BOOL DoKeyboardSplit();
Performs the keyboard split command, usually “Window Split”.