Click here to Skip to main content
Click here to Skip to main content

Another splitter control for dialog

By , 12 Jul 2002
 
Sample Image

Introduction

I'm a student and very interested in VC++. I often enter this web site to get free source code. I was in need of a splitter in a dialog. I downloaded one but it's very complex and I felt it was difficult to use the control (although it's very powerful) so I made one for myself. Maybe, it's not useful for you, but if there's only one person who thinks it's useful, I will be very happy. Sometimes you don't need good skill, just a good idea, and by this simple way a useful piece of code will be produced. My splitter control is one of this kind.

How to use the CSplitterControl class

First of all, add two files SplitterControl.h and SplitterControl.cpp to the project. Remember to add #include "splittercontrol.h" to the header file of the class which uses it.

Add member varible to the dialog class

 protected: 
    CSplitterControl     m_wndSplitter1; 

Now, we create the control by calling it's create function. This code would appear in the OnInitDialog or OnCreate function.

BOOL CSPDemoDlg::OnInitDialog()
{ 
	...
	pWnd = GetDlgItem(IDC_SPLITTER1);
	pWnd->GetWindowRect(rc);
	ScreenToClient(rc);
	m_wndSplitter1.Create(WS_CHILD | WS_VISIBLE, rc, this, IDC_SPLITTER1);
	m_wndSplitter1.SetRange(50, 50, -1);
	...

There is a tip here. Instead of calculating the rect for the splitter, we add a static control on the dialog (by resource editor), give it an ID (IDC_SPLITTER1) and make it invisible. Size it and locate in the resource editor, and then call the function GetWindowRect(rc) to move the m_wndSplitter1 to the rect.

And here is the code for resizing controls on the dialog when the user moves the splitter control.

//
// LRESULT CSPDemoDlg::DefWindowProc(UINT message, WPARAM wParam, LPARAM lParam) 
{
	if (message == WM_NOTIFY)
	{
		if (wParam == IDC_SPLITTER1)
		{	
			SPC_NMHDR* pHdr = (SPC_NMHDR*) lParam;
			DoResize1(pHdr->delta);
		}
	}
	
	return CDialog::DefWindowProc(message, wParam, lParam);
}
//
void CSPDemoDlg::DoResize1(int delta)
{
	// Change the width for m_wndType, m_lstItem, m_txtContent	
	CSplitterControl::ChangeWidth(&m_wndType, delta);
	CSplitterControl::ChangeWidth(&m_lstItem, -delta, CW_RIGHTALIGN);
	CSplitterControl::ChangeWidth(&m_txtContent, -delta, CW_RIGHTALIGN);
	Invalidate();
	UpdateWindow();
}

About the class CSplitterControl and it's functions

Here's the interface for the class CSplitterControl
class CSplitterControl : public CStatic
{
	// Construction
public:
	CSplitterControl();

	// Attributes
protected:
	BOOL m_bIsPressed;
	int m_nType;
	int m_nX, m_nY;
	int m_nMin, m_nMax;
	int m_nSavePos; // Save point on the lbutton down

public:
	// ClassWizard generated virtual function overrides
	//{{AFX_VIRTUAL(CSplitterControl)
	//}}AFX_VIRTUAL

	// Implementation
public:
	static void ChangePos(CWnd* pWnd, int dx, int dy);
	static void ChangeWidth(CWnd* pWnd, int dx, DWORD dwFlag = CW_LEFTALIGN);
	static void ChangeHeight(CWnd* pWnd, int dy, DWORD dwFlag = CW_TOPALIGN);

public:
	void SetRange(int nMin, int nMax);
	void SetRange(int nSubtraction, int nAddition, int nRoot);
	int GetStyle();
	int SetStyle(int nStyle = SPS_VERTICAL);
	void Create(DWORD dwStyle, const CRect& rect, CWnd* pParent, UINT nID);
	virtual ~CSplitterControl();

	// Generated message map functions
protected:
	virtual void DrawLine(CDC* pDC, int x, int y);
	void MoveWindowTo(CPoint pt);
	//{{AFX_MSG(CSplitterControl)
	afx_msg void OnPaint();
	afx_msg void OnMouseMove(UINT nFlags, CPoint point);
	afx_msg BOOL OnSetCursor(CWnd* pWnd, UINT nHitTest, UINT message);
	afx_msg void OnLButtonDown(UINT nFlags, CPoint point);
	afx_msg void OnLButtonUp(UINT nFlags, CPoint point);
	//}}AFX_MSG
	DECLARE_MESSAGE_MAP()
};

// this struct is sent as lparam in WM_NOTIFY message 
typedef struct SPC_NMHDR
{
	NMHDR hdr;
	int delta; // delta : the different position of the splitter before and 
                    // after being moved.
} SPC_NMHDR;

Conclusion

Well, that's all about my code. Maybe, the explanation is not very clear, but I hope you'll find it easy to use. No special skill, you see. Very simple. Thanks for reading my article. Please give your ideas as to whether you like or hate it.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here

About the Author

Hung Nguyen
United States United States
Member
No Biography provided

Sign Up to vote   Poor Excellent
Add a reason or comment to your vote: x
Votes of 3 or less require a comment

Comments and Discussions

 
You must Sign In to use this message board.
Search this forum  
    Spacing  Noise  Layout  Per page   
QuestionThank you for your helpmemberiishero17 Mar '12 - 0:59 
Thank you!
I'm a student too, and my english is very poor. But i want to say thank you for you, this demo is very useful for me
QuestionAny License attached with this codememberGreatestMaverick13 Dec '11 - 21:55 
Hi Hung
 
Is there any license associated with this code ?
 
Or as mentioned in the header can we use it for any purpose
 
rgds.
Shashi
Rgds.
Sulphur

GeneralMy vote of 5memberXiangism27 Oct '11 - 19:14 
perfect
GeneralMy vote of 3memberhelloworldbomb9 Dec '10 - 3:45 
good
Generalvery simple,thanksmemberjacksp28 Apr '10 - 22:49 
modify something later, i used it in my work Smile | :)
GeneralNice work!memberjcleung6 Nov '09 - 15:35 
It's very useful to me,thanks 4 ur share!
GeneralI want to display a Bitmap on the splittermemberlan_63738369 Aug '09 - 17:22 
I want to display a Bitmap on the splitter .What should I do?Thx
GeneralOne problemmembermxs8102 Apr '09 - 0:19 
This is a good job,But I found a problem when I use it.
One dialog is Maximize,before it is restored.If I click the splitter and move down in this time,
it will move to the bottom of the dialog.And I could not move it up.Confused | :confused:
Could you tell me how to deal with the problem?
Thank you very much. Laugh | :laugh:
Generalnice codememberyusc198626 Feb '09 - 15:48 
Smile | :)
GeneralGood source codememberziguowen27 Nov '08 - 14:15 
Thanks for your source code!It is so good!
General[Message Removed]memberimmetoz6 Oct '08 - 7:51 
Spam message removed
Questionno ideamemberMaryVeranis20 Mar '08 - 4:04 
sorry but i'm an asp.net programmer - can you direct me to what to do with this as far as what goes where? i don't know a thing about c#. i need it to work in an asp.net project so i can't put it uncompiled into my project.
thanks for any help.
GeneralVery nice work. I found a bug and my solutionmembertimyin2 Feb '08 - 21:17 
Hi all,
 
My Problem is:
There is two sub-dialogs in the parent dialog box, i use splitter to split the sub-dialog,
but when i create the splitter dynamically and the ID for this splitter is created in STRING table, when i drag the splitter, the parent dialog exits and the splitter bar is displayed on the screen.
 
My environment: Windows XP and Virtual C++ 6.0
 
Solution:
Comment the line // CStatic::OnLButtonDown(nFlags, point); in function void CSplitterControl::OnLButtonDown(UINT nFlags, CPoint point).
Now i can split the sub-dialogs correctly.
 
Hope it's userful to others.
 
Thanks!
 
BR,
Tim
 
I'm on my way!

GeneralVery nice workmemberAndrea Cacciarru21 Nov '07 - 0:31 
Simple usage for a quite little piece of code to add to my app. Thanks.
 

GeneralNgu bo mememberTo Quang Hiep6 Jun '07 - 23:08 
Doi voi Dialog thi can gi phai sung Splitter
Chi can ve cac control la giai quyet duoc roi
GeneralSlight fixmemberChrisHowe19 Feb '07 - 0:31 
Here is what I think is a fix to the SetRange method...
 
void CSplitterControl::SetRange(int nSubtraction, int nAddition, int nRoot)
{
if (nRoot < 0)
{
CRect rcWnd;
GetWindowRect(rcWnd);
GetParent()->ScreenToClient(rcWnd);
 
The original code was just calling GetWindowRect, which gets the window coordinates in screen space. What you want though is the coordinates in the client space of the parent window.
Questionhow can i make Splitter bar to be not allowed movememberkiranin31 Oct '06 - 0:28 
how can i make splitterbar to be not allow to move
AnswerRe: how can i make Splitter bar to be not allowed movememberNvng YA27 Nov '06 - 23:04 
Yes, I think you can do it with the following code,
 
m_wndSplitter1.SetRange(0, 0, -1);
 
or don't deal with these messages:
ChangePos(CWnd* pWnd, int dx, int dy);
ChangeWidth(CWnd* pWnd, int dx, DWORD dwFlag = CW_LEFTALIGN);
ChangeHeight(CWnd* pWnd, int dy, DWORD dwFlag = CW_TOPALIGN);

AnswerRe: how can i make Splitter bar to be not allowed movemembersharki198711 Aug '10 - 15:54 
Why you want to do this?Splitter is designed for that.
or you can delete these lines as follows:

void CSplitterControl::OnLButtonUp(UINT nFlags, CPoint point)
{
if (pOwner && IsWindow(pOwner->m_hWnd))
{
//pOwner->SendMessage(WM_NOTIFY, nmsp.hdr.idFrom, (LPARAM)&nmsp); }
}

 
and

void CSplitterControl::OnLButtonUp(UINT nFlags, CPoint point)
{
void CSplitterControl::MoveWindowTo(CPoint pt)
{
//MoveWindow(rc);

}

QuestionHow to use your code in SDI application?memberBhushan198014 Jul '06 - 11:54 
Hi,
I want to use this code in SDI application that has multiple views? As you made all your changes in the OnInitDialog handler here, what handlers need to be accessed in case of an SDI application? Is it possible? Thank you.
 

 
Bhushan.
Generalmoving out of dialogmembermirex25 Aug '04 - 4:00 
First I have to say that its a great control, i've been searching for something like this ... but i have one problem .. control can be moved out of the dialog ... is this handled somehow inside the code, or do I have to check out the new splitter position ?
GeneralRe: moving out of dialogmemberatripathi3 Aug '05 - 1:05 
I also run into same problem. following code worked for me
>>>>>
void CSplitterControl::OnMouseMove(UINT nFlags, CPoint point)
{
if (m_bIsPressed)
{
CWindowDC dc(NULL);
DrawLine(&dc, m_nX, m_nY);

CPoint pt = point;
ClientToScreen(&pt);
// GetParent()->ScreenToClient(&pt);
 
if (pt.x < m_nMin)
pt.x = m_nMin;
if (pt.y < m_nMin)
pt.y = m_nMin;
 
if (pt.x > m_nMax)
pt.x = m_nMax;
if (pt.y > m_nMax)
pt.y = m_nMax;
 
// GetParent()->ClientToScreen(&pt);
m_nX = pt.x;
m_nY = pt.y;
DrawLine(&dc, m_nX, m_nY);
}
CStatic::OnMouseMove(nFlags, point);
}
>>>>
 

AnswerRe: moving out of dialogmemberbm20 Jul '06 - 22:01 
A better fix to prevent moving outside the parent:
void CSplitterControl::OnMouseMove(UINT nFlags, CPoint point)
{
if (m_bIsPressed)
{
CWindowDC dc(NULL);
DrawLine(&dc, m_nX, m_nY);

CPoint pt = point;
ClientToScreen(&pt);
GetParent()->ScreenToClient(&pt);
 
if (pt.x < m_nMin)
pt.x = m_nMin;
if (pt.y < m_nMin)
pt.y = m_nMin;
 
if (pt.x > m_nMax)
pt.x = m_nMax;
if (pt.y > m_nMax)
pt.y = m_nMax;
 
//>>> BM/BEGIN: prevent moving outside the parent
CRect rParent;
GetParent()->GetClientRect( &rParent );
int minBorder = 10;
int iHeight = rParent.bottom - rParent.top - minBorder;
int iWidth = rParent.right - rParent.left - minBorder;
 
if (pt.x < minBorder)
pt.x = minBorder;
if (pt.y < minBorder)
pt.y = minBorder;
 
if (pt.x > iWidth)
pt.x = iWidth;
if (pt.y > iHeight)
pt.y = iHeight;
//>>> BM/END
 
GetParent()->ClientToScreen(&pt);
m_nX = pt.x;
m_nY = pt.y;
DrawLine(&dc, m_nX, m_nY);
}
CStatic::OnMouseMove(nFlags, point);
}

 
b.minet
Generalone problemmemberregale4 Sep '03 - 1:49 
if you add two buttons under the tree control,.i expect two buttons seem independent,in fact,they have overlappeded
each other.can you help me ?thanks a lotConfused | :confused:
GeneralRe: one problemmemberR.selvam3 Dec '03 - 13:39 
Hi,
 
You set property in Picture Control Visible as false ( Remove default check mark in Visible property )
 

GeneralWorks great for mememberAleemSheikh7 Jul '03 - 20:22 
Cool | :cool: I am using it in a calender application where there are multiple controls on a single dialog and it works great for me. I am using MFC 7.1. Thanks a lot.
 
M.Aleem Sheikh
Abacusoft
Generalsmall TCHAR.H -compatible modificationmembernanev23 May '03 - 2:08 
In CSplitterControl::Create() function find row
 
CStatic::Create("", dwStyle, rc, pParent, nID);
 
replace it with
 
CStatic::Create(_T(""), dwStyle, rc, pParent, nID);
 
in order to make code TCHAR.H compatible.
GeneralGood work. I've used it but with a little modificationmemberjstuardo8 Mar '03 - 19:29 
Very good work, that class was what I was looking for.
 
The modification I've made is to delete the OnPaint method since I didn't want the splitter bar to be seen, that way, it's more similar with the windows standard splitter, making my application more professional Smile | :) .
 
I have placed the splitter inside a tab control which is inside a controlbar and it looks great.
 
Wink | ;)
 
Thanks
Jaime
GeneralThx a lotmembersinouhe15 Jan '03 - 2:22 
A very simple and helpfull class for an MFC beginner as I am.
Thx again. OMG | :OMG:
 
Sinouhe
GeneralMoak: 84 error(s), 0 warning(s)memberMr Razz4 Jan '03 - 12:06 
copy and paste the header and source. Add to project. Compile. hmmmmmm.
\splittercontrol.h(109) : error C2653: 'std' : is not a class or namespace name.
etc etc.
 
Ok, ive now spent about an hour trying to interpret what this means:
//#include // STL Standard Template Library (add to StdAfx.h).
 
Lets see.. i know what a header file is. Should i include one where it says #include?
or, do should i add a library under project settings, or do i add a line (#include something maybe?)
to the StdAfx.h file? Should i automatically know what this means?
 
hmmm.. probably best to give up.
GeneralRe: Moak: 84 error(s), 0 warning(s)sussAnonymous8 Jan '03 - 16:35 
Mr Razz wrote:
Ok, ive now spent about an hour trying to interpret what this means:
//#include // STL Standard Template Library (add to StdAfx.h).

 
it's the HTML parsing in this forum...
 
//#include <vector> // STL Standard Template Library (add to StdAfx.h)
GeneralRe: Moak: 84 error(s), 0 warning(s)memberjchan5 Feb '04 - 13:36 
After I added "#include " to stdafx.h, I still got the following error with the new CSplitterControl code for ResizableDialog.
 
error C2955: 'vector' : use of class template requires template argument list
 
Did anyone encounter the same issue? Any solution for this? Thanks.
GeneralRe: Moak: 84 error(s), 0 warning(s)memberGE - NBGYF20 Jun '04 - 20:48 
Moak:
I got the following error,too:
error C2955: 'vector' : use of class template requires template argument list
Can you help me?
Thanks!

 


 
Hello!
Hope all to be pleasure!
GeneralRe: Moak: 84 error(s), 0 warning(s)sussAnonymous28 Jul '04 - 11:05 

For some reason, the code did not fully compile.
I modified the usage of std::vector to CArray which I'm more familiar with.
 
Otherwise, I think you can add the 'SCO_SplitterEntry' template whenever you need it, if you insist on using the STL.
 
Robin Lavallée
GeneralRe: Moak: 84 error(s), 0 warning(s)memberdream1213-wang25 May '05 - 12:48 
Hi Robin,
 
I have the same problem with you. I spent one day cannot fix it. Could you please tell me how you fixed it?
 
Thanks,
 
wang
 
wang@recombo.com
GeneralMany thanks to Moak for updated version.memberNguyen Huy Hung7 Dec '02 - 16:44 
Hi everyone.
I'm Hung Nguyen, the original author of the class.
I fell really happy because you find the class useful.
For a long time, I have not visited the site and my old email has been invalidated. I'm sorry for this. Please contact me via my new email:huyhung@hcm.fpt.vn, if necessary.
Many thanks to Moak for the updated version. I have looked at your interface. It sounds great. So, will you mind sending me your updated version.
Have a good time to everyone.
Smile | :)
GeneralUpdated version (resizeable dialog)sussMoak14 Nov '02 - 20:13 
Well, my updated version is also available for resizeable dialogs. pretty much the same (simple) interface as before.
 
Code is too long to post it here.... if someone wants to write an article please contact me (only then, thx!). Wink | ;) I will provide code and answers.
GeneralRe: Updated version (resizeable dialog)memberMoak27 Dec '02 - 10:08 
Hi, I did mail the updated version to Nguyen Huy Hung (original author).
 
PS: He did never write back, I dunno if he got my email and later patches.
GeneralRe: Updated version (resizeable dialog)memberCui Sheng30 Jan '07 - 7:20 
hi,Moak,
 
Would you like to send me a copy of the updated version available for resizeable dialogs?
 
Thanks in advanve,

 
Cui Sheng

GeneralRe: Updated version (resizeable dialog)memberMTenzer26 Jul '07 - 0:28 
hi Moak and Cui Sheng,
 
would you please send me a copy, too ?
I'm currently facing the problem to resize/reposition the splitter as my window changes size...
 
Any further ideas about resizing the splitter control are also welcome !
 
Thanks a lot,
M
GeneralRe: Updated version (resizeable dialog)memberCui Sheng24 Aug '07 - 5:09 
hi MTenzer,
 
I had studied it for two days ,but at last I gave it up, Sorry.
 
Best regards,
 
Cui Sheng

GeneralRe: Updated version (resizeable dialog)memberAndrea Cacciarru21 Nov '07 - 3:28 
Hi Moak, can I have a copy of your control? thanks in advance! Smile | :)
 

GeneralUpdated CSplitterControl versionsussMoak31 Oct '02 - 7:53 
Someone interested in an updated version of this control?
GeneralRe: Updated CSplitterControl versionmemberjohnyboy31 Oct '02 - 15:00 
Hi,
 
Yes, I am interested. Please forward.
 
Thanks,
 
John
john@1startdepot.com
GeneralRe: Updated CSplitterControl versionsussVVS1 Nov '02 - 23:17 
please send me
vvsprog@mail.ru
GeneralRe: Updated CSplitterControl versionsussMoak6 Nov '02 - 1:04 
A few more days, I'm currently solving some issues with resizeable dialogs. Will post after all runs & tested.
GeneralRe: Updated CSplitterControl versionsussMoak7 Nov '02 - 3:11 
Okay, here is my code.
 
I did run into wrong direction with resizeable dialogs, so here
is the version for static dialogs only. Couldn't contact the
original author by email, that's why I post here.
 
New features:
1. You can add controls and the splitter will resize them automatically.
2. You can show/hide controls of a pane (e.g all control left from splitter)
 
header file (load in VC and doesn't look strange formated)
 
#if !defined(AFX_SPLITTERCONTROL_H__C5E28DD6_B347_44F2_9DA0_20C4CFD50B5D__INCLUDED_)
#define AFX_SPLITTERCONTROL_H__C5E28DD6_B347_44F2_9DA0_20C4CFD50B5D__INCLUDED_
 
#if _MSC_VER > 1000
#pragma once
#endif // _MSC_VER > 1000
// SplitterControl.h : header file
//

//#include <vector>				// STL Standard Template Library (add to StdAfx.h)

/////////////////////////////////////////////////////////////////////////////
// CSplitterControl

class CSplitterControl : public CStatic
{
// Construction
public:
	CSplitterControl();
	virtual		~CSplitterControl();
											//type of splitter
	enum	{	TYPE_VERTICAL = 0, TYPE_HORIZONTAL }; 
	
	enum	{	PANE_NONE = 0,				//orientation of handled control
				PANE_LEFT = 1, PANE_RIGHT,	
				PANE_TOP = 1, PANE_BOTTOM,
				PANE_ALL = 3
			};
											//action when dragging splitter
	enum	{	DRAGGING_NOCHANGE = 0, DRAGGING_RESIZE, DRAGGING_REPOSITION };
 
	typedef struct {						//handled control entry
		HWND	hWnd;						//window handle of control
		int		nPane;						//orientation of control (left/right or top/bottom from splitter)
		int		nDraggingAction;			//resize, reposition, etc control?
	} SCO_SplitterEntry;
 
// Operations
public:
	void GetType(BOOL& bUpdateWhileDragging, BOOL& bDrawSplitter, BOOL& bDrawRubberband) const 
			{	bUpdateWhileDragging = m_bUpdateWhileDragging; 
				bDrawSplitter = m_bDrawSplitter;
				bDrawRubberband = m_bDrawRubberband; 
			}
	void SetType(BOOL bUpdateWhileDragging = TRUE, BOOL bDrawSplitter = FALSE, BOOL bDrawRubberband = FALSE) 
			{	m_bUpdateWhileDragging = bUpdateWhileDragging; 
				m_bDrawSplitter = bDrawSplitter;
				m_bDrawRubberband = bDrawRubberband; 
			}
	void SetBorder(int nBorderLeftTop = 0, int nBorderRightBottom = 0);
	void SetRangeRelative(int nDiffNegative, int nDiffPositive);
	void SetResize(BOOL bLeft, BOOL bTop, BOOL bWidth, BOOL bHeight);
	int	 GetSplitterPos();
	void SetSplitterPos(int nPos);
 
	BOOL Add(HWND hWnd, int nPane, BOOL bResize = TRUE, BOOL bDragChange = TRUE);
	BOOL Add(CWnd* pWnd, int nPane, BOOL bResize = TRUE, BOOL bDragChange = TRUE);
	BOOL Add(UINT nID, int nPane, BOOL bResize = TRUE, BOOL bDragChange = TRUE);
	BOOL Remove(HWND hWnd);
	BOOL Remove(CWnd* pWnd);
	BOOL Remove(UINT nID);
	
	BOOL IsChildControl(HWND hWnd) const;
	BOOL IsChildControl(CWnd* pWnd) const;
	BOOL IsChildControl(UINT nID) const;
	
	BOOL ShowPane(int nPane, BOOL bShow = TRUE);
	BOOL HidePane(int nPane) { return ShowPane(nPane, FALSE); }
	int  GetHiddenPane() const;
	int  GetOtherPane(int nPane) const;
	BOOL IsValidPane(int nPane) const { return nPane>=PANE_LEFT && nPane<=PANE_BOTTOM; }
	BOOL IsPaneVisible(int nPane) const;
	
	void RecalcLayout();
	void ChangePos(HWND hWnd, int dx, int dy);
	void ChangePos(CWnd* pWnd, int dx, int dy);
	void ChangeWidth(HWND hWnd, int dx, int nAlignPane);
	void ChangeWidth(CWnd* pWnd, int dx, int nAlignPane);
	void ChangeHeight(HWND hWnd, int dy, int nAlignPane);
	void ChangeHeight(CWnd* pWnd, int dy, int nAlignPane);
 
// Overrides and Protected Methods
protected:
	// ClassWizard generated virtual function overrides
	//{{AFX_VIRTUAL(CSplitterControl)
	protected:
	virtual void PreSubclassWindow();
	//}}AFX_VIRTUAL

	// Generated message map functions
	//{{AFX_MSG(CSplitterControl)
	afx_msg void	OnPaint();
	afx_msg void	OnMouseMove(UINT nFlags, CPoint point);
	afx_msg BOOL	OnSetCursor(CWnd* pWnd, UINT nHitTest, UINT message);
	afx_msg void	OnLButtonDown(UINT nFlags, CPoint point);
	afx_msg void	OnLButtonUp(UINT nFlags, CPoint point);
	afx_msg void OnMove(int x, int y);
	//}}AFX_MSG
	DECLARE_MESSAGE_MAP()
 
	HWND	GetCommonParent();
	void	LoadDefaultCursors();
	void	SetDraggingPos(CPoint& point);
	void	DrawRubberBand(BOOL bDraw);
	void	MoveSplitter(int nDelta);
	void	ResizeControls(int nDelta, int nPanes = PANE_ALL, BOOL bMoveSplitter = TRUE);
	void	ShowControls(int nPane, BOOL bShow);
	void	CalcRange(HWND hWnd, int nPane, int nSplitterPos);
	int 	CalcHiddenRange(int nPane, BOOL bShow);
	void	CalcHiddenRange(HWND hWnd, int nPane);
 
// Members
private:
	int			m_nType;					//horizontal or vertical splitter?
	BOOL		m_bUpdateWhileDragging;		//update controls while dragging mouse?
	BOOL		m_bDrawSplitter;			//draw splitter seperator? 
	BOOL		m_bDrawRubberband;			//draw rubberband when dragging mouse?

	HWND		m_hWndParent;				//parent of controls (a dialog usualy)
	std::vector<SCO_SplitterEntry> m_ControlsArray; //array of handled controls
	BOOL		m_bNotify;					//parent wants notify?
	BOOL		m_bPaneLeftTopVisible;		//controls left/top from splitter currently visible?
	BOOL		m_bPaneRightBottomVisible;	//controls right/bottom from splitter currently visible?
	BOOL		m_bButtonPressed;			//mouse button pressed?
	BOOL		m_bUpdating;				//currently updating controls?

	int			m_nBorderLeftTop;			//outer border to left/top controls (pixels)
	int			m_nBorderRightBottom;		//outer border to right/bottom controls (pixels)

	int			m_nParentPosMin, m_nParentPosMax; //possible range to drag splitter (parent coordinates)
	int			m_nParentPanePosMin, m_nParentPanePosMax; //new splitter range when hiding a pane (parent coordinates)
	int			m_nDistanceLeftTop;			//smallest distance from splitter to next left/top control (pixels)
	int			m_nDistanceRightBottom;		//smallest distance from splitter to next right/bottom control (pixels)

	int			m_nDragPosStart;			//start position when dragging splitter (screen coordinates)
	int			m_nDragPosStartOfset;		//ofset from left/top edge of splitter (pixels)
	int			m_nDragPosX, m_nDragPosY;	//current position while dragging (screen coordinates)
	int			m_nDraggedDelta;			//already dragged distance (pixels)

	HCURSOR		m_hCursorHorizontal;		//used cursors
	HCURSOR		m_hCursorVertical;
};
 
/////////////////////////////////////////////////////////////////////////////

//{{AFX_INSERT_LOCATION}}
// Microsoft Visual C++ will insert additional declarations immediately before the previous line.

#endif // !defined(AFX_SPLITTERCONTROL_H__C5E28DD6_B347_44F2_9DA0_20C4CFD50B5D__INCLUDED_)
 
source code:
 
// CSplitterControl : implementation file
 
// Description: Another splitter control for dialogs
// Authors: Mark "Moak" Seuffert <captain@pirate.de>, 
//          based on previous code from Nguyen Huy Hung (big thx)
// Date:    07.11.2002
// Version: 0.9 (TODO: update for resizeable dialog)
//
// Use: Create a static control (e.g. a picture frame) and make it a
// CSplitterControl member variable. In OnInitDialog() of your dialog use 
// member function Add() to attach all your splitted controls. When used in 
// a resizeable dialog use SetResize() and move/resize the splitter in your 
// dialog, all attached controls are handled by the splitter.
//
// Free for any use. NO WARRANTY, you run at own risk! You may not remove 
// the authors or this notice. Support open source. Please let me know of 
// any bugs/mods/improvements that you have found/implemented and I will 
// fix/incorporate them into this file.
 
#include "stdafx.h"
#include "SplitterControl.h"
 
#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif
 
//#define DEBUG_VERBOSE		//uncomment this line if you want verbose debug output
#ifndef DEBUG_VERBOSE		//... you have been warned. :-)
  #undef  TRACEV
  #define TRACEV              (void)0
#else
  #undef  TRACEV
  #define TRACEV TRACE
#endif
 
const int SCO_BORDER_WIDTH = 6;
 
/////////////////////////////////////////////////////////////////////////////
// CSplitterControl
 
CSplitterControl::CSplitterControl()
{
	m_nType = 0;							//initialize with defaults
	m_bUpdateWhileDragging = TRUE;
	m_bDrawSplitter = FALSE;
	#ifdef DEBUG_VERBOSE
	m_bDrawRubberband = TRUE;				//note: rubber band is great for debugging
	#else
	m_bDrawRubberband = FALSE;
	#endif
 
	m_hWndParent = NULL;
	m_bNotify = FALSE;
	m_bPaneLeftTopVisible = TRUE;
	m_bPaneRightBottomVisible = TRUE;
	m_bButtonPressed = FALSE;
	m_bUpdating = FALSE;
 
	m_nBorderLeftTop = 0;
	m_nBorderRightBottom = 0;
 
	m_nParentPosMin = m_nParentPosMax = 0;
	m_nParentPanePosMin = m_nParentPanePosMax = 0;
	m_nDistanceLeftTop = 0;
	m_nDistanceRightBottom = 0;
	
	m_nDragPosStart = 0;	
	m_nDragPosStartOfset = 0;
	m_nDragPosX = m_nDragPosY = 0;
	m_nDraggedDelta = 0;
	
	m_hCursorHorizontal = NULL;
	m_hCursorVertical = NULL;
 
	LoadDefaultCursors();
}
 
CSplitterControl::~CSplitterControl()
{
}
 
BEGIN_MESSAGE_MAP(CSplitterControl, CStatic)
	//{{AFX_MSG_MAP(CSplitterControl)
	ON_WM_PAINT()
	ON_WM_MOUSEMOVE()
	ON_WM_SETCURSOR()
	ON_WM_LBUTTONDOWN()
	ON_WM_LBUTTONUP()
	ON_WM_MOVE()
	//}}AFX_MSG_MAP
END_MESSAGE_MAP()
 
/////////////////////////////////////////////////////////////////////////////
// CSplitterControl member functions and message handlers
 
// Create initial settings
void CSplitterControl::PreSubclassWindow() 
{
	CRect rect;								//get initial width
	GetClientRect(rect);					//detect type (horizontal/vertical)
	m_nType = (rect.Width() < rect.Height())?TYPE_VERTICAL:TYPE_HORIZONTAL;
											
	m_hWndParent = GetCommonParent();		//get parent for all handled controls
	ASSERT(m_hWndParent);
	
	m_bNotify = GetStyle() & SS_NOTIFY;		//splitter wants notify?
	ModifyStyle(0, SS_NOTIFY | WS_CHILD);
											//reset splitter range
	SetBorder(SCO_BORDER_WIDTH, SCO_BORDER_WIDTH);
 
	CStatic::PreSubclassWindow();
}
 
// Get parent of splitter control (used as parent for all handled controls)
HWND CSplitterControl::GetCommonParent()
{
	return GetParent()->GetSafeHwnd();
}
 
// Load default resize cursors
void CSplitterControl::LoadDefaultCursors()
{
	m_hCursorHorizontal = AfxGetApp()->LoadStandardCursor(IDC_SIZEWE);
	m_hCursorVertical = AfxGetApp()->LoadStandardCursor(IDC_SIZENS);
}
 
// Return splitter position (parent coordinates)
int CSplitterControl::GetSplitterPos()
{
	ASSERT(m_hWndParent);
 
	RECT rect;
	::GetWindowRect(m_hWnd, &rect);		
	
	POINT point = { rect.left, rect.top };
    ::ScreenToClient(m_hWndParent, (LPPOINT)&point);
	
	return (m_nType==TYPE_VERTICAL)?point.x:point.y;
}
 
// Set splitter position (parent coordinates)
void CSplitterControl::SetSplitterPos(int nPos)
{
	if(nPos >= m_nParentPosMin && nPos <= m_nParentPosMax)
	{
		int nDelta = nPos - GetSplitterPos(); 
		ResizeControls(nDelta);
		//TODO: that's all here?
	}
}
 
// Set splitter range
void CSplitterControl::SetBorder(int nBorderLeftTop, int nBorderRightBottom)
{
	//Note: Set splitter range by specifying distance to border of controls.
 
	ASSERT(nBorderLeftTop>=0 && nBorderRightBottom>=0);
					
	m_nBorderLeftTop = nBorderLeftTop;
	m_nBorderRightBottom = nBorderRightBottom;
	
	TRACEV("[SCO]   SetRangeBorder %d, %d\n", m_nBorderLeftTop, m_nBorderRightBottom);
 
	RecalcLayout();
}
 
// Set relative splitter range
void CSplitterControl::SetRangeRelative(int nDiffNegative, int nDiffPositive)
{
	//Note: Set splitter range by specifying range relative to current splitter.
	
	ASSERT(nDiffNegative<=0 && nDiffPositive>=0);
 
	int nSplitterPos = GetSplitterPos();	//get current splitter position (parent coordinates)
	
	m_nParentPosMin = nSplitterPos + nDiffNegative;
	m_nParentPosMax = nSplitterPos + nDiffPositive;
 
	TRACEV("[SCO]   SetRangeRelative %d (base pos %d) +%d\n", nDiffNegative, nSplitterPos, nDiffPositive);
}
 
void CSplitterControl::SetResize(BOOL bLeft, BOOL bTop, BOOL bWidth, BOOL bHeight)
{
 
}
 
// Recalculate layout 
void CSplitterControl::RecalcLayout()
{
	ASSERT(m_hWndParent);
 
	if(m_bUpdating) return;					//prevent multiple calls (resizing/repositioning would cause them)
 
	//Reset range to parent dimensions
	int nSplitterPos = GetSplitterPos();	//get current splitter position (parent coordinates)
 
	RECT rect;
	::GetClientRect(m_hWndParent, &rect);	
	if(m_nType == TYPE_VERTICAL)
	{
		m_nParentPosMin = rect.left;
		m_nParentPosMax = rect.right;
		m_nDistanceLeftTop = m_nDistanceRightBottom = rect.right-rect.left;
	} else {
		m_nParentPosMin = rect.top;
		m_nParentPosMax = rect.bottom;
		m_nDistanceLeftTop = m_nDistanceRightBottom = rect.bottom-rect.top;
	}
	
	//Calculate limits from handled controls
	std::vector<SCO_SplitterEntry>::const_iterator itControl;
	for(itControl=m_ControlsArray.begin();itControl!=m_ControlsArray.end();itControl++)
	{										//only resizeable controls count here
		if((*itControl).nDraggingAction == DRAGGING_RESIZE)			
			CalcRange((*itControl).hWnd, (*itControl).nPane, nSplitterPos);
	}
 
	TRACEV("[SCO]   RecalcLayout PosSplitter %d, PosMin=%d, PosMax=%d (distance %d, %d)\n", nSplitterPos, m_nParentPosMin, m_nParentPosMax, m_nDistanceLeftTop, m_nDistanceRightBottom);
}
 
void CSplitterControl::CalcRange(HWND hWnd, int nPane, int nSplitterPos)
{
	//Note: Range calculation is based on splitter's left/top position. Ranges will be 
	//adjusted so that splitter's left/top edge never overruns a control, pretty simple.
	
	ASSERT(hWnd);
	ASSERT(m_hWndParent);
	ASSERT(IsValidPane(nPane));
 
	RECT rect;								//get position in parent coordinates 
	::GetWindowRect(hWnd, &rect);
    ::ScreenToClient(m_hWndParent, (LPPOINT)&rect);
	::ScreenToClient(m_hWndParent, ((LPPOINT)&rect)+1);
 
	if(m_nType == TYPE_VERTICAL)			//allow splitter within reasonable limits
	{
		if(nPane == PANE_LEFT) {
			int nDistance = nSplitterPos - rect.right;
			if(rect.left + nDistance + m_nBorderLeftTop > m_nParentPosMin) 
				m_nParentPosMin = rect.left + nDistance + m_nBorderLeftTop;
			if(nDistance < m_nDistanceLeftTop) 
				m_nDistanceLeftTop = nDistance;
		} else {
			int nDistance = rect.left - nSplitterPos;
			if(rect.right - nDistance - m_nBorderRightBottom < m_nParentPosMax) 
				m_nParentPosMax = rect.right - nDistance - m_nBorderRightBottom;
			if(nDistance < m_nDistanceRightBottom) 
				m_nDistanceRightBottom = nDistance;
		}
	} else {
		if(nPane == PANE_TOP) {
			int nDistance = nSplitterPos - rect.bottom;
			if(rect.top + nDistance + m_nBorderLeftTop > m_nParentPosMin) 
				m_nParentPosMin = rect.top + nDistance + m_nBorderLeftTop;
			if(nDistance < m_nDistanceLeftTop) 
				m_nDistanceLeftTop = nDistance;
		} else {
			int nDistance = rect.top - nSplitterPos;
			if(rect.bottom - nDistance - m_nBorderRightBottom < m_nParentPosMax) 
				m_nParentPosMax = rect.bottom - nDistance - m_nBorderRightBottom;
			if(nDistance < m_nDistanceRightBottom) 
				m_nDistanceRightBottom = nDistance;
		}
	}
}
 
// Calculate range of a hidden pane
int CSplitterControl::CalcHiddenRange(int nPane, BOOL bShow)
{
	ASSERT(IsValidPane(nPane));
	ASSERT(PANE_LEFT==PANE_TOP && PANE_RIGHT == PANE_BOTTOM);
	ASSERT(m_hWndParent);
 
	int nPos = GetSplitterPos();			//get current splitter position (parent coordinates)
 
	if(!bShow)								//hide this pane?
	{
		m_nParentPanePosMin = m_nParentPanePosMax = nPos;
											//calculate range from handled controls
		std::vector<SCO_SplitterEntry>::const_iterator itControl;
		for(itControl=m_ControlsArray.begin();itControl!=m_ControlsArray.end();itControl++)
		{
			if((*itControl).nPane == nPane)			
				CalcHiddenRange((*itControl).hWnd, nPane);
		}
											//return distance to current splitter position
		return (nPane==PANE_LEFT)?m_nParentPanePosMin-nPos:m_nParentPanePosMax-nPos;
	
	} else {								//when showing pane again
											//... just return reverse distance
		return (nPane==PANE_LEFT)?nPos-m_nParentPanePosMin:nPos-m_nParentPanePosMax;
	}
}
 
void CSplitterControl::CalcHiddenRange(HWND hWnd, int nPane)
{
	//Note: Hiden range is calculated relative to splitter's left/top position. Ranges 
	//will be adjusted so that the other (visible) pane can occupy the whole space.
 
	ASSERT(hWnd);
	ASSERT(m_hWndParent);
	ASSERT(IsValidPane(nPane));
 
	RECT rect;								//get position in parent coordinates 
	::GetWindowRect(hWnd, &rect);
    ::ScreenToClient(m_hWndParent, (LPPOINT)&rect);
	::ScreenToClient(m_hWndParent, ((LPPOINT)&rect)+1);
 
	if(m_nType == TYPE_VERTICAL)			//calculate a new (virtual) splitter range
	{
		if(nPane == PANE_LEFT) {
			if(rect.left - m_nDistanceRightBottom < m_nParentPanePosMin) 
				m_nParentPanePosMin = rect.left - m_nDistanceRightBottom;
		} else {
			if(rect.right + m_nDistanceLeftTop > m_nParentPanePosMax) 
				m_nParentPanePosMax = rect.right + m_nDistanceLeftTop;
		}
	} else {
		if(nPane == PANE_TOP) {
			if(rect.top - m_nDistanceRightBottom < m_nParentPanePosMin) 
				m_nParentPanePosMin = rect.top - m_nDistanceRightBottom;
		} else {
			if(rect.bottom + m_nDistanceLeftTop > m_nParentPanePosMax) 
				m_nParentPanePosMax = rect.bottom + m_nDistanceLeftTop;
		}
	}
}
 
// Show or hide requested pane
BOOL CSplitterControl::ShowPane(int nPane, BOOL bShow)
{
	ASSERT(IsValidPane(nPane));
 
	if(bShow) {								//plausability check, only one pane can be hidden at a time
		if(nPane != GetHiddenPane()) return FALSE;
	} else {
		if(GetHiddenPane()) return FALSE;
	}
 
	ShowControls(nPane, bShow);				//show/hide controls of requested pane
	ShowWindow(bShow);						//splitter is hidden while one pane is hidden
											//resize/reposition controls of remaining pane
	int nDelta = CalcHiddenRange(nPane, bShow);
	ResizeControls(nDelta, GetOtherPane(nPane), FALSE);
	
	return TRUE;							//pane update was sucessfull
}
 
// Show or hide controls of a pane 
void CSplitterControl::ShowControls(int nPane, BOOL bShow)
{											
	ASSERT(IsValidPane(nPane));
	ASSERT(PANE_LEFT==PANE_TOP && PANE_RIGHT == PANE_BOTTOM);
 
	std::vector<SCO_SplitterEntry>::const_iterator itControl;
	for(itControl=m_ControlsArray.begin();itControl!=m_ControlsArray.end();itControl++)
	{
		if((*itControl).nPane == nPane)			
			::ShowWindow((*itControl).hWnd, bShow);
	}
 
	if(nPane == PANE_LEFT) m_bPaneLeftTopVisible = bShow;
	else m_bPaneRightBottomVisible = bShow;
}
 
// Check if pane is visible
BOOL CSplitterControl::IsPaneVisible(int nPane) const
{
	ASSERT(IsValidPane(nPane));
	ASSERT(PANE_LEFT==PANE_TOP && PANE_RIGHT == PANE_BOTTOM);
 
	return (nPane==PANE_LEFT)?m_bPaneLeftTopVisible:m_bPaneRightBottomVisible;
}
 
// Return which pane is invisible
int CSplitterControl::GetHiddenPane() const
{
	if(!m_bPaneLeftTopVisible) return (m_nType == TYPE_VERTICAL)?PANE_LEFT:PANE_TOP;
	if(!m_bPaneRightBottomVisible) return (m_nType == TYPE_VERTICAL)?PANE_RIGHT:PANE_BOTTOM;
	return PANE_NONE;
}
 
int CSplitterControl::GetOtherPane(int nPane) const
{
	ASSERT(IsValidPane(nPane));
	ASSERT(PANE_LEFT==PANE_TOP && PANE_RIGHT == PANE_BOTTOM);
 
	return (nPane==PANE_LEFT)?PANE_RIGHT:PANE_LEFT;
}
 
// Add control to list of handled controls
BOOL CSplitterControl::Add(HWND hWnd, int nPane, BOOL bResize, BOOL bDragChange)
{
	ASSERT(hWnd);
	ASSERT(IsValidPane(nPane));
	ASSERT(m_hWndParent == ::GetParent(hWnd));
 
	SCO_SplitterEntry Control;				//store control preferences
	Control.hWnd = hWnd;
	Control.nPane = nPane;
	Control.nDraggingAction = bResize?DRAGGING_RESIZE:DRAGGING_REPOSITION;
	if(!bDragChange) Control.nDraggingAction = DRAGGING_NOCHANGE;
 
	ASSERT(!IsChildControl(hWnd));			//make sure a control is handled only once
	m_ControlsArray.push_back(Control);		//add it to handled controls
 
	if(Control.nDraggingAction == DRAGGING_RESIZE)
	{										//if it's resizeable control, update splitter range
		CalcRange(hWnd, nPane, GetSplitterPos());		
	}
 
	return TRUE;
}
 
BOOL CSplitterControl::Add(CWnd* pWnd, int nPane, BOOL bResize, BOOL bDragChange)
{
	return Add(pWnd->GetSafeHwnd(), nPane, bResize, bDragChange);
}
 
BOOL CSplitterControl::Add(UINT nID, int nPane, BOOL bResize, BOOL bDragChange)
{
	ASSERT(m_hWndParent);
	return Add(::GetDlgItem(m_hWndParent, nID), nPane, bResize, bDragChange);
}
 
// Check if control is already handled by splitter
BOOL CSplitterControl::IsChildControl(HWND hWnd) const
{
	std::vector<SCO_SplitterEntry>::const_iterator itControl;
	for(itControl=m_ControlsArray.begin();itControl!=m_ControlsArray.end();itControl++)
		if((*itControl).hWnd == hWnd) return TRUE;
 
	return FALSE;
}
 
BOOL CSplitterControl::IsChildControl(CWnd* pWnd) const
{
	return IsChildControl(pWnd->GetSafeHwnd());
}
 
BOOL CSplitterControl::IsChildControl(UINT nID) const
{
	ASSERT(m_hWndParent);
	return IsChildControl(::GetDlgItem(m_hWndParent, nID));
}
 
// Remove control from list of handled controls
BOOL CSplitterControl::Remove(HWND hWnd)
{
	std::vector<SCO_SplitterEntry>::iterator itControl;
	for(itControl=m_ControlsArray.begin();itControl!=m_ControlsArray.end();itControl++)
	{
		if((*itControl).hWnd == hWnd)		//control found?
		{									//remove it from handled controls
			m_ControlsArray.erase(itControl);
			RecalcLayout();					//we need to recalculate splitter layout
			return TRUE;
		}
	}
	
	return FALSE;
}
 
BOOL CSplitterControl::Remove(CWnd* pWnd)
{
	return Remove(pWnd->GetSafeHwnd());
}
 
BOOL CSplitterControl::Remove(UINT nID)
{
	ASSERT(m_hWndParent);
	return Remove(::GetDlgItem(m_hWndParent, nID));
}
 
// Draw splitter control
void CSplitterControl::OnPaint() 
{
	if(m_bDrawSplitter)						//draw splitter separator between panes?
	{										//... normaly splitter itself is not visible
		CPaintDC dc(this);						
		CRect rcClient;
		GetClientRect(rcClient);			//get the client coords
 
		dc.Draw3dRect(rcClient,				//draw rectangle
			::GetSysColor(COLOR_BTNHIGHLIGHT),
			::GetSysColor(COLOR_BTNSHADOW));
		
	} else ValidateRect(NULL);				//validate client rect (or window update becomes screwed up)
}
 
// Move splitter control (e.g. from a dialog resize)
void CSplitterControl::OnMove(int x, int y) 
{
	CStatic::OnMove(x, y);
 
	TRACEV("[SCO]   OnMove PosSplitter %d, PosMinOld=%d, PosMaxOld=%d (distance %d, %d)\n", x, m_nParentPosMin, m_nParentPosMax, m_nDistanceLeftTop, m_nDistanceRightBottom);
 
	int	nSplitterPos = x;					//equivalent to GetSplitterPos() here
	ASSERT(nSplitterPos>=m_nParentPosMin);  //Oops, splitter did not stay within range
											//...make sure your resize control runs well configured! That's what GetMinPos() and GetMinLength() are thought for.
	RecalcLayout();
}
 
// Set cursor (depending on type of splitter control)
BOOL CSplitterControl::OnSetCursor(CWnd* pWnd, UINT nHitTest, UINT message) 
{
	if(m_nType == TYPE_VERTICAL) ::SetCursor(m_hCursorHorizontal);
	else ::SetCursor(m_hCursorVertical);
 
	return 0;
}
 
// Mouse button pressed
void CSplitterControl::OnLButtonDown(UINT nFlags, CPoint point) 
{
	//Note: Start dragging splitter
 
	CStatic::OnLButtonDown(nFlags, point);
	
	if(!m_bButtonPressed)
	{
		m_bButtonPressed = TRUE;
		SetCapture();						//claim mouse input
		
		CRect rcWnd;						//store start position (screen coordinates)
		GetWindowRect(rcWnd);
 
		if(m_nType == TYPE_VERTICAL)		
		{
			m_nDragPosStart = m_nDragPosX = rcWnd.left + point.x;	
			m_nDragPosStartOfset = point.x;
		} else {
			m_nDragPosStart = m_nDragPosY = rcWnd.top + point.y;
			m_nDragPosStartOfset = point.y;
		}
		m_nDraggedDelta = 0;
 
		DrawRubberBand(TRUE);				//draw rubber band
	}
}
 
// Splitter control is dragged with mouse
void CSplitterControl::OnMouseMove(UINT nFlags, CPoint point) 
{
	if(m_bButtonPressed)
	{
		DrawRubberBand(FALSE);				//remove last rubber band
		SetDraggingPos(point);				//update current position
 
		if(m_bUpdateWhileDragging)			//update controls while dragging mouse?
		{
			int nDelta;						//calculate dragged distance
			if(m_nType == TYPE_VERTICAL)
				nDelta = m_nDragPosX - m_nDragPosStart;
			else
				nDelta = m_nDragPosY - m_nDragPosStart;
											//resize/reposition controls
			ResizeControls(nDelta-m_nDraggedDelta);			
			m_nDraggedDelta = nDelta;
		}
 
		DrawRubberBand(TRUE);				//draw new rubber band
	}
 
	CStatic::OnMouseMove(nFlags, point);
}
 
// Mouse button released
void CSplitterControl::OnLButtonUp(UINT nFlags, CPoint point) 
{
	//Note: Stop dragging splitter
 
	if(m_bButtonPressed)
	{
		m_bButtonPressed = FALSE;
		ReleaseCapture();					//back to normal mouse input processing
 
		DrawRubberBand(FALSE);				//remove last rubber band
		SetDraggingPos(point);				//update current position
 
		int nDelta;							//calculate dragged distance
		if(m_nType == TYPE_VERTICAL)
			nDelta = m_nDragPosX - m_nDragPosStart;
		else
			nDelta = m_nDragPosY - m_nDragPosStart;
											//resize/reposition controls
		ResizeControls(nDelta-m_nDraggedDelta);	
	}
 
	CStatic::OnLButtonUp(nFlags, point);
}
 
// Set current dragging position
void CSplitterControl::SetDraggingPos(CPoint& point) 
{
	ASSERT(m_hWndParent);
	if(m_nParentPosMin == m_nParentPosMax) return;
 
	ClientToScreen(&point);					//check if we are within limits
	::ScreenToClient(m_hWndParent, &point);
 
	if(m_nType == TYPE_VERTICAL)
	{
		if(point.x < m_nParentPosMin + m_nDragPosStartOfset) 
			point.x = m_nParentPosMin + m_nDragPosStartOfset;
		else if(point.x > m_nParentPosMax + m_nDragPosStartOfset) 
			point.x = m_nParentPosMax + m_nDragPosStartOfset;
	} else {
		if(point.y < m_nParentPosMin + m_nDragPosStartOfset) 
			point.y = m_nParentPosMin + m_nDragPosStartOfset;
		else if(point.y > m_nParentPosMax + m_nDragPosStartOfset) 
			point.y = m_nParentPosMax + m_nDragPosStartOfset;
	}
 
	::ClientToScreen(m_hWndParent, &point);	//store new position (screen coordinates)
	m_nDragPosX = point.x;
	m_nDragPosY = point.y;
}
 
// Draw or remove a rubber band
void CSplitterControl::DrawRubberBand(BOOL bDraw)
{
	if(!m_bDrawRubberband) return;			//rubber band requested?
	
	CWindowDC dc(NULL);
 
	CRect rcWnd;							//draw rubber band
	GetWindowRect(&rcWnd);
	
	if(!m_bUpdateWhileDragging)
		rcWnd.OffsetRect(m_nDragPosX-m_nDragPosStart, 0);
											
	CSize sizeBar(rcWnd.Width(), rcWnd.Height());
    dc.DrawDragRect(&rcWnd, sizeBar, NULL, sizeBar);
 
	#ifdef DEBUG_VERBOSE					//also show splitter range in verbose debug mode
	int nOldRop = dc.SetROP2(R2_NOTXORPEN);
	CPen pen(PS_DOT, 1, RGB(0,0,0));
	CPen* pOldPen = dc.SelectObject(&pen);
	
	if(m_nType == TYPE_VERTICAL)
	{
		CPoint point(m_nParentPosMin, 0);
		::ClientToScreen(m_hWndParent, &point);
		dc.MoveTo(point.x, rcWnd.top);
		dc.LineTo(point.x, rcWnd.bottom);
 
		point.x = m_nParentPosMax;
		::ClientToScreen(m_hWndParent, &point);
		dc.MoveTo(point.x, rcWnd.top);
		dc.LineTo(point.x, rcWnd.bottom);
	} else 	{
		CPoint point(0, m_nParentPosMin);
		::ClientToScreen(m_hWndParent, &point);
		dc.MoveTo(rcWnd.left, point.y);
		dc.LineTo(rcWnd.right, point.y);
 
		point.y = m_nParentPosMax;
		::ClientToScreen(m_hWndParent, &point);
		dc.MoveTo(rcWnd.left, point.y);
		dc.LineTo(rcWnd.right, point.y);
	}
 
	dc.SetROP2(nOldRop);
	dc.SelectObject(pOldPen);
	#endif
}
 
// Resize and reposition all handled controls
void CSplitterControl::ResizeControls(int nDelta, int nPanes, BOOL bMoveSplitter)
{
	ASSERT(m_hWndParent);
	ASSERT(nPanes != PANE_NONE);
 
	if(!nDelta) return;					//nothing to do?
	m_bUpdating = TRUE;
 
	TRACEV("[SCO]   ResizeControls delta %d, %sPosSplitter %d\n", nDelta, bMoveSplitter?"":"virtual ", GetSplitterPos()+nDelta);
 
	//First reposition splitter itself
	if(bMoveSplitter) MoveSplitter(nDelta);				
 
	//Update controls
	if(m_nType == TYPE_VERTICAL)
	{	
		std::vector<SCO_SplitterEntry>::iterator itControl;
		for(itControl=m_ControlsArray.begin();itControl!=m_ControlsArray.end();itControl++)
		{
			switch((*itControl).nDraggingAction)
			{
			case DRAGGING_RESIZE: 		//resize control
					if((*itControl).nPane == PANE_LEFT) {
						if(nPanes & PANE_LEFT) ChangeWidth((*itControl).hWnd, nDelta, PANE_LEFT);
					} else {
						if(nPanes & PANE_RIGHT) ChangeWidth((*itControl).hWnd, -nDelta, PANE_RIGHT);
					}
					break;
			case DRAGGING_REPOSITION:	//reposition control
					if((*itControl).nPane == PANE_LEFT) {
						if(nPanes & PANE_LEFT) ChangePos((*itControl).hWnd, nDelta, 0);
					} else {
						if(nPanes & PANE_RIGHT) ChangePos((*itControl).hWnd, nDelta, 0);
					}
					break;
			}
		}
	} else {
		std::vector<SCO_SplitterEntry>::iterator itControl;
		for(itControl=m_ControlsArray.begin();itControl!=m_ControlsArray.end();itControl++)
		{
			switch((*itControl).nDraggingAction)
			{
			case DRAGGING_RESIZE: 		//resize control
					if((*itControl).nPane == PANE_TOP) {
						if(nPanes & PANE_TOP) ChangeHeight((*itControl).hWnd, nDelta, PANE_TOP);
					} else {
						if(nPanes & PANE_BOTTOM) ChangeHeight((*itControl).hWnd, -nDelta, PANE_BOTTOM);
					}
					break;
			case DRAGGING_REPOSITION:	//reposition control
					if((*itControl).nPane == PANE_TOP) {
						if(nPanes & PANE_TOP) ChangePos((*itControl).hWnd, 0, nDelta);
					} else {
						if(nPanes & PANE_BOTTOM) ChangePos((*itControl).hWnd, 0, nDelta);
					}
					break;
			}
		}
 
	}
	::UpdateWindow(m_hWndParent);		//now parent window needs a repaint
										//...for previously invalidated regions
	//Send notification if requested
	if(m_bNotify)						
	{
		NMHDR nmh;
		nmh.hwndFrom = m_hWnd;
		nmh.idFrom   = GetDlgCtrlID();
		nmh.code     = nDelta;
 
		ASSERT(m_hWndParent && nmh.idFrom);
		::SendMessage(m_hWndParent, WM_NOTIFY, nmh.idFrom, (LPARAM)&nmh);
	}
 
	m_bUpdating = FALSE;
}
 
// Update position of splitter control
void CSplitterControl::MoveSplitter(int nDelta)
{
	if(m_nType == TYPE_VERTICAL) ChangePos(m_hWnd, nDelta, 0);
	else ChangePos(m_hWnd, 0, nDelta);
}
 
// Change width of a control
void CSplitterControl::ChangeWidth(HWND hWnd, int dx, int nAlignPane)
{
	ASSERT(hWnd);
	ASSERT(m_hWndParent == ::GetParent(hWnd));
	ASSERT(nAlignPane==PANE_LEFT || nAlignPane==PANE_RIGHT);
 
	RECT rect;
	::GetWindowRect(hWnd, &rect);
	::ScreenToClient(m_hWndParent, (LPPOINT)&rect);
	::ScreenToClient(m_hWndParent, ((LPPOINT)&rect)+1);
 
	if(dx<0) ::InvalidateRect(m_hWndParent, &rect, TRUE);
 
	if(nAlignPane == PANE_LEFT) rect.right += dx;
	else rect.left -= dx;
	
	::MoveWindow(hWnd, rect.left, rect.top,	rect.right - rect.left,	rect.bottom - rect.top, FALSE);
	::InvalidateRect(m_hWndParent, &rect, FALSE);
}
 
void CSplitterControl::ChangeWidth(CWnd* pWnd, int dx, int nAlignPane)
{
	ChangeWidth(pWnd->GetSafeHwnd(), dx, nAlignPane);
}
 
// Change height of a control
void CSplitterControl::ChangeHeight(HWND hWnd, int dy, int nAlignPane)
{
	ASSERT(hWnd);
	ASSERT(m_hWndParent == ::GetParent(hWnd));
	ASSERT(nAlignPane==PANE_TOP || nAlignPane==PANE_BOTTOM);
 
	RECT rect;
	::GetWindowRect(hWnd, &rect);
	::ScreenToClient(m_hWndParent, (LPPOINT)&rect);
	::ScreenToClient(m_hWndParent, ((LPPOINT)&rect)+1);
	
	if(dy<0) ::InvalidateRect(m_hWndParent, &rect, TRUE);
 
	if(nAlignPane == PANE_TOP) rect.bottom += dy;
	else rect.top -= dy;
 
	::MoveWindow(hWnd, rect.left, rect.top,	rect.right - rect.left,	rect.bottom - rect.top, FALSE);
	::InvalidateRect(m_hWndParent, &rect, FALSE);
}
 
void CSplitterControl::ChangeHeight(CWnd *pWnd, int dy, int nAlignPane)
{
	ChangeHeight(pWnd->GetSafeHwnd(), dy, nAlignPane);
}
 
// Change position of a control
void CSplitterControl::ChangePos(HWND hWnd, int dx, int dy)
{
	ASSERT(hWnd);
	ASSERT(m_hWndParent == ::GetParent(hWnd));
 
	RECT rect;
	::GetWindowRect(hWnd, &rect);
	::ScreenToClient(m_hWndParent, (LPPOINT)&rect);
	::ScreenToClient(m_hWndParent, ((LPPOINT)&rect)+1);
 
	::InvalidateRect(m_hWndParent, &rect, TRUE);
	::OffsetRect(&rect, dx, dy);
 
	::MoveWindow(hWnd, rect.left, rect.top,	rect.right - rect.left,	rect.bottom - rect.top, FALSE);
	::InvalidateRect(m_hWndParent, &rect, FALSE);
}
 
void CSplitterControl::ChangePos(CWnd* pWnd, int dx, int dy)
{
	ChangePos(pWnd->GetSafeHwnd(), dx, dy);
}

GeneralRe: Updated CSplitterControl versionmembersk8ing5 Nov '06 - 19:36 
int nSplitterPos = x; //equivalent to GetSplitterPos() here
 
It's not equivalent, this works only for vertical splitters, therefore it should be nSplitterPos = GetSplitterPos();
 

 
Thaks for the control Wink | ;)
GeneralRe: Updated CSplitterControl versionmemberZiem2 Mar '07 - 8:24 
Hi Moak,
 
I'm also interested in your version.
 
Thanks,
Thomas
GeneralPossible bug in CSplitterControl::SetRange(int nSubtraction, int nAddition, int nRoot)memberAndreas.Stuebinger28 Oct '02 - 0:01 
Hi,
 
on using your splitter control (which saved me a lot of time)
together with cdxCDynamicCDialog I had to fix SetRange as
follows:
 
      if (nRoot < 0)
      {
            CRect rcWnd;
            GetWindowRect(rcWnd);
 
            GetParent()->ScreenToClient(rcWnd); // <-- Fix: Need to work in client coordinates!!
 
            if (m_nType == SPS_VERTICAL)
                  nRoot = rcWnd.left + rcWnd.Width() / 2;
            else // if m_nType == SPS_HORIZONTAL
                  nRoot = rcWnd.top + rcWnd.Height() / 2;
      }
 
Ciao,
            Andreas

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Permalink | Advertise | Privacy | Mobile
Web04 | 2.6.130516.1 | Last Updated 13 Jul 2002
Article Copyright 2002 by Hung Nguyen
Everything else Copyright © CodeProject, 1999-2013
Terms of Use
Layout: fixed | fluid