Topic : Splitter Window
Author : Unknown
Page : << Previous 2  Next >>
Go to page :

the splitter window is created as follows:

(Code omitted)

In the above code, we did not pass any value to dwStyle and nID parameters of function CSplitterWnd::Create(...), so the default values are used.

3 Customizing the Behavior of Split Bar

The behavior of a dynamic splitter window is different from that of a static splitter window. For the dynamic splitter window, panes could be dynamically created by double clicking on the split boxes (Figure 3-3). After new panes are added, one or more split bars will appear. If the user double clicks any of the split bar, one f the two panes divided by that split bar will be deleted (Figure 3-4). We can examine the sample applications we've created to see the difference between static splitter window and dynamic splitter window.

(Figure omitted)

This behavior could be customized. For example, sometimes by double clicking on the split bar, we want to resize the two panes instead of deleting one of them. This feature gives the user much convenience for changing the size of each pane bit by bit.

Splitter Window Layout

We need to override the following two member functions of class CSplitterWnd in order to implement this feature: CSplitterWnd::DeleteRow(...) and CSplitterWnd::DeleteColumn(...). When the user double clicks on the split bar, one of the two functions will be called to delete a row or column dynamically. In order to customize this behavior, after the split bar is clicked, we can first change the size of each pane, then judge if the size of one pane is smaller than its minimum size. If so, we call the default implementation of the corresponding function to delete one row or column.

To change a pane's size, we need to call function CSplitterWnd::SetColumnInfo(...) and CSplitterWnd::SetRowInfo(...). The current size of a pane could be obtained by their counterpart functions CSplitterWnd::GetColumnInfo(...) and CSplitterWnd::GetRowInfo(...). The following shows the formats of the above four functions:

void CSplitterWnd::SetColumnInfo(int col, int cxIdeal, int cxMin);

void CSplitterWnd::SetRowInfo(int row, int cyIdeal, int cyMin);

void CSplitterWnd::GetColumnInfo(int col, int& cxCur, int& cxMin);

void CSplitterWnd::GetRowInfo(int row, int& cyCur, int& cyMin);

In the above functions, parameters row and col are used to identify a pane with specified row and column indices, cyIdeal and cxIdeal are the ideal size of a pane, cyMin and cxMin indicate minimum size of it.

When the splitter window is being displayed, each pane's dimension is decided from its ideal size. According to the current size of the frame window, some panes may be set to their ideal sizes, but some may not (This depends on how much space is left for that pane). In any case, a pane's actual size should not be smaller than its minimum size. This is why we need both ideal size and minimum size to set a row or column's dimension.

The number of rows and columns a splitter window currently has can be obtained by calling other two member functions of CSplitterWnd:

int CSplitterWnd::GetRowCount();

int CSplitterWnd::GetColumnCount();

After we call function CSplitterWnd::SetColumnInfo(...) or CSplitterWnd::SetRowInfo(...), the old layout will not change until we call function CSplitterWnd::RecalcLayout() to update the splitter window. The system will re-calculate the layout for each pane according to their new sizes (both ideal size and minimum size), and the split bar will be moved to a new position according to the new layout.

Overriding CSplitterWnd::DeleteRow(...) and CSplitterWnd:: DeleteColumn(...)

Sample 3\Spw is based on sample 2\Spw. In the new sample, the behavior of the split bar is modified: if the user double clicks on it, it will move a small step downward (for horizontal split bar) or rightward (for vertical split bar). A pane will be deleted after it reaches its minimum size.

In the sample application, first a new class MCSplitterWnd is derived from class CSplitterWnd:

class MCSplitterWnd : public CSplitterWnd



void DeleteRow(int);

void DeleteColumn(int);

The class does nothing but overriding two functions. The implementation of function MCSplitterWnd ::DeleteRow(...) is listed as follows:

(Code omitted)

Since the maximum number of rows that can be implemented in a dynamic split window is 2, we will call the default implementation of this function (the corresponding function of the base class) if the number of rows is not 2. Otherwise, we first obtain the size of upper pane (pane 0), enlarge its vertical size, and set its current size. Then the current size of lower pane is reduced, if its ideal size is smaller than its minimum size after change, we simply call function CSplitterWnd::DeleteRow(...) to delete this row. If the panes are resized instead of being deleted, we call function CSplitterWnd::RecalcLayout() to update the new layout.

Function MCSplitterWnd::DeleteColumn(int colDelete) is implemented in the same way, except that here we call all the functions dealing with column instead of row.

Using the New Class

Using this new class is simple, we just need to include the header file containing class MCSplitterWnd in file "MainFrm.h", then use it to declare variable m_wndSpw in class CMainFrame as follows:

class CMainFrame : public CFrameWnd





MCSplitterWnd m_wndSp;



After these changes, by compiling and executing the application again, we will see that the split bar behaves differently.

4 Customizing the Default Appearance

Drawing Functions

Class CSplitterWnd has two member functions that can be overridden to customize the appearance of split bar, split box, split border, and split tracker. The functions are CSplitterWnd::OnDrawSplitter(...) and CSplitter::OnInvertTracker(...) respectively, which have the following formats:

void CSplitterWnd::OnDrawSplitter(CDC *pDC, ESplitType nType, const CRect &rect);

void CSplitterWnd::OnInvertTracker(const CRect &rect);

Function CSplitterWnd::OnDrawSplitter(...) is called when either the split bar, split box or split border needs to be painted. It has three parameters, the first of which is a pointer to the target device DC, which will be used to draw the objects. The second parameter is an enumerate type, which indicates what type of object is being drawn. This parameter could be either CSplitterWnd::splitBox, CSplitterWnd::splitBar, or CSplitterWnd::splitBorder, which indicates different splitter window objects. The third parameter specifies a rectangle region within which the object will be drawn.

Function CSplitterWnd::OnInvertTracker(...) is called when the user clicks the mouse on the split bar and drags it to resize the panes contained in the splitter window. In this case, a tracker will appear on the screen and move with the mouse. By default, the tracker is a grayed line. By overriding this function, we could let the tracker have a different appearance.


Sample 4\Spw demonstrates how to customize these styles. It is based on sample 3\Spw. First, two functions are declared in class MCSplitterWnd to override the default implementation:

class MCSplitterWnd : public CSplitterWnd



virtual void DeleteRow(int);

virtual void DeleteColumn(int);


virtual void OnDrawSplitter(CDC*, CSplitterWnd::ESplitType, const CRect&);

virtual void OnInvertTracker(const CRect& rect);


Function MCSplitterWnd::OnDrawSplitter(...) is overridden as follows:

(Code omitted)

In the above function, first parameter pDC is checked. If it is not an available DC, we do nothing but calling the default implementation of the base class. Otherwise, the object type is checked. We will go on to implement the customization if the object is either a split bar or a split box.

The simplest way to fill a rectangle with certain pattern is to use brush. A brush can be different types: solid, hatched, etc. It could also be initialized with any color. To use a brush, we need to first create brush, then select it into the device context. If we draw a rectangle with this DC, the interior of the rectangle will be automatically filled with the currently selected brush, and its border will be drawn using the currently selected pen. After using the brush, we must select it out of the DC.

Brush selection can be implemented by calling function CDC::SelectObject(...). This function will return a pointer to the old brush. After using the brush, we can call this function again and pass the old brush to it. This will let the old brush be selected into the DC so the new brush is selected out.

When creating a brush, we need to use RGB macro to indicate the brush color. The three parameters of RGB macro indicate the intensity of red, green and blue colors.

A rectangle can be drawn by calling function CDC::Rectangle(...). We need to pass a CRect type variable to indicate the position and size of the rectangle.

In the sample, function MCSplitterWnd::OnInvertTracker(...)is implemented as follows:

(Code omitted)

There is no CDC type pointer passed to this function. However, for any window, its DC could always be obtained by calling function CWnd::GetDC(). This function will return a pointer to window's device context. After we use the DC, we must release it by calling function CWnd::ReleaseDC(...). In function MCSplitterWnd::OnDrawSplitter(...), first a solid brush with red color is created, then we select it into the DC, call function CDC::PatBlt(...) to fill the interior of the rectangle using the selected brush.

Function CDC::PatBlt(...) allows us to create a pattern on the device. We can choose different color output mode: we can copy the brush color to the destination, or we can combine brush color with the color on the target device using bit-wise operations. The first four parameters of function CDC::PatBlt(...) indicate the position and size of the rectangle within which we can output the pattern. The fifth parameter indicates the output mode. In the sample we use PATINVERT drawing mode, this will combine the destination color and brush color using bit-wise

Page : << Previous 2  Next >>