Objective Grid : PART II Programmer’s Guide : Chapter 19 Dynamic Splitter Window Support : Process
Process
1. Generate a default SDI doc/view or MDI doc/view based skeleton application with the MFC AppWizard.
2. Include grid\gxall.h in stdafx.h.
3. Add "grid\gxresrc.h" and "grid\gxres.rc" to the Resource Includes dialog.
4. Add a call to the GXInit() function call in the InitInstance() function of the CWndApp-derived class of the application.
 
BOOL CGridDSplitApp::InitInstance()
{
AfxEnableControlContainer();
GXInit();
5. Add grid-related data to your document class. Remember to initialize the m_pParam pointer to NULL in the constructor of the document class.
 
class CGridDSplitDoc : public CDocument
{
public:
CGXGridParam* GetParam()
{return m_pParam;};
void SetParam(CGXGridParam* pParam)
{m_pParam = pParam;};
protected:
CGXGridParam* m_pParam;
6. Change the base class for your view class from CView to CGXGridView. The following steps are specific to adding dynamic splitter support.
7. Add a member variable of CGXDTabWnd and CGXDSplitWnd to the CMainFrame class.
 
protected: // control bar embedded members
CStatusBar m_wndStatusBar;
CToolBar m_wndToolBar;
 
CGXDSplitWnd m_wndSplitter;
CGXDTabWnd m_wndTab;
8. Override the OnCreateClient() virtual function of the CMainFrame class for SDI application, or override the CChildFrame class for MDI applications and implement the function as shown below. In addition, include the header file for our view class, GridDSplitView.h, at the top of the mainfrm.cpp file. After you add the include, add a forward declaration of the CGridDSplitDoc class to the top of CGridDSplitView class.
 
BOOL CMainFrame::OnCreateClient(LPCREATESTRUCT lpcs,
CCreateContext* pContext)
{
// Specify the run-time class for the first sheet.
// Don't forget to register the document template
// in InitInstance!
pContext->m_pNewViewClass=RUNTIME_CLASS(CGridDSplitView);
 
// creates the tab window
VERIFY(m_wndTab.Create(this, _T("What's New?"),
pContext));
 
// each view should associated with the same document.
 
m_wndSplitter.m_pOwnerWnd = &m_wndTab;
pContext->m_pNewViewClass=UNTIME_CLASS(CGridDSplitView);
 
m_wndSplitter.Create(&m_wndTab,
2, 2, // TODO: adjust the number of rows, columns
CSize(10, 10), // TODO: adjust the minimum pane size
pContext);
m_wndSplitter.ShowWindow(SW_HIDE);
m_wndTab.AttachWnd(&m_wndSplitter, "Splitter Tab");
 
return TRUE;
}
9. Now, you need to add some code to the view class to support the dynamic splitter window. Override the OnInitialUpdate() virtual function in the view class.
 
void CGridDSplitView::OnInitialUpdate()
{
if(GetDocument()->GetParam() == NULL)
SetParam(new CGXGridParam(), TRUE);
else
SetParam(GetDocument()->GetParam(), FALSE);
 
DoInitialUpdate();
GetDocument()->SetParam(GetParam());
GetParam()->EnableUndo(FALSE);
 
SetRowCount(100);
SetColCount(20);
 
SetValueRange(CGXRange(1,1), _T("Dynamic!"));
 
EnableHints();
//GetParam()->SetSmartResize(FALSE);
GetParam()->EnableUndo(TRUE);
 
UpdateScrollbars();
}
10. Implement the DoInitialUpdate() function used in OnInitialUpdate(). The implementation of this function is almost an exact copy of the OnInitialUpdate() method in the CGXGridView class, which is a class in the Objective Grid library. Notice that in the preceding implementation, we used some protected member variables of the CGXGridCore class. To give your view class access these variables, you need to make your view class a friend of CGXGridCore.
Let's add an empty derived class_core to the top of the DoInitialUpdate() function.
 
class _core: public CGXGridCore
{
friend class CGridDSplitView;
};
 
void CGridDSplitView::DoInitialUpdate()
{
SetGridWnd(this, GetDocument(), TRUE);
 
CWnd* pWnd = DXGetParentSplitter(this, TRUE);
 
if(pWnd && pWnd->IsKindOf(RUNTIME_CLASS(CGXDSplitWnd)))
m_pSplitterWnd = (CSplitterWnd*) pWnd;
else
m_pSplitterWnd = NULL;
 
// Commment: I have move the following lines before
// CGXGridCore::OnGridInitialUpdate() so that scrollbars
// can be initialized correctly.
if (m_pSplitterWnd || GetSharedScrollbarParentWnd())
SetScrollBarMode(SB_BOTH, gxnShared | gxnEnhanced);
else
SetScrollBarMode(SB_BOTH, gxnAutomatic |
gxnEnhanced);
 
OnGridInitialUpdate();
CGXView::OnInitialUpdate();
 
CGXGridParam* pParam = GetParam();
 
// check print device
if (GetPrintDevice() == NULL)
{
// is print device in parameter object?
if (pParam->GetPrintDevice() == NULL)
pParam->SetPrintDevice(new CGXPrintDevice);
 
SetPrintDevice(pParam->GetPrintDevice(), FALSE);
}
 
// if this is a new pane in a dynamic splitter window
// I will initialize top row or left column
 
if (m_pSplitterWnd != NULL)
{
ASSERT(m_pSplitterWnd-> IsKindOf(RUNTIME_CLASS(CGXDSplitWnd)));
VERIFY(m_pSplitterWnd->IsChildPane(this, m_nSplitRow,
m_nSplitCol));
 
ASSERT(m_nSplitRow < 2);
ASSERT(m_nSplitCol < 2);
 
if (m_nSplitRow > 0 || m_nSplitCol > 0)
{
// copy settings from other pane
CGXGridView *pView;
 
pView = (CGXGridView *) m_pSplitterWnd->GetPane(0, 0);
 
ASSERT(pView != NULL);
if (pView->IsKindOf(RUNTIME_CLASS(CGXGridView)))
{
if (m_nSplitRow > 0)
{
pView = (CGXGridView *) m_pSplitterWnd->GetPane(0,
m_nSplitCol);
 
ASSERT(pView != NULL);
if (pView->IsKindOf(RUNTIME_CLASS(CGXGridView)))
{
m_nLeftCol = pView->m_nLeftCol;
m_nTopRow = pView->m_nTopRow;
m_bDisplayHeaderRow = FALSE;
 
// disable smart redrawing of WM_SIZE message
((_core*)pView)->m_cxOld = (
(_core*)pView)->m_cyOld = 0;
}
}
 
if (m_nSplitCol > 0)
{
pView = (CGXGridView *)
m_pSplitterWnd->GetPane(m_nSplitRow, 0);
 
ASSERT(pView != NULL);
if (pView->IsKindOf(RUNTIME_CLASS(CGXGridView)))
{
m_nTopRow = pView->m_nTopRow;
if (m_nSplitRow == 0)
m_nLeftCol = pView->m_nLeftCol;
m_bDisplayHeaderCol = FALSE;
 
// disable smart redrawing of WM_SIZE message
((_core*)pView)->m_cxOld = (
(_core*)pView)->m_cyOld = 0;
}
}
}
 
}
m_pSplitterWnd = NULL;
UpdateScrollbars();
}
}
11. Implement the DXGetParentSplitter() function that is used in the DoInitialUpdate() function. This function is implemented as static.
 
CWnd* CGridDSplitView::DXGetParentSplitter(const CWnd *pWnd,
BOOL bAnyState)
{
CWnd* pSplitter = pWnd->GetParent();
if (!pSplitter->IsKindOf(RUNTIME_CLASS(CGXDSplitWnd))
&& !pSplitter->IsKindOf(RUNTIME_CLASS(CGXDTabWnd)))
return NULL; // not a splitter
if (!bAnyState)
{
// ignore splitters in minimized (iconic) windows
while ((pWnd = pWnd->GetParent()) != NULL)
if (pWnd->IsIconic())
return NULL;
}
return pSplitter;
}
12. Now, delegate the OnDraw() function to that of CGXGridView class.
 
void CGridDSplitView::OnDraw(CDC* pDC)
{
CGXGridView::OnDraw(pDC);
}
13. If you compile and run the application now, you will have a grid application with dynamic splitter support. If you choose the Splitter tab, you will see our dynamic splitter window at work. Two sets of scrollbars will be displayed. To hide one set of the scroll bars, you need to make a small change to your code.
 
14. Override the GetScrollBarCtrl() function of the CGXGridView class.
 
CScrollBar* CGridDSplitView::GetScrollBarCtrl(int nBar) const
{
#define _AfxGetDlgCtrlID(hWnd) ((UINT)(WORD)::GetDlgCtrlID(hWnd))
 
ASSERT(nBar == SB_HORZ || nBar == SB_VERT);
if (GetStyle() &
((nBar == SB_HORZ) ? WS_HSCROLL : WS_VSCROLL))
{
// it has a regular windows style scrollbar
// (no control)
return NULL;
}
 
CWnd* pParent = DXGetParentSplitter(this, TRUE);
if (pParent == NULL)
return NULL; // no splitter
 
UINT nID = _AfxGetDlgCtrlID(m_hWnd);
if (nID < AFX_IDW_PANE_FIRST || nID >
AFX_IDW_PANE_LAST)
return NULL; // not a standard pane ID
 
// appropriate PANE id - look for sibling (splitter, or
// just frame)
UINT nIDScroll;
if (nBar == SB_HORZ)
nIDScroll = AFX_IDW_HSCROLL_FIRST +
(nID - AFX_IDW_PANE_FIRST) % 16;
else
nIDScroll = AFX_IDW_VSCROLL_FIRST +
(nID - AFX_IDW_PANE_FIRST) / 16;
 
// return shared scroll bars that are immediate
// children of splitter
// return (CScrollBar*)pParent->GetDlgItem(nIDScroll);
if(GetParent()->IsKindOf(RUNTIME_CLASS(CGXDTabWnd)))
{
// appropriate PANE id - look for sibling
// (splitter, or just frame)
UINT nIDScroll;
if (nBar == SB_HORZ)
nIDScroll = AFX_IDW_HSCROLL_FIRST;
else
nIDScroll = AFX_IDW_VSCROLL_FIRST;
return (CScrollBar*)
pParent->GetDlgItem(nIDScroll);
}
CScrollBar* pBar = (CScrollBar*)
pParent->GetDlgItem(nIDScroll);
 
// check one parent up
if(pBar == NULL)
pBar = (CScrollBar*)
pParent->GetParent()->GetDlgItem(nIDScroll);
 
return pBar;
}
15. Now, the dynamic splitter window should only have one set of scrollbars. You can use mouse to drag the splitter box to split the window in the Splitter tab window.