Click here to Skip to main content
Click here to Skip to main content
Add your own
alternative version

Fully owner drawn tab control

, 31 Jan 2000 CPOL
A better looking tab control
lbtab_demo.zip
LBTabDemo
res
LBTabDemoDoc.ico
LBTabDemo.ico
Toolbar.bmp
LBTabDemo.clw
LBTabDemo.dsp
Doc
oldtab_inline_demo.gif
lbtab_stacked_demo.gif
lbtab_inline_demo.gif
LBTabDemo.dsw
lbtab_src.zip
LBTabSrc
Doc
oldtab_inline_demo.gif
lbtab_stacked_demo.gif
lbtab_inline_demo.gif
// LBSpinButtonCtrl.cpp : implementation file
//
// Copyright � 1999 Oleg Lobach, All Rights Reserved.
//
// mailto:oleglb@mail.ru
//or
// mailto:oleg@alexen.ru
//
//
// This source file may be redistributed unmodified by any means PROVIDING 
// it is NOT sold for profit without the authors expressed written 
// consent, and providing that this notice and the authors name and all 
// copyright notices remain intact. This software is by no means to be 
// included as part of any third party components library, or as part any
// development solution that offers MFC extensions that are sold for profit. 
// 
// If the source code is used in any commercial applications then a statement 
// along the lines of:
// 
// "Portions Copyright � 1999 Oleg Lobach" must be included in the "Startup 
// Banner", "About Box" or "Printed Documentation". This software is provided 
// "as is" without express or implied warranty. The author accepts no
// liability for any damage/loss of business that this product may cause.
//
/////////////////////////////////////////////////////////////////////////////
//****************************************************************************

#include "stdafx.h"
#include "LBSpinButtonCtrl.h"

#pragma warning(disable:4786)
#include <map>


#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif

using namespace std;
typedef map <HWND,HWND> HWNDMAP;
static HWNDMAP gHandleMap;


static LRESULT CALLBACK FilterBuddyMsgProc(int code,WPARAM wParam,  LPARAM lParam );
static HHOOK ghHook=NULL;

/////////////////////////////////////////////////////////////////////////////
// CLBSpinButtonCtrl

CLBSpinButtonCtrl::CLBSpinButtonCtrl():m_hWndBuddy(NULL),
				   m_bVertical(true),m_nSpinAlign(Outside),
				   m_bDefaultDirection(true),m_bBuddyIsEdit(false),
				   m_rctIsPressed(0,0,0,0),m_bActiveSpinPressed(false),
				   m_bAutoDisable(true)
{
	
}

CLBSpinButtonCtrl::~CLBSpinButtonCtrl()
{
}


BEGIN_MESSAGE_MAP(CLBSpinButtonCtrl, CSpinButtonCtrl)
	//{{AFX_MSG_MAP(CLBSpinButtonCtrl)
	ON_NOTIFY_REFLECT_EX(UDN_DELTAPOS, OnDeltapos)
	ON_WM_LBUTTONDOWN()
	ON_WM_ERASEBKGND()
	ON_WM_LBUTTONUP()
	ON_WM_DESTROY()
	ON_WM_PAINT()
	//}}AFX_MSG_MAP

END_MESSAGE_MAP()

/////////////////////////////////////////////////////////////////////////////
// CLBSpinButtonCtrl message handlers

void CLBSpinButtonCtrl::PreSubclassWindow() 
{
	//I need custom pens to draw up_down control

	COLORREF clr=::GetSysColor(COLOR_3DDKSHADOW);
	m_penDarkShadow=::CreatePen(PS_SOLID,0,clr);	

	clr=::GetSysColor(COLOR_BTNSHADOW);
	m_penShadow=::CreatePen(PS_SOLID,0,clr);

	clr=::GetSysColor(COLOR_3DHILIGHT);
	m_penLight=::CreatePen(PS_SOLID,0,clr);

	clr=::GetSysColor(COLOR_3DLIGHT);
	m_penLightShadow=::CreatePen(PS_SOLID,0,clr);

	clr=::GetSysColor(COLOR_BTNFACE);
	m_penButtonFace=::CreatePen(PS_SOLID,0,clr);


	Init();
	CSpinButtonCtrl::PreSubclassWindow();
}

void CLBSpinButtonCtrl::OnPaint() 
{
	if(m_bAutoDisable)
	{
		//Draw control by hands

		CPaintDC RealDC(this); // device context

		CRect rctPaint=m_rctClient;
		CRect rcPaintUp,rcPaintDown;

		//To get rid of flickering we are drawing to memory DC (dc),
		//and then BitBlting it to the screen.
		//So we have to create compatible memory DC and select bitmap into it.

		CDC dc;
		CBitmap bmpMem,*pOldMemBmp;
		dc.CreateCompatibleDC(&RealDC);
		bmpMem.CreateCompatibleBitmap(&RealDC,rctPaint.Width(),rctPaint.Height());
		pOldMemBmp=dc.SelectObject(&bmpMem);

		//As so I bypassed WM_ERASEBCKGND, do its job here 
		dc.FillSolidRect(&rctPaint,::GetSysColor(COLOR_BTNFACE));


		if(::IsWindow(m_hWndBuddy) && m_nSpinAlign!=Outside )
		{
			rctPaint.top+=2;
			rctPaint.bottom-=2;
			if(m_nSpinAlign == OnRightInside)
				rctPaint.right-=2;
			else
				rctPaint.left+=2;
		}

		rcPaintUp= rctPaint;

		//Draw control, depending on it alignment and orientation
		if(m_bVertical)
		{
			rcPaintUp.bottom=rcPaintUp.top+rcPaintUp.Height()/2;
			rcPaintDown=rctPaint;
			rcPaintDown.top=rcPaintDown.bottom-rcPaintUp.Height();

			dc.DrawFrameControl(&rcPaintUp,DFC_SCROLL,DFCS_SCROLLUP);
			dc.DrawFrameControl(&rcPaintDown,DFC_SCROLL,DFCS_SCROLLDOWN);

		}
		else
		{
			rcPaintUp.right=rcPaintUp.left+rcPaintUp.Width()/2;
			rcPaintDown=rctPaint;
			rcPaintDown.left=rcPaintDown.right-rcPaintUp.Width();

			dc.DrawFrameControl(&rcPaintUp,DFC_SCROLL,DFCS_SCROLLLEFT);
			dc.DrawFrameControl(&rcPaintDown,DFC_SCROLL,DFCS_SCROLLRIGHT);
		
		}
		
		if(::IsWindow(m_hWndBuddy) && m_nSpinAlign!=Outside )
		{
			//We are inside buddy,so have to draw buddy's border around
			//CLBSpinButtonCtrl
			
			//We use m_rctClient as so rctPaint may have been already modified.
			if(m_nSpinAlign == OnRightInside)
			{
				::SelectObject(dc.m_hDC,m_penShadow);
				dc.MoveTo(m_rctClient.left,m_rctClient.top);
				dc.LineTo(m_rctClient.right,m_rctClient.top);

				::SelectObject(dc.m_hDC,m_penDarkShadow);
				dc.MoveTo(m_rctClient.left,m_rctClient.top+1);
				dc.LineTo(m_rctClient.right-1,m_rctClient.top+1);

				::SelectObject(dc.m_hDC,m_penLight);
				dc.LineTo(m_rctClient.right-1,m_rctClient.bottom-1);
				dc.LineTo(m_rctClient.left-1,m_rctClient.bottom-1);
			}
			else
			{
				::SelectObject(dc.m_hDC,m_penShadow);
				dc.MoveTo(m_rctClient.right,m_rctClient.top);
				dc.LineTo(m_rctClient.left,m_rctClient.top);
				dc.LineTo(m_rctClient.left,m_rctClient.bottom);

				::SelectObject(dc.m_hDC,m_penDarkShadow);
				dc.MoveTo(m_rctClient.right,m_rctClient.top+1);
				dc.LineTo(m_rctClient.left+1,m_rctClient.top+1);
				dc.LineTo(m_rctClient.left+1,m_rctClient.top+1);
				dc.LineTo(m_rctClient.left+1,m_rctClient.bottom-1);

				::SelectObject(dc.m_hDC,m_penLight);
				dc.LineTo(m_rctClient.right+1,m_rctClient.bottom-1);
			}

		}
		//If the position reached the limit, draw corresponding
		//part of control as disabled
		switch (m_nSpinState)
		{
			case DisableRight:
								if(m_bDefaultDirection)
								{
									if(m_bVertical)
										DisableRect(dc,rcPaintDown);
									else
										DisableRect(dc,rcPaintUp);
								}
								else
								{
									if(m_bVertical)
										DisableRect(dc,rcPaintUp);
									else
										DisableRect(dc,rcPaintDown);
								}

								break;
			case DisableLeft:
								if(m_bDefaultDirection)
								{
									if(m_bVertical)
										DisableRect(dc,rcPaintUp);
									else
										DisableRect(dc,rcPaintDown);
								
								}
								else
								{
									if(m_bVertical)
										DisableRect(dc,rcPaintDown); 
									else
										DisableRect(dc,rcPaintUp);

								}
								break;
		}

		if(m_bActiveSpinPressed)
		{
			//The control's position has changed, so we have to
			//draw corresponding part as pressed
			if(m_rctIsPressed.IsRectEmpty())
			{
				CPoint pt=::GetMessagePos();
				ScreenToClient(&pt);
				if(rcPaintUp.PtInRect(pt))
				{
					m_rctIsPressed = rcPaintUp;
				}
				else
				{
					if(rcPaintDown.PtInRect(pt))
					{
						m_rctIsPressed = rcPaintDown; 
					}
				}

			}
			DrawPressedRect(dc,m_rctIsPressed);
			m_bActiveSpinPressed=false;

		}
		//Copy drawing from memory device context to the screen
		RealDC.BitBlt(m_rctClient.left,m_rctClient.top,m_rctClient.Width(),
			m_rctClient.Height(),&dc,m_rctClient.left,m_rctClient.top,SRCCOPY);

		dc.SelectObject(pOldMemBmp);
	}
	else
	{
		//Let the Windows do it's work 
		Default();
		return;
	}

}
void CLBSpinButtonCtrl::DisableRect(CDC &dc, const CRect &rectDisable) const
{
	CBitmap bmpMask,*pBmpBeforeMask;
	CBrush	brushMask,*pOldBrush;
	COLORREF clrOldBack,clrOldText;
	CDC memDC;
	memDC.CreateCompatibleDC(&dc);

	
	CRect rctClient;
	CRect rectToDisable=rectDisable;
	GetClientRect(&rctClient);

	//Create MONO bitmap
	bmpMask.CreateBitmap(rctClient.Width(),rctClient.Height(),1,1,NULL);
	pBmpBeforeMask=memDC.SelectObject(&bmpMask);

	CDC dcSrc;
	dcSrc.CreateCompatibleDC(&dc);
	CBitmap bmpSrc,*pBmpBeforeSrc;
	
	bmpSrc.CreateCompatibleBitmap(&dc,rctClient.Width(),rctClient.Height());
	pBmpBeforeSrc=dcSrc.SelectObject(&bmpSrc);

	clrOldBack=dc.SetBkColor(RGB(0,0,0)); //Suppose arrows are black

	//Create mask into memDC (the pixels which in dc were black  - become white in memDC,
	//all others pixels in memDC become black)
	memDC.BitBlt(rectToDisable.left,rectToDisable.top,
		rectToDisable.Width(),rectToDisable.Height(),&dc,
		rectToDisable.left,rectToDisable.top,SRCCOPY);

	//Copy bitmap from screen to memory DC
	dcSrc.BitBlt(rectToDisable.left,rectToDisable.top,
		rectToDisable.Width(),rectToDisable.Height(),&dc,
		rectToDisable.left,rectToDisable.top,SRCCOPY);

	CBrush brushSrc;
	rectToDisable.DeflateRect(1,1);

	//Apply DSPxax ROP code to memory dc . As result black arrow become
	// of COLOR_3DSHADOW
	brushSrc.CreateSolidBrush(::GetSysColor(COLOR_3DSHADOW));
	dcSrc.SelectObject(&brushSrc);
	dcSrc.SetBkColor(RGB(255,255,255));
	dcSrc.SetTextColor(RGB(0,0,0));
	dcSrc.BitBlt(rectToDisable.left,rectToDisable.top,
		rectToDisable.Width(),rectToDisable.Height(),&memDC,
		rectToDisable.left,rectToDisable.top,0x00E20746L);

	//Apply DSPxax ROP code to screen dc and shift result one pixel left and bottom.
	//As result black arrow become shifted and of COLOR_3DLIGHT.
	brushMask.CreateSolidBrush(::GetSysColor(COLOR_3DHILIGHT));
	pOldBrush = dc.SelectObject(&brushMask);
	dc.SetBkColor(RGB(255,255,255));
	clrOldText=dc.SetTextColor(RGB(0,0,0));
	dc.BitBlt(rectToDisable.left+1,rectToDisable.top+1,
		rectToDisable.Width()-1,rectToDisable.Height()-1,&memDC,
		rectToDisable.left,rectToDisable.top,0x00E20746L);


	// Draw memory dc (dcSrc) transparently over device dc.
	//As result only arrow of color COLOR_3DSHADOW will be drawn over device dc
	//-------------------------------------------------------------------------

	//Create mask into memDC (the pixels which in dc were COLOR_BTNFACE
	//- become white in memDC,all others pixels in memDC become black)
	dcSrc.SetBkColor(GetSysColor(COLOR_BTNFACE));
	memDC.BitBlt(rectToDisable.left,rectToDisable.top,
		rectToDisable.Width(),rectToDisable.Height(),&dcSrc,
		rectToDisable.left,rectToDisable.top,SRCCOPY);

	dc.BitBlt(rectToDisable.left,rectToDisable.top,
		rectToDisable.Width(),rectToDisable.Height(),&dcSrc,
		rectToDisable.left,rectToDisable.top,SRCINVERT);

	dc.BitBlt(rectToDisable.left,rectToDisable.top,
		rectToDisable.Width(),rectToDisable.Height(),&memDC,
		rectToDisable.left,rectToDisable.top,SRCAND);
	dc.BitBlt(rectToDisable.left,rectToDisable.top,
		rectToDisable.Width(),rectToDisable.Height(),&dcSrc,
		rectToDisable.left,rectToDisable.top,SRCINVERT);
	//-------------------------------------------------------------------------

	//Restore resourses
	memDC.SelectObject(pBmpBeforeMask);
	dcSrc.SelectObject(pBmpBeforeSrc);

	dc.SetBkColor(clrOldBack);
	dc.SetTextColor(clrOldText);
	dc.SelectObject(pOldBrush);
}

void CLBSpinButtonCtrl::DrawPressedRect(CDC &dc, const CRect &rctToDown) const
{

	CRect 	rctDown=rctToDown;
	HPEN hOldPen;
	
		//Offset bitmap one pixel left and down
	
		dc.BitBlt(rctDown.left+1,rctDown.top+1,
		rctDown.Width()-1,rctDown.Height()-1,&dc,
		rctDown.left,rctDown.top,SRCCOPY);

		// Draw the border of pressed button
		rctDown.bottom-=1;

		hOldPen = (HPEN)SelectObject(dc.m_hDC,m_penShadow);
		dc.MoveTo(rctDown.left,rctDown.bottom-1);
		dc.LineTo(rctDown.left,rctDown.top);
		dc.LineTo(rctDown.right-1,rctDown.top);
		
		SelectObject(dc.m_hDC,m_penLight);
		dc.LineTo(rctDown.right-1,rctDown.bottom);
		dc.LineTo(rctDown.left-1,rctDown.bottom);


		SelectObject(dc.m_hDC,m_penDarkShadow);
		dc.MoveTo(rctDown.left+1,rctDown.bottom-2);
		dc.LineTo(rctDown.left+1,rctDown.top+1);
		dc.LineTo(rctDown.right-2,rctDown.top+1);

		SelectObject(dc.m_hDC,m_penLightShadow);
		dc.LineTo(rctDown.right-2,rctDown.bottom-1);
		dc.LineTo(rctDown.left,rctDown.bottom-1);

		SelectObject(dc.m_hDC,m_penButtonFace);
		dc.MoveTo(rctDown.left+2,rctDown.bottom-2);
		dc.LineTo(rctDown.left+2,rctDown.top+2);
		dc.LineTo(rctDown.right-2,rctDown.top+2);

		
		
		SelectObject(dc.m_hDC,hOldPen);


}

void CLBSpinButtonCtrl::Init()
{ 
	//We need to know m_hWndBuddy even if UDS_WRAP style applied, to fix bug
	// with possible incorrect Z-order of buddy/spin
	m_hWndBuddy=(HWND)::SendMessage(m_hWnd, UDM_GETBUDDY, 0, 0l);

	DWORD dwStyle=::GetWindowLong(m_hWnd,GWL_STYLE);
	if(dwStyle & UDS_WRAP)
		m_bAutoDisable=false; //if the UDS_WRAP style was applied, we can't disable arrows
	
	// The m_bAutoDisable can also  be set from outside using SetAutoDisable method;
	if(m_bAutoDisable) 
	{
		//Determine the orientation and alignment of this CLBSpinButtonCtrl
		if(dwStyle & UDS_ALIGNRIGHT )
			m_nSpinAlign=OnRightInside;
		else
		{
			if(dwStyle & UDS_ALIGNLEFT)
			{
				m_nSpinAlign=OnLeftInside;
			
			}
		}
		if(dwStyle & UDS_HORZ)
			m_bVertical=false;


		//Determine the control's limits and direction of increasing of it's position
		GetRange32(m_nMinPos,m_nMaxPos);	
		if( m_nMinPos < m_nMaxPos)
			m_bDefaultDirection =false;
		else
		{
			int nTemp = m_nMinPos;
			m_nMinPos = m_nMaxPos;
			m_nMaxPos=nTemp;
		}

		GetClientRect(&m_rctClient);

		m_nPrevPos = GetPos();

		//This is done special for up-down control which lives in CTabCtrl,
		//as so its initial value is out of range.
		if(m_nPrevPos < m_nMinPos || m_nPrevPos > m_nMaxPos)
			m_nPrevPos=m_nMinPos;

		//Determine the control's initial enable/disable state
		m_nSpinState = BothEnable;
		if(m_nPrevPos == m_nMinPos)
				m_nSpinState = DisableLeft;
		else
		{
			if(m_nPrevPos == m_nMaxPos)
				m_nSpinState = DisableRight;

		}

		if(::IsWindow(m_hWndBuddy))
		{
			char buf[5];
			::GetClassName(m_hWndBuddy,buf,sizeof(buf)/sizeof(buf[0]));
			
			if(!strcmp(buf,"Edit"))
			{
				//The class  of buddy is Edit
				m_bBuddyIsEdit=true;

				//I need to update the enabled/disabled state of CLBSpinButtonCtrl
				//when contents of buddy window is changing.
				//For instance, if user enters into buddy window the value greater
				//than upper possible limit - it is obvious that the increasing 
				//arrow should switch to disable state.
				//The best way to do it is to create WH_CALLWNDPROC hook 
				//and test within it if EN_UPDATE message came from buddy window
				//Another posibility- keyboard hook, but then the user can foolish 
				//the control, using clipboard Copy/Cut/Paste functions.
						
				//Test if WH_CALLWNDPROC hook already was installed.
				// If no, set up it.
				if(ghHook==NULL)
					ghHook=SetWindowsHookEx(WH_CALLWNDPROC,FilterBuddyMsgProc,NULL, //WH_GETMESSAGE
											GetCurrentThreadId());
				//As so I use a single WH_CALLWNDPROC hook for all existing in
				//an application CLBSpinButtonCtrl controls, I need to distinguish
				// between these controls in the static FilterBuddyMsgProc.
				//For this purpose I use static std:map<HWND,HWND> gHandleMap
				
				//Try to find m_hWndBuddy in the gHandleMap
				HWNDMAP::iterator iterHwnd=gHandleMap.find(m_hWndBuddy);
				if(iterHwnd != gHandleMap.end())
				{
					if((*iterHwnd).second != m_hWnd)
					{
						//If in the gHandleMap already defined  another CLBSpinButtonCtrl
						//for that buddy (m_hWndBuddy), then redefine it .
						gHandleMap.erase(iterHwnd);	
						gHandleMap.insert(HWNDMAP::value_type(m_hWndBuddy,m_hWnd));
					}
				}
				else
					//If the CLBSpinButtonCtrl assosiated with
					//m_hWndBuddy is not found in the gHandleMap, then add it.
					gHandleMap.insert(HWNDMAP::value_type(m_hWndBuddy,m_hWnd));
			}
		}
		Invalidate(FALSE);
	}
}

BOOL CLBSpinButtonCtrl::OnDeltapos(NMHDR* pNMHDR, LRESULT* pResult) 
{
	if(m_bAutoDisable)
	{
		NM_UPDOWN* pNMUpDown = (NM_UPDOWN*)pNMHDR;

		int nNextPos=pNMUpDown->iPos+pNMUpDown->iDelta;

		//Determine the enable/disable state of CLBSpinButtonCtrl
		if(nNextPos > m_nMaxPos)
			nNextPos = m_nMaxPos;
		else
		{
			if(nNextPos < m_nMinPos)
				nNextPos = m_nMinPos;
		}
	
		if(m_nPrevPos!=nNextPos)
		{
			if(pNMUpDown->iDelta)
				m_bActiveSpinPressed=true;

			if(nNextPos  <m_nMaxPos && nNextPos >m_nMinPos)
			{
				m_nSpinState = BothEnable;
			}
			else
			{
				if(nNextPos == m_nMaxPos)
				{
					if(m_nMaxPos != m_nMinPos)
					{
						m_nSpinState=DisableRight;
						m_bActiveSpinPressed=false;
					}
					else
					{
						m_nSpinState = BothDisable;
						m_bActiveSpinPressed=false;
					}
				}
				else
				{
					if(nNextPos == m_nMinPos)
					{
						m_nSpinState=DisableLeft;
						m_bActiveSpinPressed=false;
					}
				}
			}
			m_nPrevPos=nNextPos;
			Invalidate(FALSE);

			//Let the Windows process UDN_DELTAPOS too
			*pResult = 0;
			return FALSE;
		}
		else
		{
			//If the next position of control is the same as current,
			//what can happen when user clicked the disable arrow,
			//then eat UDN_DELTAPOS
			*pResult = 1;
			return TRUE;
		}
	}
	else
	{ 
		//Let the Windows process UDN_DELTAPOS
		*pResult = 0;
		return FALSE;
	}
}


LRESULT CLBSpinButtonCtrl::WindowProc(UINT message, WPARAM wParam, LPARAM lParam) 
{
	//Let the Windows do it's work 
	LRESULT nRet = CSpinButtonCtrl::WindowProc(message, wParam, lParam);

	switch(message)
	{
		case UDM_SETRANGE32:
		case UDM_SETRANGE: 
							//Need to reinit CLBSpinButtonCtrl, as so the
							//limits probably changed
							Init();
							break;


		case UDM_SETBUDDY :
							//Need to reinit CLBSpinButtonCtrl, as so the
							//buddy changed
							CleanUpHook();
							Init();
							//If buddy window(m_hWndBuddy) is placed after this 
							//CLBSpinButtonCtrl in Z-order , then m_hWndBuddywindow will 
							//get WM_PAINT message after  this CLBSpinButtonCtrl control 
							//and in case CLBSpinButtonCtrl is attached to buddy,
							// it will be overpainted by buddy's border
							//This undocumented bug persist for CSpinButtonCtrl as well.
							//
							//To reproduce it create on dialog template attached
							//CSpinButtonCtrl control and CEdit control and make
							//tab order so, that for CSpinButtonCtrl tab position 
							//was less then for CEdit;
							//Then in OnInitDiaolg call SetBuddy(pointerToEdit) function 
							//of CSpinButtonCtrl.
							//
							//
							//To work around this simply place CSpinButtonCtrl
							//after it's buddy in Z-order. 
							if(::IsWindow(m_hWndBuddy))
								::SetWindowPos(m_hWnd,m_hWndBuddy,0,0,0,0,
									           SWP_NOMOVE|SWP_NOSIZE);

							break;
	}
	return nRet;

}

void CLBSpinButtonCtrl::CleanUpHook() const
{
	if(m_bBuddyIsEdit)
	{
		//If the buddy is edit, then try to find out if
		//it was added to gHandleMap.
		HWNDMAP::iterator iterHwnd=gHandleMap.find(m_hWndBuddy);
		if(iterHwnd != gHandleMap.end() && (*iterHwnd).second == m_hWnd)
		{
			//If m_hWndBuddy found and is assosiated with current window,
			//then delete it from gHandleMap.
			iterHwnd = gHandleMap.erase(iterHwnd);
			if(!gHandleMap.size() && ghHook!=NULL)
			{
				//If just deleted from the gHandleMap m_hWndBuddy was
				//the last for current application,
				//then remove a hook procedure. 
				UnhookWindowsHookEx( ghHook);
				ghHook=NULL;
			}
		}
	}
}

void CLBSpinButtonCtrl::OnLButtonDown(UINT nFlags, CPoint point) 
{
	m_rctIsPressed.SetRectEmpty();
	CSpinButtonCtrl::OnLButtonDown(nFlags, point);
}

BOOL CLBSpinButtonCtrl::OnEraseBkgnd(CDC* pDC) 
{
	return TRUE	; //Get rid of flickering

}

void CLBSpinButtonCtrl::OnLButtonUp(UINT nFlags, CPoint point) 
{
	Invalidate();	
	UpdateWindow();
	CSpinButtonCtrl::OnLButtonUp(nFlags, point);
}

bool CLBSpinButtonCtrl::SetAutoDisable(bool bSetOn)
{
	// User should have an opportunity to swith on/off the custom
	//behavior of  the CLBSpinButtonCtrl control.
	
	bool bPrev = m_bAutoDisable;
	m_bAutoDisable = bSetOn;
	Init();

	return bPrev;
}

LRESULT CALLBACK FilterBuddyMsgProc(  int code,  WPARAM wParam,  LPARAM lParam )
{
	CWPSTRUCT* pData=reinterpret_cast<CWPSTRUCT*>(lParam);
	if(WM_COMMAND==pData->message && EN_UPDATE==HIWORD(pData->wParam))
	{
		//If the incoming message is EN_UPDATE
		HWNDMAP::iterator iterHwnd=gHandleMap.find(reinterpret_cast<HWND>(pData->lParam));
		if(iterHwnd != gHandleMap.end())
		{
			//The incoming EN_UPDATE message has been sent
			//to the edit control, defined in the gHandleMap.

			//So get the value, entered into the edit control
			//and send it to the CLBSpinButtonCtrl, assosiated
			//with the edit control.
			CString strText;
			int nLen=::GetWindowTextLength((*iterHwnd).first);
			::GetWindowText((*iterHwnd).first,strText.GetBufferSetLength(nLen),nLen+1);
			strText.ReleaseBuffer();

			//In case UDS_NOTHOUSANDS style not applied
			//we have to delete thousands delimiter from string
			strText.Remove((TCHAR)0xA0);

			NMUPDOWN nmUpDn;
			nmUpDn.iDelta=0;
			nmUpDn.iPos=atoi(strText);
			nmUpDn.hdr.code=UDN_DELTAPOS;
			nmUpDn.hdr.hwndFrom=(*iterHwnd).second;
			nmUpDn.hdr.idFrom=::GetDlgCtrlID((*iterHwnd).second);

			///HWND hWndSpinParent=::GetParent((*iterHwnd).second);!!! watermark
			::SendMessage(::GetParent((*iterHwnd).second),
							WM_NOTIFY,(WPARAM)nmUpDn.hdr.idFrom,
							(LPARAM)&nmUpDn);
		}
	}
	return CallNextHookEx(ghHook,code,wParam,lParam);
}

void CLBSpinButtonCtrl::OnDestroy() 
{
	//Remove current CLBSpinButtonCtrl control from the gHandleMap and
	//if it is the last control, defined in the gHandleMap
	//then remove a hook procedure. 
	CleanUpHook();

	CSpinButtonCtrl::OnDestroy();
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Oleg Lobach

Russian Federation Russian Federation
No Biography provided

| Advertise | Privacy | Terms of Use | Mobile
Web01 | 2.8.150414.1 | Last Updated 1 Feb 2000
Article Copyright 2000 by Oleg Lobach
Everything else Copyright © CodeProject, 1999-2015
Layout: fixed | fluid