Undocumented Corner

George and Scot examine the undocumented areas of CSplitterWnd, one of the most complicated and confusing of all MFC classes.


December 01, 1996
URL:http://www.drdobbs.com/windows/undocumented-corner/184410025

December 1996: Undocumented Corner

Inside MFC's CSplitterWnd

George Shepherd and Scot Wingo

Scot is a cofounder of Stingray Software. He can be contacted at [email protected]. George is a senior computer scientist with DevelopMentor and can be contacted at [email protected]. They are the coauthors of MFC Internals (Addison-Wesley, 1996).


CSplitterWnd is one of the most complicated and confusing of all MFC classes. This month, we'll examine the undocumented areas of CSplitterWnd; in our next column, we'll show how to apply this knowledge in your MFC applications. Before diving into the undocumented areas of CSplitterWnd, let's take a look at the anatomy of a splitter window.

Like a real-world window, a splitter window (see Figure 1) contains panes; in MFC, there can be two or four panes. Panes are usually CViews, but CSplitterWnd is flexible enough that these can be any CWnd derivative. Users create a pane by grabbing the split box and dragging it to place a splitter bar. When there are two splitter bars, the point where the bars meet is called a "splitter intersection."

Splitter windows internally maintain the panes in rows and columns. Since splitter windows use views as panes, when users change the document data the results are automatically updated in each pane. If you use CWnd derivatives as panes, this synchronization will be up to you.

It is important to note that splitter windows have their own scrollbars. Splitter windows need their own scrollbar logic because the scrollbars often affect two panes at once. The bottom horizontal scrollbar scrolls both pane 1 and pane 2.

How to Use CSplitterWnd

Dynamic splitters let users create "splits" on the fly (as with Word, Excel, Visual C++, for example), while static splitters provide an always-present split that the user can move (as with Microsoft Explorer, Exchange, and the like).

To create a static splitter window in your MFC application:

1.Add a CSplitterWnd data member to your child-frame derivative (this will vary depending on whether you are using MDI or SDI); for example, CSplitterWnd m_wndSplitter;.

2.In the CMyChildFrame::OnCreateClient() handler, add a call to CSplitterWnd::Create(). Create() takes several arguments. The first is a pointer to the parent frame (usually this). The second and third arguments specify the maximum number of rows and columns. The fourth specifies a minimum allowable pane size. The fifth argument is a pointer to a CCreateContext. Example 1 creates a splitter window with a maximum of two rows and two columns. The minimum pane size is 10x10 and the context pointer is passed through without change.

Dynamic splitters in MFC are limited to 2x2, so the row and column argument are mostly used to restrict the rows and columns by specifying 1.

That's all that is needed to add dynamic splitter windows to MFC applications. When users create a split, MFC automatically takes care of filling the splitter with the panes (views) that are wired together with the splitter window.

To use static splitter windows, call CreateStatic() instead of Create(). It is also up to you to create the views for each static window since MFC does not know the type of the view to create. You create new panes by calling CSplitterWnd::CreateView().

Static splitter windows do not have the 2x2 restrictions of dynamic splitter windows.

Table 1 is a list of the interesting CSplitterWnd members. Some are documented, others aren't.

Inside CSplitterWnd

CSplitterWnd is implemented entirely in MFC and is not a wrapper around a common control. Thus CSplitterWnd is one of the largest and most interesting of the MFC user-interface classes.

CSplitterWnd is declared in AFXEXT.H. Listing One implements specific aspects of the declaration. In Listing One, we've retained numerous members in the declaration that are above the // Implementation section. While these members are documented, they are also important to understand the CSplitterWnd implementation. MFC does a great job of "virtualizing" CSplitterWnd so that you can create CSplitterWnd derivatives and change the default CSplitterWnd behavior. Once you understand more about how CSplitterWnd is implemented, this will be even easier.

All of CSplitterWnd lives in the WINSPLIT.CPP MFC source file. You may want to open that file and refer to it in the following discussion.

CSplitterWnd Initialization

The CSplitterWnd constructor initializes m_cxSplitter, m_cySplitter, m_cxBorderShare, m_cyBorderShare, m_cxSplitterGap, m_cySplitterGap, m_cxBorder, and m_cyBorder to the values previously detailed. Both Create() and CreateStatic() eventually call CreateCommon().

Create() (see Listing Two) begins with assertions that force the CSplitterWnd to be created with either or both nMaxCols and nMaxRows as 2. If maximum rows and columns are both 1, there would be no point in having a splitter window. Since dynamic splitters can only have two rows and columns, there are only three valid ways to call create--1,2; 2,1; or 2,2 as the maximum columns and rows. If the arguments make it through the assertion gauntlet, Create() stores the nMaxRows/nMaxCols arguments in corresponding data members. Next, Create() initializes the current number of rows and columns to 1. (m_nRows and m_nCols must always be 1 or 2 in dynamic splitters.) A splitter window with one column and row is like a window with one view. After initializing the current and maximum rows/columns, Create() passes the parent window pointer, minimum size, style, and ID to CreateCommon().

After calling CreateCommon(), Create() stores the CRuntimeClass information for the pane view in m_pDynamicViewClass. Create() then passes the pointer in a call to CreateView(). Finally, Create() stores the sizeMin values in the m_pColInfo/ m_pRowInfo for the current pane (0,0). This is a little strange because we haven't seen these arrays allocated yet. This must be happening in CreateCommon() or CreateView(). Let's compare Create() to CreateStatic().

In the pseudocode for CreateStatic() (Listing Three ), the initial assertions reveal that static splitter windows have a limit of 16 rows and columns (that's 256 panes!). The assertions also check that there is more than one row or column.

After testing its arguments, CreateStatic() sets m_nRows/m_nMaxRows (these are always equal in static splitter windows) to the nRows argument. CreateStatic() does the same for the column counterparts of these data members.

Finally, CreateStatic() calls CreateCommon(), passing the parent pointer, a size of (0,0), the style, and the integer ID. Unlike Create(), CreateStatic() does not call CreateView(). It is up to the static splitter window user to create all the views since the run-time information is not known by CreateStatic().

Create() and CreateStatic() are similar except for the restrictions they place on their arguments. Also, Create() calls CreateView(), while CreateStatic() does not. Since both functions call CreateCommon(), let's take a look at the other initializations this member function is performing.

CreateCommon() (see Listing Four) first creates a temporary style holder, dwCreateStyle, which contains the original dwStyle argument, minus the WS_HSCROLL and WS_VSCROLL style bits. CreateCommon() does this because it does not want the Window's window to have scrollbars--the scrollbars need to be managed by the splitter window, not the Window's window.

After making adjustment to the style flags, CreateCommon() subtracts the WS_BORDER flag if the application is running under Windows 95 (Windows 95 draws the border, not MFC).

Next, CreateCommon() calls AfxDeferRegisterClass() and calls CWnd::CreateEx() with no extended styles, _afxWndMDIFrame (the MDI frame window class; no erase-background handling), the handle of the parent from pParentWnd, and nID as the identifier. After calling CreateEx(), CreateCommon() allocates and initializes both the m_pColInfo and m_pRowInfo arrays using m_nMaxCols/Rows as the size of the array.

CreateCommon() iterates through both the row and column CRowColInfo arrays. Both nMinSize and nIdealSize are set to the sizeMin argument. nCurSize is initialized to -1, which indicates that it should be set when the pane's size is initialized (RecalcLayout()).

After initializing both the column and row CRowColInfo arrays, CreateCommon calls SetScrollStyle() to initialize m_bHasHScroll and m_bHasVScroll, then returns True.

CreateView() (see Listing Five) initially stores the sizeInit argument in the CRowColInfo corresponding array indexes. Next, CreateView() sets a local flag, bSendInitialUpdate to False.

If for some reason the CCreateContext pointer is NULL, CreateView() creates a local CCreateContext() and does its best to initialize each element based on sane values. It calls GetActivePane() to determine the m_pLastView CView pointer. Once CreateView() has the m_pLastView, it can determine the other CCreateContext fields by calling GetDocument() and then CDocument::GetDocTemplate(). After it has found these elements, CreateView() points the pContext pointer to them and sets bSendInitialUpdate to True so that they will be initialized correctly later in the function.

Next, CreateView() calls CreateObject() to create a pane (CWnd derivative) object from the CRuntimeClass information. After creating the pane object, CreateView() sets the styles and position rectangle, which are passed to a Create() call. The IdFromRowCol() function calculates the ID of the pane, based on the row and column. The formula is AFX_IDW_PANE_FIRST + row x 16 + col. If it is row 0 and column 5, the ID is AFX_IDW_PANE_FIRST + 5. If it is row 5 and column 0, the ID is AFX_IDW_PANE_FIRST + 80, and so on. This formula guarantees that all 256 possible panes will have a unique identifier in the AFX_IDW_PANE_FIRST to AFX_IDW_ PANE_LAST range.

If the call to Create() fails, CreateView() returns False, indicating failure. If Create() succeeds, CreateView() sends a WM_INITIALUPDATE to the pane if the bSendInitialUpdate flag is set. The flag is only set if CreateView() has to find the CCreateContext information on its own. Finally, CreateView() returns True.

That's all the logic for initializing and creating a splitter window. After the splitter window has been created with Create() or CreateStatic() and the panes have been created, splitter window action takes place in response to window messages and user interactions.

CSplitterWnd Pane Management

Two CSplitterWnd members, SplitRow() and SplitColumn(), are called to dynamically create a pane. SplitColumn() takes one argument--the location of the split. SplitColumn() is called when you create a vertical split. Listing Six, the SplitColumn() pseudocode, is available electronically.

SplitColumn() is not allowed for static splitter windows, so it first asserts that the current splitter window is dynamic by checking the SPLS_DYNAMIC_SPLIT flag.

SplitColumn() subtracts the size of the border from the column size and sets the local colNew variable to the number of columns. SplitColumn() then sets the local cxNew (the new column width) to the results of calling CanSplitRowCol(). CanSplitRowCol() calculates the new width of the pane based on the column information, the cxBefore value and the width of a splitter. If users have created a split smaller than the minimum pane size, CanSplitRowCol() will return -1, indicating that the pane could not be created. SplitColumn() checks for this and returns False if the pane can not be created. CanSplitRowCol() generates a Trace statement in the debug build. (If you run the Scribble tutorial with splitter windows in debug mode and create a small split, you will see CanSplitRowCol() reject the new pane size and revert the splitter window to an unsplit window.)

After approval from CanSplitRowCol(), SplitColumn() creates the scrollbar for the control if m_bHasHScroll is True. The m_bHasHScroll and m_bHasVScroll flags are both set in SetScrollStyle() based on the WS_VSCROLL/WS_HSCROLL style bits. Remember that SetScrollStyle() was called by CreateCommon(). Once the scrollbars are created, SplitColumn() increments the number of columns, m_nCols, by 1.

The next block of code in SplitColumn() creates the new pane. This is a For loop that iterates over the number of columns. SplitColumn() does this because if there are two rows and users creates a new column, then two (not one) new panes need to be created.

As the SplitColumn() For loop iterates through the rows creating a new pane for each one, it calls CreateView() with a size based on cxNew for width and the current height of the row for height. If the CreateView() fails, SplitColumn cleans up and returns False.

Once the For loop completes, SplitColumn() updates the CRowColInfo array elements for the current and previous columns with the post-split column widths. SplitColumn() concludes by calling RecalcLayout() to force a redraw of all of the splitter window panes and gadgets.

When examining the CSplitterWnd source code, you see that at the bottom of almost every member function that has anything to do with pane layout is a call to RecalcLayout(), which controls the placement of every splitter window component. Understanding RecalcLayout() key to understanding how splitter windows work and are updated. In general, RecalcLayout():

1. Gets the client rectangle by calling ::GetClientRect() and also the inside rectangle by calling CSplitterWnd::GetInsideRect().

2. Calculates the row and column layouts in m_pColInfo/m_pRowInfo by calling LayoutRowCol(), which calculates where the rows and columns are located based on the size argument and the size hints stored in the CRowColInfo argument. RecalcLayout() calls LayoutRowCol() twice, once for columns with the inside rectangle width and once for rows with the inside rectangle height.

3.Next, RecalcLayout() calls ::BeginDeferWindowPos() to setup a Begin/Defer/ End WindowPos call trio. RecalcLayout() uses the number of rows and columns to calculate the number of windows that will be moved. The handle returned by ::BeginDeferWindowPos() is stored in a local AFX_SIZEPARENTPARAMS structure called layout.

4. RecalcLayout() next calculates the size of the scrollbars and changes their position (including the size box) by calling a helper, DeferClientPos() which tweaks the sizes and then calls AfxRepositionWindow() passing along the pointer to the AFX_SIZEPARENTPARAMS structure. AfxRepositionWindow() lives in WINCORE.CPP. It performs more checks and finally calls DeferWindowPos using the handle in the AFX_SIZEPARENTPARAMS structure.

5. After repositioning the scrollbars, RecalcLayout iterates through the panes and calls DeferClientPos() for each of with the size information from the corresponding CRowColInfo array element with some minor adjustments for splitter gaps, and so on.

6. Once the scrollbars and panes have been repositioned, RecalcLayout() calls ::EndDeferWindowPos() to move all windows at once.

7. Finally, RecalcLayout() calls DrawAllSplitBars() with a NULL DC to invalidate the splitter bars and force a redraw in the new positions. The splitter bars will be drawn relative to the new row and column positions.

CSplitterWnd Drawing

The DrawAllSplitBars() member function does all of the actual drawing. This function takes a DC pointer, ESplitType enumeration, and rectangle.

First, DrawAllSplitBars() iterates through the columns and draws vertical splitter bars for m_nCols -1. DrawAllSplitBars() calculates the splitter bar dimensions using the width of the splitter, CRowColInfo for the column, and some of the other cosmetic data members.

Next, DrawAllSplitBars() iterates through the rows and performs the identical operations as above, except using the height of the splitter window and the row information to draw the row splitter bars.

Finally, if the program is running on Windows 95, DrawAllSplitBars() draws a 3-D border around each pane. It does this so that the splitter bars and the border merge instead of looking like they were drawn by different programs.

OnDrawSplitter() does splitter-window drawing. Listing Seven, is the pseudocode for this member function.

WINSPLIT.CPP

If the CDC pointer passed to OnDrawSplitter() is NULL, the caller wants to invalidate the rectangle. In this case OnDrawSplitter() calls CWnd::RedrawWindow() with the rectangle and the RDW_INVALIDATE flag set.

On the other hand, if the CDC pointer is not NULL, OnDrawSplitter switches on the ESplitType argument. Remember from the CSplitterWnd header that this is an enumeration of the various splitter window components (for example, splitBorder, splitIntersection, splitBox, and so on).

Before looking at the different cases of the switch statement, notice that after the switch OnDrawSplitter() calls Fill

SolidRect() with the local rectangle variable and button face color. Knowing this, you can deduce that the switch statement at the beginning of FillSolidRect() is adding some "dressing" to the filled rectangle.

The first case in the switch is splitBorder. The only call to OnDrawSplitter() with type sizeBorder is made by DrawSplitBars() if the application is running on Windows 95.

When OnDrawSplitter() draws the border, it makes two calls to CDC::Draw3dRect()--the first draws the outside of the border and the second draws the inside of the border using an offset by CX_BORDER, CY_BORDER. This creates a window border affect for the inside of the pane. The second case is for splitIntersection which just breaks out of the switch statement.

The splitBox type is the third case. Figure 2(a) shows a close up of a Windows 95 split and indicates which part of the splitBox is drawn by each GDI call.

If the application is running on Windows 95, OnDrawSplitter() draws a 3-D splitbox by making two calls to the CDC::Draw3dRect() member function. The first call draws the outside border and the second draws the inside border. Notice in Figure 2(a) how Draw3dRect() uses the two argument colors to draw the top and left rectangle sides in one color, and the bottom and right rectangle sides in another. The result is the Windows 95 "chiseled" 3-D look. For Windows 95, the splitBox case breaks and falls through to the FillSolidRect() call after the switch block. The FillSolidRect() call completes the splitbox by filling in its center area.

If the application is running on Windows NT (or Win32s), the splitBox case falls through to the splitBar case. Figure 2(b) shows the Windows NT splitbox. Note the black outline is there to provide contrast for the button. This border is not drawn by OnDrawSplitter().

For splitBar, OnDrawSplitter() draws some 3-D shading only if it is not running on Windows 95. Remember that this code will be the same for splitboxes and splitbars on NT and Win32s. The NT splitBox and splitBar case draws one 3-D rectangle and then deflates the rectangle in preparation for the FillSolidRect() call. Figure 2(b) shows the results of each GDI call on the appearance of the splitter.

Now you almost have the complete CSplitterWnd picture. Remember that DrawAllSplitBars() takes care of drawing the splitter bars and the splitter border. The only components you haven't seen drawn are splitboxes. The splitboxes are not drawn in OnDrawSplitter(), but are instead drawn in CSplitterWnd::OnPaint(). Listing Eight is pseudocode for OnPaint().

OnPaint() prepares to draw by getting the client rectangle and the inside rectangle. Then OnPaint() calls OnDrawSplitter() to draw both the horizontal and vertical splitboxes.

After drawing the splitboxes, OnPaint() calls DrawAllSplitBars() to draw the split bars and the split border.

Finally, if the application is not running on Windows 95, OnPaint() does lots of work to draw splitter bar intersections.

Conclusion

In the next column we will apply the knowledge presented here and show you how to use a CWnd as a CSplitterWnd pane, programmatically create/remove splits, how to customize the look-and-feel of the splitter windows and more.

Example 1: Creating a splitter window.

BOOL CChildFrame::OnCreateClient(LPCREATESTRUCT , CCreateContext* pContext) 
{
     return m_wndSplitter.Create(this,2, 2, CSize(10, 10), pContext);
}

Figure 1: Typical splitter window.

Figure 2: (a) Windows 95 splitbox, (b) Windows NT splitbox.

Table 1: (a) Encapsulated data types; (b) creation/layout data members; (c) cosmetic data members; (d) tracking data members; (e) general member functions; (f) layout member functions; (g) drawing member functions; (h) hit-test member functions; (i) tracking member functions; (j) message handlers.

     Data Type/Function                Description
     
(a)  ESplitType                        Defines the type of splitter to be drawn. Valid types are splitBox, splitBar,
                                         splitIntersection, and splitBorder.
     CRowColInfo                       Internal structure that maintains the minimum, ideal, and current size of a
                                         row/column. For rows, values refer to height; for columns, fields refer to width. 

(b)  m_pDynamicViewClass               Pointer to the CRuntimeClass information for the view (pane) to be dynamically
                                         created by CSplitterWnd.  Specified in Create() for dynamic splitters and CreateView() for static splitters.

     m_nMaxRows/m_nMaxCols             Maximum number of rows/columns as specified in the call to Create() 
                                         or CreateStatic(). 
     m_nRows/m_nCols                   Number of rows and columns currently being displayed in the CSplitterWnd.

     m_bHasHScroll/m_bHasVScroll       Flags indicate if there is a horizontal or vertical scrollbar.
     m_pColInfo                        Array of CRowInfo with one element for each column in CSplitterWnd. Value is fixed
                                         in static splitter windows and varies in dynamic splitter windows.
     m_pRowInfo                        Array of CRowColInfo with one element for each row in the CSplitterWnd. Value is
                                         fixed in static splitter windows; varies in dynamic splitter windows.

(c)  m_cxSplitter/m_cySplitter         Width and height of splitbox and splitter. Value is 7 for Windows 95, 4 for NT.
     m_cxBorderShare/m_cyBorderShare   Values are 0 if splitter window is drawing the border (Windows 95), or 1 if border is
                                         drawn by Windows NT.
     m_cxSplitterGap/m_cySplitterGap   Gap between splitbox/split bar and the scrollbar/border. On NT, value is 6; on
                                         Windows 95, value is 7.
     m_cxBorder/m_cyBorder             Border width of the splitter border. Value is 0 on NT; 2 on Windows 95. 

(d)  m_bTracking                      If True, user is dragging one splitter bar.
     m_bTracking2                     If True, user is dragging two splitter bars (m_bTracking will be true, too). If user drags
                                        the intersection point, both split bars must be tracked.
     m_ptTrackOffset                  "Pick" size of the hit testing. Hit testing is not limited to the exact width/height of a
                                        splitbox/split bar. m_ptTrackOffset gives the user some.
     m_rectLimit                      Size of a pane during tracking, used to determine the height of the tracking split bar.
     m_rectTracker                    Rectangle used to draw the split bar when tracking. 
     m_rectTracker2                   Rectangle used to draw the second split bar when tracking (if user is dragging two via an intersection).
     m_htTrack                        Set by the CSplitterWnd hit-tracking mechanism to an enumeration that describes
                                        the splitter window component that was "hit." 

(e)  CreateCommon()                   Called by Create() and CreateStatic() once they have initialized the dynamic and
                                        static specific members of CSplitterWnd. 
     CreateScrollBarCtrl()            Creates scrollbar with the specified style and identifier.
     DoScroll()                       Responds to scrollbar messages. DoScroll() synchronizes the appropriate panes.
     DoScrollBy()                     Scrolls appropriate panes by a specified amount. 
     DoKeyboardSplit()                Called to cause the window to split. 
     CanActivateNext()                Called to find out if next pane can be activated--if it can be given focus. Called by
                                        class CView. 
     ActivateNext()                   Used to activate next pane. Usually called when a pane is deleted to change the focus before dying.

(f)  RecalcLayout()                   Positioning for the splitter window. Called after a pane is created and also after it is destroyed.
     TrackRowSize()                   Updates m_pRowInfo array information for specified row. Also determines if there is
                                        enough room for the row and if not, deletes it (dynamic only).
     TrackColumnSize()                Same as above, but for a column.
     GetSizingParent()                Retrieves a sizable parent window (Windows 95 only). 

(g)  DrawAllSplitBars()               Drives the splitter window drawing process by making calls to OnDrawSplitter for
                                        each of the splitter window components to be drawn.
     OnDrawSplitter()                 OnDrawSplitter() draws the different splitter window components. OnDrawSplitter() is
                                        virtual, so you can modify the appearance of the splitter windows if you want to.
     OnPaint()                        Called in response to a WM_PAINT message. 

(h)  HitTest()                        Takes a point and returns hit-test value for it.
     GetInsideRect()                  Similar to GetClientRect(), except it takes into account shared scrollbars.
     GetHitRect()                     Retrieves hit rectangle for specified splitter-window component. 
     SetSplitCursor()                 Uses hit testing to determine which cursor to display. If the mouse is over a splitbox
                                        or split bar, SetSplitCursor() displays sizing arrows. If mouse is over an intersection,
                                        displays four-way arrows.

(i)  StartTracking()                  Starts tracking operation based on hit-test value. Called by DoKeyboardSplit() and
                                        OnLButtonDown(). 
     StopTracking()                   Stops tracking operation. Called by ONLButtonDblClk(), OnLButtonUp(), and other
                                        functions that want to stop tracking for various reasons. 
     OnInvertTracker()                Inverts tracker during rubber banding.
 
(j)  OnNcCreate()                     CSplitterWnd handles WM_NCCREATE message so that it can remove WS_EX_
                                        CLIENTEDGE extended style bit. CSplitterWnd draws 3-D border so that splitters can
                                        appear to be part of border.
     OnPaint()                        Draws splitter-window components.
     OnDisplayChange()                Called when users change the monitor resolution. OnDisplayChange() calls
                                        RecalcLayout() to update the splitter windows for the new setting.
     OnSize()                         Calls RecalcLayout() when the user resizes the splitter window. 
     OnMouseMove()                    Performs hit testing on splitter-window components.
     OnLButtonDown()                  Performs hit testing; if users click on a splitter-window component, starts tracking.
     OnLButtonDblClk()                Hit tests double click; if users double click on splitbox, automatically splits the
                                        window in half. If users double click splitter bar, split is removed. 
     OnLButtonUp()                    Stops tracking.
     OnKeyDown()                      Provides sequences that control splitter-window tracking.

Listing One

class CSplitterWnd : public CWnd
{
DECLARE_DYNAMIC(CSplitterWnd)
// Construction ** omitted.
// Attributes **omitted.
// Operations 
public:
virtual void RecalcLayout();    // call after changing sizes
// Overridables **some omitted.
protected:
enum ESplitType { splitBox, splitBar, splitIntersection, splitBorder }; 
virtual void OnDrawSplitter(CDC* pDC, ESplitType nType, const CRect& rect); 
virtual void OnInvertTracker(const CRect& rect);
public:
virtual BOOL CreateScrollBarCtrl(DWORD dwStyle, UINT nID);
public:
// high level command operations - called by default view implementation 
virtual BOOL CanActivateNext(BOOL bPrev = FALSE); 
virtual void ActivateNext(BOOL bPrev = FALSE); 
virtual BOOL DoKeyboardSplit();
// synchronized scrolling
virtual BOOL DoScroll(CView* pViewFrom, UINT nScrollCode, BOOL bDoScroll = TRUE);
virtual BOOL DoScrollBy(CView* pViewFrom, CSize sizeScroll, 
BOOL bDoScroll = TRUE);
// Implementation  **some omitted for brevity
public:
struct CRowColInfo {
          int nMinSize;       // below that try not to show
          int nIdealSize;     // user set size
          int nCurSize;       // 0 => invisible, -1 => nonexistent
     };
protected:
CRuntimeClass* m_pDynamicViewClass; 
int m_nMaxRows, m_nMaxCols;
// implementation attributes which control layout of the splitter
     int m_cxSplitter, m_cySplitter;         // size of splitter bar
     int m_cxBorderShare, m_cyBorderShare;   // space on either side of splitter
     int m_cxSplitterGap, m_cySplitterGap;   // amount of space between panes
     int m_cxBorder, m_cyBorder;             // borders in client area
// current state information
int m_nRows, m_nCols;
BOOL m_bHasHScroll, m_bHasVScroll;
CRowColInfo* m_pColInfo;
CRowColInfo* m_pRowInfo;
// Tracking info - only valid when 'm_bTracking' is set 
BOOL m_bTracking, m_bTracking2;
CPoint m_ptTrackOffset;
CRect m_rectLimit;
CRect m_rectTracker, m_rectTracker2; 
int m_htTrack;
// implementation routines
BOOL CreateCommon(CWnd* pParentWnd, SIZE sizeMin, DWORD dwStyle, UINT nID); 
int HitTest(CPoint pt) const; 
void GetInsideRect(CRect& rect) const; 
void GetHitRect(int ht, CRect& rect); 
void TrackRowSize(int y, int row); 
void TrackColumnSize(int x, int col); 
void DrawAllSplitBars(CDC* pDC, int cxInside, int cyInside); 
void SetSplitCursor(int ht);
CWnd* GetSizingParent();
// starting and stopping tracking
virtual void StartTracking(int ht);
virtual void StopTracking(BOOL bAccept);
afx_msg BOOL OnSetCursor(CWnd* pWnd, UINT nHitTest, UINT message); 
afx_msg void OnMouseMove(UINT nFlags, CPoint pt); 
afx_msg void OnPaint(); 
afx_msg void OnLButtonDown(UINT nFlags, CPoint pt); 
afx_msg void OnLButtonDblClk(UINT nFlags, CPoint pt); 
afx_msg void OnLButtonUp(UINT nFlags, CPoint pt); 
afx_msg void OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags); 
afx_msg void OnSize(UINT nType, int cx, int cy); 
afx_msg void OnHScroll(UINT nSBCode, UINT nPos, CScrollBar* pScrollBar); 
afx_msg void OnVScroll(UINT nSBCode, UINT nPos, CScrollBar* pScrollBar); 
afx_msg BOOL OnNcCreate(LPCREATESTRUCT lpcs); 
afx_msg void OnSysCommand(UINT nID, LPARAM lParam); 
afx_msg void OnDisplayChange();
DECLARE_MESSAGE_MAP()
};

Listing Two

BOOL CSplitterWnd::Create(CWnd* pParentWnd, int nMaxRows, int nMaxCols, 
SIZE sizeMin, CCreateContext* pContext, DWORD dwStyle, UINT nID)
{
ASSERT(nMaxRows >= 1 && nMaxRows <= 2);
ASSERT(nMaxCols >= 1 && nMaxCols <= 2);
ASSERT(nMaxCols > 1 || nMaxRows > 1);       // 1x1 is not permitted
m_nMaxRows = nMaxRows;
m_nMaxCols = nMaxCols;
m_nRows = m_nCols = 1;      // start off as 1x1
if (!CreateCommon(pParentWnd, sizeMin, dwStyle, nID))
return FALSE; 
m_pDynamicViewClass = pContext->m_pNewViewClass;
if (!CreateView(0, 0, m_pDynamicViewClass, sizeMin, pContext))
DestroyWindow(); // will clean up child windows return FALSE;
     m_pColInfo[0].nIdealSize = sizeMin.cx;
m_pRowInfo[0].nIdealSize = sizeMin.cy;
return TRUE;
}

Listing Three

BOOL CSplitterWnd::CreateStatic(CWnd* pParentWnd, int nRows, int nCols, 
DWORD dwStyle, UINT nID)
{
ASSERT(nRows >= 1 && nRows <= 16);
ASSERT(nCols >= 1 && nCols <= 16);
ASSERT(nCols > 1 || nRows > 1); 
ASSERT(!(dwStyle & SPLS_DYNAMIC_SPLIT)); 
m_nRows = m_nMaxRows = nRows; 
m_nCols = m_nMaxCols = nCols;
if (!CreateCommon(pParentWnd, CSize(0, 0), dwStyle, nID))
return FALSE;
return TRUE;
}

Listing Four

BOOL CSplitterWnd::CreateCommon(CWnd* pParentWnd,SIZE sizeMin, DWORD dwStyle, UINT nID) 
{
DWORD dwCreateStyle = dwStyle & ~(WS_HSCROLL|WS_VSCROLL); 
if (afxData.bWin4)
dwCreateStyle &= ~WS_BORDER;
if (!AfxDeferRegisterClass(AFX_WNDMDIFRAME_REG))
return FALSE;
if (!CreateEx(0, _afxWndMDIFrame, NULL, dwCreateStyle, 
0, 0, 0, 0, pParentWnd->m_hWnd, (HMENU)nID, NULL))
return FALSE;       // create invisible
m_pColInfo = new CRowColInfo[m_nMaxCols];
for (int col = 0; col < m_nMaxCols; col++){
m_pColInfo[col].nMinSize = m_pColInfo[col].nIdealSize = sizeMin.cx;
m_pColInfo[col].nCurSize = -1; // will be set in RecalcLayout
     }
m_pRowInfo = new CRowColInfo[m_nMaxRows];
for (int row = 0; row < m_nMaxRows; row++){
m_pRowInfo[row].nMinSize = m_pRowInfo[row].nIdealSize = sizeMin.cy;
m_pRowInfo[row].nCurSize = -1; // will be set in RecalcLayout
     }
SetScrollStyle(dwStyle);
return TRUE;
}

Listing Five

BOOL CSplitterWnd::CreateView(int row, int col, CRuntimeClass* pViewClass, 
SIZE sizeInit, CCreateContext* pContext)
{
m_pColInfo[col].nIdealSize = sizeInit.cx;
m_pRowInfo[row].nIdealSize = sizeInit.cy;
BOOL bSendInitialUpdate = FALSE;
CCreateContext contextT;
if (pContext == NULL) {
CView* pOldView = (CView*)GetActivePane();
if (pOldView != NULL && pOldView->IsKindOf(RUNTIME_CLASS(CView))){
contextT.m_pLastView = pOldView; 
contextT.m_pCurrentDoc = pOldView->GetDocument(); 
if (contextT.m_pCurrentDoc != NULL)
contextT.m_pNewDocTemplate = 
contextT.m_pCurrentDoc->GetDocTemplate();
          }
pContext = &contextT;
bSendInitialUpdate = TRUE;     
     }
CWnd* pWnd = (CWnd*)pViewClass->CreateObject();
DWORD dwStyle = AFX_WS_DEFAULT_VIEW;
if (afxData.bWin4)
dwStyle &= ~WS_BORDER;
// Create with the right size (wrong position)
CRect rect(CPoint(0,0), sizeInit);
if (!pWnd->Create(NULL, NULL, dwStyle, rect, this, IdFromRowCol(row, col), 
pContext)) 
return FALSE;
// send initial notification message
if (bSendInitialUpdate)
pWnd->SendMessage(WM_INITIALUPDATE);
return TRUE;
}

Listing Six

BOOL CSplitterWnd::SplitColumn(int cxBefore)
{
ASSERT(GetStyle() & SPLS_DYNAMIC_SPLIT);
cxBefore -= m_cxBorder;
int colNew = m_nCols;
int cxNew = CanSplitRowCol(&m_pColInfo[colNew-1], cxBefore, m_cxSplitter); if (cxNew == -1)
return FALSE;   // too small to split
// create the scroll bar first (so new views can see that it is there)
if (m_bHasHScroll &&
!CreateScrollBarCtrl(SBS_HORZ, AFX_IDW_HSCROLL_FIRST + colNew))     { TRACE0("Warning: SplitRow failed to create scroll bar.\n"); return FALSE;
     }

m_nCols++;  // bump count during view creation
// create new views to fill the new column (RecalcLayout will position)
for (int row = 0; row < m_nRows; row++)     {
CSize size(cxNew, m_pRowInfo[row].nCurSize);
if (!CreateView(row, colNew, m_pDynamicViewClass, size, NULL))     {
TRACE0("Warning: SplitColumn failed to create new column.\n");
// delete anything we partially created 'col' = # columns created 
while (row > 0)
DeleteView(--row, colNew); 
if (m_bHasHScroll)
GetDlgItem(AFX_IDW_HSCROLL_FIRST + colNew)->DestroyWindow();
m_nCols--;      // it didn't work out return FALSE;
          }
     }
// new parts created - resize and re-layout
m_pColInfo[colNew-1].nIdealSize = cxBefore;
m_pColInfo[colNew].nIdealSize = cxNew;
RecalcLayout();
return TRUE;
}

Listing Seven

void CSplitterWnd::OnDrawSplitter(CDC* pDC, ESplitType nType, const CRect& rectArg)
{
if (pDC == NULL) 
RedrawWindow(rectArg, NULL, RDW_INVALIDATE|RDW_NOCHILDREN); return;
     CRect rect = rectArg;
switch (nType)     {
case splitBorder:
pDC->Draw3dRect(rect, afxData.clrBtnShadow, afxData.clrBtnHilite); 
rect.InflateRect(-CX_BORDER, -CY_BORDER); 
pDC->Draw3dRect(rect, afxData.clrWindowFrame, afxData.clrBtnFace);
return;
case splitIntersection:
break;
case splitBox:
if (afxData.bWin4){
pDC->Draw3dRect(rect, afxData.clrBtnFace,
afxData.clrWindowFrame); 
rect.InflateRect(-CX_BORDER, -CY_BORDER); 
pDC->Draw3dRect(rect, afxData.clrBtnHilite,
afxData.clrBtnShadow); 
rect.InflateRect(-CX_BORDER, -CY_BORDER); 
break;
          }
// fall through...  
case splitBar:
if (!afxData.bWin4){
pDC->Draw3dRect(rect, afxData.clrBtnHilite, 
afxData.clrBtnShadow); 
rect.InflateRect(-CX_BORDER, -CY_BORDER);
               }
break;
default:
ASSERT(FALSE);  // unknown splitter type
     }
pDC->FillSolidRect(rect, afxData.clrBtnFace);
}

Listing Eight

void CSplitterWnd::OnPaint()
{
CPaintDC dc(this);
CRect rectClient;
GetClientRect(&rectClient);
rectClient.InflateRect(-m_cxBorder, -m_cyBorder);
CRect rectInside;
GetInsideRect(rectInside);
if (m_bHasVScroll && m_nRows < m_nMaxRows)
OnDrawSplitter(&dc, splitBox, CRect(rectInside.right + afxData.bNotWin4,
rectClient.top, rectClient.right, rectClient.top + m_cySplitter));
     if (m_bHasHScroll && m_nCols < m_nMaxCols)
OnDrawSplitter(&dc, splitBox,
CRect(rectClient.left, rectInside.bottom + afxData.bNotWin4,
rectClient.left + m_cxSplitter, rectClient.bottom));
     DrawAllSplitBars(&dc, rectInside.right, rectInside.bottom);
if (!afxData.bWin4) {
// draw splitter intersections (inside only)
GetInsideRect(rectInside); 
dc.IntersectClipRect(rectInside);
CRect rect;
rect.top = rectInside.top;
for (int row = 0; row < m_nRows - 1; row++) {
rect.top += m_pRowInfo[row].nCurSize + m_cyBorderShare;
rect.bottom = rect.top + m_cySplitter;
rect.left = rectInside.left;
for (int col = 0; col < m_nCols - 1; col++) {
rect.left += m_pColInfo[col].nCurSize + m_cxBorderShare; 
rect.right = rect.left + m_cxSplitter;
OnDrawSplitter(&dc, splitIntersection, rect); 
rect.left = rect.right + m_cxBorderShare;
          }
rect.top = rect.bottom + m_cxBorderShare;
     }
     }
}

Terms of Service | Privacy Statement | Copyright © 2024 UBM Tech, All rights reserved.