Back to Amir Israeli Homepage

[CNSSplitter : (Netscape style) splitter window ]

This article was contributed by Amir Israeli Amir Israeli.
Amir Israeli Web Site

Environment: Win 95/98 VC6 SP4

CNSSplitter is another implementation that resembles the Netscape style of splitter windows . This implementation intends to be used as static (a static splitter) . This class overrides several virtual functions that reside within MFC CSplitterWnd , since some functions aren't documented in MSDN , I will also describe them shortly.

View that show appearance of vertical/horizontal CNSSplitter windows (flat+non flat styles)



In order to use that class you need first to create that class. The code to create it resides in OnCreateClient of CFrameWnd derived class : you will find remarks within that code :

BOOL CMainFrame::OnCreateClient(LPCREATESTRUCT /*lpcs*/,
	CCreateContext* pContext)
{
	// create splitter window (static type)
	if (!m_wndSplitter.CreateStatic(this, 1, 2))
		return FALSE;

	// create some view
	if (!m_wndSplitter.CreateView(0, 0, RUNTIME_CLASS(CLeftView), CSize(195, 100), pContext))
	{
		m_wndSplitter.DestroyWindow();
		return FALSE;
	}
	if (!m_wndSplitter1.CreateStatic(&m_wndSplitter,2,1,WS_CHILD|WS_VISIBLE,
		   m_wndSplitter.IdFromRowCol(0,1)))
	{
		m_wndSplitter.DestroyWindow();
		return FALSE;
	}
	if (!m_wndSplitter1.CreateView(0, 0, RUNTIME_CLASS(CLeftView), CSize(0, 200), pContext))
	{
		m_wndSplitter1.DestroyWindow();
	    m_wndSplitter.DestroyWindow();
		return FALSE;
	}
	if (!m_wndSplitter1.CreateView(1, 0, RUNTIME_CLASS(CCustomSplitterView), CSize(0, 0), pContext))
	{
		m_wndSplitter1.DestroyWindow();
		m_wndSplitter.DestroyWindow();
		return FALSE;
	}
	// until here general calls that are called for any CSplitterWnd class.
	
	// calls unique to this class
	// set bitmaps to the splitters
	m_wndSplitter.SetBitmaps(IDB_BITMAP2,IDB_BITMAP1);
	m_wndSplitter1.SetBitmaps(IDB_BITMAP9,IDB_BITMAP10);
	
	// set optiize size : to be restored in double-click event
	m_wndSplitter1.SetOptimizedSize(100,FALSE);
	m_wndSplitter.SetOptimizedSize(300,FALSE);
	return TRUE;
}
I included handlers in mainframe class the set flat/non flat style to that splitter : as you see these are commonly used handlers , notice that both use IsFlat() function (of CNSSplitter) that returns a boolean value : is splitter has flat style or not.
void CMainFrame::OnFlatBar() 
{
	BOOL bFlat = m_wndSplitter.IsFlat();
	m_wndSplitter.SetAsFlat((bFlat) ? FALSE : TRUE);
	m_wndSplitter1.SetAsFlat((bFlat) ? FALSE : TRUE);
}
void CMainFrame::OnUpdateFlatBar(CCmdUI* pCmdUI) 
{
	BOOL bFlat = m_wndSplitter.IsFlat();
	if (bFlat) pCmdUI->SetText(_T("Unset Flat Splitter"));
	else pCmdUI->SetText(_T("Set Flat Splitter"));
}
This class includes very few public functions :
BOOL SetBitmaps(UINT nRes,UINT nResHover); sets bitmaps to splitter window : hover / regular bitmaps so that the splitter will behave as rebar ctrl / toolbars when mouse hovers. another check is done when mouse hovers to indicate whether it is above the bitmap or not.
These 2 functions set style of splitter , when reset splitter is redrawn using CSplitterWnd::RecalcLayout(); (an internal to draw all bars (in case of many: look into CSplitterWnd there you find the pointers that hold splitters : m_pColInfo ,m_pRowInfo)
BOOL SetAsFlat(BOOL bFlat=TRUE);,BOOL IsFlat() { return m_bFlatSplitter; } SetAsFlat sets several non documented internal variables of CSplitterWnd (with non default values) and
BOOL CNSSplitter::SetAsFlat(BOOL bFlat)
{
	// ...
	// NOTICE : these are non documented variables whithis CSplitterWnd class
	m_cxSplitter = m_cySplitter = m_nSplitterWidth + 2 + 2;
	m_cxBorderShare = m_cyBorderShare = 0;
	m_cxSplitterGap = m_cySplitterGap = m_nSplitterWidth + 2 + 2;

	// ...
	
	if (!bFlat) // edge parameters (not flat)
	{
		// borders of non flat are wider than flat
		m_cxBorder = m_cyBorder = 2;
	} else { // flat parameters
		m_cxBorder = m_cyBorder = 1;
	}
	// ... redraws the window 
}

SetOptimizedSize sets a default size that is restored when user double clicks the bars : again position is calculated , the optimized position is restored only when user double clicks the bitmap area , and when window size (width/height) is bigger than "cx" parameter. you can also use the second field to determine orientation of measurements : size is set topleft (default) or from bottomright (bFromLeftTop=FALSE).I used a negetive value to descriv\be the second condition , by that I wont need to add additinal variable (BOOL=int).

An importent function is "int SetSplitterWidth(int nWidth)" which sets a new width for bar. (do you want it narrow or wide?) if you call this function during runtime - after splitter has been shown : you need to redraw the splitter. Default values are being used : make splitter wider than usual.
IsVerticalSplitter() checks an internal variable of CSplitterWnd (m_nRows) of size of 1 : if so splitter is vertical, or else - horizontal.
IsBarOnEdge(); checks whether bar is on edge : this is useful when redrawing : to decide to which direction triangles direct. or to efine behaviour when bar's bitmap is double clicked.
UseHandCursor,IsUsingHandCursor : These functions set hand cursor whenever mouse hovers bitmap. Netscape navigator uses arrow cursor.
the second returns state to user.

True work is done behind the scenes : OnCreate handler calls LoadCursors() (loads arrow and hand cursors) and sets a default timer to splitter.
Area of bitmap is saved every time the bitmap is redrawn : during bar redrawn . It's saved in CRect variable named "m_rcLastImageRect" an extra area is added to the bitmap to widden its "influence" over client area. (more is added on flat style) , simple function CRect::PtInRect(POINT) return that value.

IsBarOnEdge checks whether bar is on edge by first defining whether its vertical/horizontal splitter , then it checks an internal variable of m_pColInfo[0].nCurSize or m_pRowInfo[0].nCurSize m_pColInfo,m_pRowInfo are really pointers to array of structures :CRowColInfo : every allocated structure describes a single row or column , also sizes are tracked , and reset every time bar is moved.

	struct CRowColInfo // from Afxext.h
	{
		int nMinSize;       // below that try not to show
		int nIdealSize;     // user set size
		// variable depending on the available size layout
		int nCurSize;       // 0 => invisible, -1 => nonexistant
	};

Drawing functions are a bit more complicated : at start OnDrawSplitter is overrided and provides a non default implementation whenever splitter isn't flat. in any case it calls DrawSplitterBitmap after bar is redrawn. this function draws the bar (simple drawing functions) and then creates 2 triangles : notice that triangles are created 8 pixels from left/right edges(in horizontal splitter) etc. (all measurements are defined as consts so you can change them easily) .This function also stores the last image rect in :

	// set last rectangle of image
	m_rcLastImageRect = CRect(ptSplitterBarStartPoint.x,ptSplitterBarStartPoint.y,
		ptSplitterBarStartPoint.x+szBitmapToDraw.cx, ptSplitterBarStartPoint.y+szBitmapToDraw.cy);
The last actions are creation of triangles that are painted

	// from here draw triangle arrows on bar (predefined Win32 arrow icons are ugly so werent used)
	CRgn rgn1,rgn2;
	CBrush br;
	br.CreateSolidBrush((bMouseOnBitmap) ? 
		::GetSysColor(COLOR_3DDKSHADOW) : ::GetSysColor(COLOR_GRAYTEXT));
	// creation of triangles
	CreateArrowRgn(rgn1,m_rcLastImageRect,bVerticalSplitter,!bBarOnEdge,TRUE);
	CreateArrowRgn(rgn2,m_rcLastImageRect,bVerticalSplitter,!bBarOnEdge,FALSE);
	// paint triangles in bars
	pDC->FillRgn(&rgn1,&br);
	pDC->FillRgn(&rgn2,&br);
CreateArrowRgn needs some optimization. (I know).

OnMouseMove handler checks whether state of hover bitmap is changed : from non-hover to hover or else. If changed we need to repaint the bitmap. When repainting we use "m_rcLastImageRect" rect of bitmap that was previously saved . (it will hold the proper rect because whenever rect canges it is recalculated , splitter are redrawn and new rect is re-saved)

void CNSSplitter::OnMouseMove(UINT nFlags, CPoint point) 
{
	CSplitterWnd::OnMouseMove(nFlags, point);
	BOOL bMouseHoversBitmap = IsMouseOnBitmap(point);
	if ((bMouseHoversBitmap && !m_bHoverBitmapDrawn) ||
			(!bMouseHoversBitmap && m_bHoverBitmapDrawn))
	{
		UpdateSplitterBitmap(TRUE);
	}
}
The function OnLButtonDblClk provides a custom implementation :
void CNSSplitter::OnLButtonDblClk(UINT nFlags, CPoint point) 
{
	if (!IsMouseOnBitmap(point)) // custom behaviour only on bitmap
	{
		CSplitterWnd::OnLButtonDblClk(nFlags, point);
		return;
	}

	int nPrev = m_nOptimizedSplitterSize;
	CRect rect;
	GetClientRect(&rect);

	if (IsVerticalSplitter())
	{
		while (rect.Width() < m_nOptimizedSplitterSize)
			m_nOptimizedSplitterSize/=2;
		int nSize = (IsBarOnEdge()) ? m_nOptimizedSplitterSize : 0;

		if (nSize>=0)
			TrackColumnSize(nSize,0);
		else {
			nSize = min(rect.Width()+nSize,rect.Width()+m_nOptimizedSplitterSize);
			TrackColumnSize(nSize,0);
		}

	} else { // horizontal splitter

		while (rect.Width() < m_nOptimizedSplitterSize)
			m_nOptimizedSplitterSize /= 2;

		int nSize = (IsBarOnEdge()) ? m_nOptimizedSplitterSize : 0;
	//...
	}
	
	RecalcLayout();
	m_nOptimizedSplitterSize = nPrev;

	CSplitterWnd::OnLButtonDblClk(nFlags, point);
}
it positions the bar in new place by using TrackColumnSize/TrackRowSize for vertical/horizontal splitters respecively. and redraws the window.

Move to downloads downloads

History

Date Posted: February 3, 2001

Home  |  Links  |  About