Introduction
Despite there being a lot of examples of tabbed and splitter view usages and implementations I couldn't find a solution that mixed it together. So I developed a WTL class to simplify building SDI applications that use the tabbed and splitted views. At first time I tried to use a standard window tab control. But later I couldn't resist to use 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. Using drag and drop operations you can
- change a tab position in tab control
- drag a tabbed client view into the other client view pane
- arrange 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 features above is SplitPane
. You should not meet any difficulties to use it in your application. First of all you need to include the follow files in the place where they can be accessible by 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 by Daniel Bowen. |
DotNetTabCtrl.h |
Tab control derived from CCustomTabCtrl meant to look like the tabs in VS.Net (MDI tabs, solution explorer tabs, etc.). 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 SDI application project with ATL/WTL Wizard. You have to turn off "Minimize CRT use in ATL" configuration option. Add Split Pane class as a main view in it with the following steps:
- Include DockTabSplitPane.h, for instance, in stdafx.h file.
- For your
CMainFrame
class add SplitPane
class member and an inheritance from CallBackListener
interface class:
class CMainFrame
: public CFrameWindowImpl< CMainFrame>
, public CUpdateUI< CMainFrame>
, public CMessageFilter
, public CIdleHandler
, public DockSplitTab::CallBackListener
....
DockSplitTab::SplitPane mainPane;
....
public:
CMainFrame();
The objective of CallBackListener
class is to provide Split Pane notifications to the owner or parent object. I think it's simpler then 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 initialize mainPane
with a pointer to this class as showed below:
...
CMainFrame::CMainFrame()
: mainPane( this, true)
...
{}
...
The second parameter for mainPane
constructor sets tab control bar on the top for all tab panes that Split Pane contains. To finish mainPane
put Split Pane window creation code in CMainFrame::OnCreate
event handler and assign it to m_hWndClient
property:
LRESULT CMainFrame::OnCreate(UINT, WPARAM, LPARAM, BOOL&) {
...
this->m_hWndClient = this->mainPane.create( this->m_hWnd);
...
}
That's it. We have finished Split Pane definition as a main view window for the application. Do not forget to define function handlers for CallBackListener
interface. How to do this see demo project.
I use DockSplitTab
namespace for all definitions. Of course you can put the line of code below instead of using the namespace specifier for each name.
using namespace DockSplitTab;
SplitPane
class
Public methods:
HWND create( parentWnd, rect);
bool append( caption, clientViewWnd, tooltip, imageIndex);
bool detachClientView( clientViewWnd);
HWND focusedClientView();
bool setFocusTo( x, y)
bool setFocusTo( clientViewWnd) {
int getClientViewCount();
bool getClientViewRect( point, &rect);
void moveClientView( HWND sourceWnd, HWND targetWnd);
void moveClientViewsTo( SplitPane* targetPane);
void splitClientView( sourceWnd, targetWnd, targetArea);
void setImageList( HIMAGELIST imgList);
HIMAGELIST getImageList();
CallBackListener
interface class
This class provides a notification interface between SplitPane
and its owner class.
virtual void clientActivate( childWnd, clientViewWnd) = 0;
virtual void clientDblClick( childWnd, clientViewWnd) = 0;
virtual void clientCloseClick( childWnd, clientViewWnd) = 0;
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;
void trackDragAndDrop( HWND hWnd, POINT startPoint,
bool lockWindowUpdate = false);
What's inside
I don't believe in "black box" concept. I think it's always better to know what's inside so I try to give some explanations what's inside of SplitPane
class. The basic building block is Pane
class that can be used independently in your application as showed for SplitPane
if you do need only tabbed pane and ability to change a tab position for tabbed views. SplitPane
owns the hierarchy of Tab Pane objects using VSpliter
and HSpliter
classes which are specializations of standard WTL::CSplitterWindowImpl<>
class template. You can check it using Spy++ utility.
class ClientProperties |
- provides three Client View attributes: caption, tooltip and image index the required parameter for Pane::get method. |
class TabControlItem |
- inherited from CCustomTabItem class and includes client view window handler member. |
class TabControl |
- specializes 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 CSplitterWindowImpl<> class template |
class HSplitter |
- horizontal splitter window. Specializes CSplitterWindowImpl<> class template |
class SplitPane::DragContext |
- inherited from RectTracker class. |
What to do next
Obviously it needs some serialization mechanism. Any other ideas? ;-)