Tab Controls And Splitters�Mixing Them Together

Environment: VC++.NET, Windows NT/2000/XP

Introduction

Dispite the fact that there are a lot of examples of tabbed and split view usages and implemetations, I couldn’t find a solution that mixed them together. So, I developed a WTL class to simplify building SDI applications that use the tabbed and split views. At first, I tried to use a standard Windows tab control. But later, I coudn’t resist using an amazing tab control implementation done by Bjarke Viksoe (CCustomTabCtrl) and Daniel Bowen (CDotNetTabCtrl).

What It Does

There are three main features of this class. By using drag and drop operations, you can do the following things:

  • Change a tab position in tab control.
  • Drag a tabbed client view into the other client view pane.
  • Arrange a tabbed client view against the edges of another tabbed pane (split vertically or horizontally with the others).

Using the Code

The main class that adds all the features above is SplitPane. You should not meet any dificulties using it in your application. First of all, you need to include the following files in the place where they can be accessed by the compiler:

atlgdix.h Additional GDI/USER wrappers. Written by Bjarke Viksoe.
CustomTabCtrl.h A base class to help implement Tab Controls with different appearances. Written by Bjarke Viksoe. Several improvements were made by Daniel Bowen.
DotNetTabCtrl.h Tab control derived from CCustomTabCtrl meant to look like the tabs in VS.Net (MDI tabs, solution explorer tabs, and so forth). Written by Daniel Bowen.
DockTabPane.h Tab Control and Tab Pane implementation. Uses all include files above.
DockTabSplitPane.h     Split Pane implementation. Includes DockTabPane.h.

Then, create an SDI application project with the ATL/WTL Wizard. You have to turn off the “Minimize CRT use in ATL” configuration option. Add Split Pane class as a main view in it with the following steps:

  1. Include DockTabSplitPane.h; for instance, in the stdafx.h file.
  2. For your CMainFrame class, add a SplitPane class member and an inheritance from the CallBackListener interface class:
    class CMainFrame
        : public CFrameWindowImpl< CMainFrame>
        , public CUpdateUI< CMainFrame>
        , public CMessageFilter
        , public CIdleHandler
        , public DockSplitTab::CallBackListener
        ....
        DockSplitTab::SplitPane mainPane;
        ....
    public:
        CMainFrame();
    

    The objectve of the CallBackListener class is to provide Split Pane notifications to the owner or parent object. I think it’s simplier than Win32 messages. Along with the inheritance from MainFrame class, you can design a special class adapter that implements all communication needs between SplitPane and its owner. So, don’t forget to initialize mainPane with a pointer to this class, as shown below:

  3. ...
    CMainFrame::CMainFrame()
        : mainPane( this, true)
        ...
    {}
    ...
    
  4. The second parameter for the mainPane constructor sets a tab control bar on the top for all tab panes that Split Pane contains. To finish mainPane, put the Split Pane window creation code in the CMainFrame::OnCreate event handler and assign it to the m_hWndClient property:
  5. LRESULT CMainFrame::OnCreate(UINT, WPARAM, LPARAM, BOOL&) {
        ...
        this->m_hWndClient = this->mainPane.create( this->m_hWnd);
        ...
    }
    

That’s it. We have finished the Split Pane definition as a main view window for the application. Do not forget to define function handlers for the CallBackListener interface. To learn how to do this, see the demo project.

I use the DockSplitTab namespace for all definitions. Of course, you can put in the line of code below instead of using the namespace specifier for each name.

using namespace DockSplitTab;

SplitPane Class

Public methods:

// creates a new Split Pane window with parentWnd and rect
// parameters
HWND create( parentWnd, rect);

// adds the new client view window to Split Pane.
// The new client view is added into the focused tab pane
bool append( caption, clientViewWnd, tooltip, imageIndex);

// detaches the client view window from Split Pane.
// This method changes a parent window of the client window view
// to the Split Pane parent one.
bool detachClientView( clientViewWnd);

// returns the client view window that receives the keyboard focus
HWND focusedClientView();

// sets the keyboard focus to a tab pane at the specified position
bool setFocusTo( x, y)

// sets the keyboard focus to the specified client view window
bool setFocusTo( clientViewWnd) {

// returns the number of client view windows in Split Pane
int getClientViewCount();

// returns the rectangle of tab pane, if any, is at a specified
// position
bool getClientViewRect( point, &rect);

// move the client view window (sourceWnd) to the same tab pane
// where the specified client view window is located
void moveClientView( HWND sourceWnd, HWND targetWnd);

// move all client view windows to the specified split pane.
void moveClientViewsTo( SplitPane* targetPane);

// splits source client view window (sourceWnd) with the target
// client view
void splitClientView( sourceWnd, targetWnd, targetArea);

// set and get Image List
void setImageList( HIMAGELIST imgList);
HIMAGELIST getImageList();

CallBackListener Interface Class

This class provides a notification interface between SplitPane and its owner class.

// triggered when client view client view wnd has gained the
// keyboard focus
virtual void clientActivate( childWnd, clientViewWnd) = 0;

// triggered when client view client view wnd got a double mouse
// click on the tab button
virtual void clientDblClick( childWnd, clientViewWnd) = 0;

// triggered the close button was pushed for the client view client
virtual void clientCloseClick( childWnd, clientViewWnd) = 0;

// drag and drop notifications
virtual void dragStart(  childWnd, clientViewWnd, x, y,
                         keysPressed) = 0;
virtual void dragOver(   childWnd, clientViewWnd, x, y,
                         keysPressed) = 0;
virtual void dragDrop(   childWnd, clientViewWnd, x, y,
                         keysPressed) = 0;
virtual void dragCancel( childWnd, clientViewWnd) = 0;

// performs the drag and drop tracking with drag and drop
// notifications
void trackDragAndDrop( HWND hWnd, POINT startPoint,
                       bool lockWindowUpdate = false);

What’s Inside

I don’t believe in the “black box” concept. I think it’s always better to know what’s inside, so I try to give some explanation of what’s inside of the SplitPane class. The basic building block is the Pane class that can be used inpenedently in your application as shown for SplitPane if you need only a tabbed pane and the ability to change a tab position for tabbed views. SplitPane owns the hierarchy of Tab Pane objects by using VSpliter and HSpliter classes that are specializations of the standard WTL::CSplitterWindowImpl<> class template. You can check it by using the Spy++ utility.

class ClientProperties Provides three Client View attribues: caption, tooltip, and image index: the required parameters for the Pane::get method.
class TabControlItem Inherited from CCustomTabItem class and includes a client view window handler member.
class TabControl Despecializes the CDotNetTabCtrlImpl<> class template.
class Pane Container class for a tab control and all client view windows linked with the tab control.
class RectTracker Helper class to draw tracker rectangle during drag and drop operations.
class VSplitter Vertical splitter window. Specializes the CSplitterWindowImpl<> class template.
class HSplitter Horizontal splitter window. Specializes the CSplitterWindowImpl<> class template.
class SplitPane::DragContext Inherited from RectTracker class.

What to Do Next

Obviously, it needs some serialization mechanism. Any other ideas?

More by Author

Get the Free Newsletter!

Subscribe to Developer Insider for top news, trends & analysis

Must Read