Click here to Skip to main content
15,886,873 members
Articles / Desktop Programming / WTL

CCaptionButton - Add Buttons on the Caption Bar

Rate me:
Please Sign up or sign in to vote.
4.96/5 (24 votes)
19 Sep 20054 min read 135.6K   5.3K   69  
A reusable WTL base class to add buttons on the caption bar.
//////////////////////////////////////////////////////////////////////////////////////////////
// ATL implementation for adding buttons to caption bar
#pragma once

#ifndef __cplusplus
	#error ATL requires C++ compilation (use a .cpp suffix)
#endif

#ifndef __ATLBASE_H__
	#error appbar.h requires atlbase.h to be included first
#endif

#ifndef __ATLAPP_H__
	#error appbar.h requires atlapp.h to be included first
#endif

#ifndef __ATLWIN_H__
	#error appbar.h requires atlwin.h to be included first
#endif

#include <vector>
using namespace std;

///////////////////////////////////////////////////////////////////////////////////////////////////////////
//	class CCaptionButton
//		Add push buttons to the caption bar
//
//	Author:		Yao Zhifeng
//	Contact:	yaozhifeng@hotmail.com
//
//	Usage:
//		1.use this class as a base class
//		2.use CHAIN_MSG_MAP to chain message to this class
//		3.call AddButton to add one or more buttons to the caption bar
//		4.(IMPORTANT) override GetButtonPos function to provide position for each button added
//		5.handle WM_COMMAND notification sent by caption button in the same way as handle normal button clicking
//		6.(optional) call CheckButton to change the checked status of the caption button
//		7.(optional) call EnableButton to change the enable status of the caption button
//
//	template argument:
//		T: derived class, must also derived from CWindowImpl directly or indirectly
//
template <class T>
class CCaptionButton
{
private:
	struct	_button
	{
		UINT	uID;	//command ID
		int		cx;		//width
		int		cy;		//height
		HIMAGELIST	himl;	//image list
		UINT	uStatus;	//status (image index)
		char	szHint[80];	//tooltip text
	};

	CToolTipCtrl	m_tooltips;
	vector<_button>	m_buttons;
	int	m_nPressed; //currently pressed button index

public:
	enum
	{
		//Button status /image index
		CAPTION_BTN_NORMAL		= 0,
		CAPTION_BTN_PUSHED		= 1,
		CAPTION_BTN_HOVER		= 2,
		CAPTION_BTN_CHECKED		= 3,
		CAPTION_BTN_DISABLED	= 4,

		//Interval between buttons
		CAPTION_BTN_INTERVAL	= 2
	};



	CCaptionButton()
	{
		m_nPressed = -1;
	}
	virtual ~CCaptionButton()
	{
		Clear();
	}

	BEGIN_MSG_MAP(CCaptionButton)
		MESSAGE_HANDLER(WM_NCHITTEST, OnNcHitTest)
		MESSAGE_HANDLER(WM_NCPAINT, OnNcPaint)
		MESSAGE_HANDLER(WM_NCLBUTTONDOWN, OnNcLButtonDown)
		MESSAGE_HANDLER(WM_LBUTTONUP, OnLButtonUp)
		MESSAGE_HANDLER(WM_MOUSEMOVE, OnMouseMove)
		MESSAGE_HANDLER(WM_ACTIVATE, OnActivate)
		NOTIFY_CODE_HANDLER(TTN_GETDISPINFO, OnGetDispInfo)
	END_MSG_MAP()

	/////////////////////////////////////////////////////////////////
	// remove all the buttons
	void Clear()
	{
		while (m_buttons.size())
			RemoveButton(0);
	}

	/////////////////////////////////////////////////////////////
	// remove the button at given position
	void RemoveButton(int index)
	{
		ATLASSERT(index>=0 && index<(int)m_buttons.size());

		vector<_button>::iterator	it = m_buttons.begin();
		for (int i=0; i<index; i++)
			it++;

		//destroy the image list handle
		ImageList_Destroy(it->himl);
		//erase from the list
		m_buttons.erase(it);

	}

	////////////////////////////////////////////////////////////////
	// append a button
	// return the count of buttons
	int AddButton(UINT uID, int cx, int cy, HIMAGELIST himl, LPCTSTR lpszHint=NULL)
	{
		T* pT = static_cast<T*>(this);

		if (m_tooltips.m_hWnd==NULL)
		{
			m_tooltips.Create(NULL, NULL, NULL, WS_POPUP | TTS_NOPREFIX | TTS_ALWAYSTIP,
				WS_EX_TOOLWINDOW|WS_EX_TOPMOST);
			ATLASSERT(m_tooltips.m_hWnd);

			TOOLINFO	info;
			memset(&info, 0, sizeof(info));
			info.cbSize		= sizeof(info);
			info.uFlags		= TTF_IDISHWND;
			info.hwnd		= pT->m_hWnd;
			info.uId		= (UINT)pT->m_hWnd;
			info.lpszText	= LPSTR_TEXTCALLBACK;
			m_tooltips.AddTool(&info);

			m_tooltips.Activate(TRUE);
		}


		_button	btn;
		memset(&btn, 0, sizeof(btn));
		btn.uID = uID;
		btn.cx	= cx;
		btn.cy	= cy;
		btn.himl= himl;
		btn.uStatus = CAPTION_BTN_NORMAL;
		if (lpszHint)
		{
			_tcsncpy(btn.szHint, lpszHint, 80);
		}

		m_buttons.push_back(btn);

		return m_buttons.size();
	}

	///////////////////////////////////////////////////////////////////////////////////
	// check or uncheck the button at postion index
	void CheckButton(int index, bool bCheck)
	{
		T* pT = static_cast<T*>(this);

		_button &btn = m_buttons[index];
		btn.uStatus = bCheck ? CAPTION_BTN_CHECKED : CAPTION_BTN_NORMAL;

		//Update visual effect
		if (pT->m_hWnd)
		{
			//force repaint
			pT->SendMessage(WM_NCPAINT, 1);
		}
	}

	/////////////////////////////////////////////////////////////////////////////////
	// if the button at position index checked
	bool IsButtonChecked(int index)
	{
		_button &btn = m_buttons[index];

		return (btn.uStatus == CAPTION_BTN_CHECKED);
	}

	/////////////////////////////////////////////////////////////////////////////
	// enable/disable button at given position
	void EnableButton(int index, bool bEnable)
	{
		T* pT = static_cast<T*>(this);

		_button &btn = m_buttons[index];
		btn.uStatus = bEnable ? CAPTION_BTN_NORMAL : CAPTION_BTN_DISABLED;

		//Update visual effect
		if (pT->m_hWnd)
		{
			//force repaint
			pT->SendMessage(WM_NCPAINT, 1);
		}
	}

	bool IsButtonEnabled(int index)
	{
		_button &btn = m_buttons[index];

		return (btn.uStatus != CAPTION_BTN_DISABLED);
	}

	/////////////////////////////////////////////////////////////////////////////////
	// Get the zero based index of the button, given the button ID
	int GetButtonIndex(UINT uID)
	{
		for (int i=0; i<(int)m_buttons.size(); i++)
		{
			if (m_buttons[i].uID == uID)
				return i;
		}

		return -1;
	}

	//////////////////////////////////////////////////////////////////////////////////
	// get the topleft corner of the given button, in window coordinate
	// override this function in the derived class to specify button position
	// this default implementation only works for tool window without any existing caption buttons
	POINT GetButtonPos(int index)
	{
		T* pT = static_cast<T*>(this);
		//get the window rect
		CRect	rcWnd;
		pT->GetWindowRect(&rcWnd);
		rcWnd.OffsetRect(-rcWnd.TopLeft());

		rcWnd.DeflateRect(2, 2);

		//locate the top right base point
		CPoint	pt(rcWnd.right, rcWnd.top);

		//calculate button position
		for (int i=0; i<=index; i++)
		{
			_button &btn = m_buttons[i];
			pt.x -= btn.cx;
			pt.x -= CAPTION_BTN_INTERVAL;
		}

		return pt;
	}

	///////////////////////////////////////////////////////////////////////////////////
	// get the rect of given button in window coordinate
	RECT GetButtonRect(int index)
	{
		T* pT = static_cast<T*>(this);

		_button btn = m_buttons[index];

		POINT	pt = pT->GetButtonPos(index);
		RECT	rc;
		rc.left = pt.x;
		rc.top  = pt.y;
		rc.right = rc.left + btn.cx;
		rc.bottom = rc.top + btn.cy;

		return rc;
	}

	////////////////////////////////////////////////////////////////////////////////////
	// get button under point, in window coordinate
	int GetButtonAtPos(POINT pt)
	{
		T* pT = static_cast<T*>(this);
		for (int i=0; i<(int)m_buttons.size(); i++)
		{
			CRect	rc = pT->GetButtonRect(i);
			if (rc.PtInRect(pt))
				return i;
		}
		return -1;
	}

private:
	////////////////////////////////////////////////////////////////////////////////////
	// WM_NCHITTEST	 message handler
	LRESULT OnNcHitTest(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
	{
		bHandled = FALSE;
		T* pT = static_cast<T*>(this);
		LRESULT hit = ::DefWindowProc(pT->m_hWnd, uMsg, wParam, lParam);

		UpdateStatus();

		CPoint	pt(lParam);
		CRect	rcWnd;
		pT->GetWindowRect(&rcWnd);
		pt.Offset(-rcWnd.TopLeft());

		int nButton = GetButtonAtPos(pt);
		if (nButton != -1 && m_buttons[nButton].uStatus!=CAPTION_BTN_DISABLED)
		{
			bHandled = TRUE;
		}

		RelayEvent();

		return hit;
	}

	///////////////////////////////////////////////////////////////////////////////////
	// WM_NCPAINT message handler
	// draw the buttons
	LRESULT OnNcPaint(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& /*bHandled*/)
	{
		T* pT = static_cast<T*>(this);

		::DefWindowProc(pT->m_hWnd, uMsg, wParam, lParam);

		CWindowDC	dc(pT->m_hWnd);

		for (int i=0; i<(int)m_buttons.size(); i++)
		{
			_button btn = m_buttons[i];
			POINT	pt = pT->GetButtonPos(i);

			ImageList_Draw(btn.himl, btn.uStatus, dc, pt.x, pt.y, ILD_NORMAL);
		}

		return 0;
	}

	///////////////////////////////////////////////////////////////////////////////////
	// WM_NCLBUTTONDOWN messange handler
	// if pressed on a button, SetCapture if not already captured
	// and push the button
	LRESULT OnNcLButtonDown(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
	{
		bHandled = FALSE;
		T* pT = static_cast<T*>(this);

		WORD	hit = wParam;
		CPoint	pt(lParam);
		CRect	rcWnd;
		pT->GetWindowRect(&rcWnd);
		pt.Offset(-rcWnd.TopLeft());

		int nButton = GetButtonAtPos(pt);
		if (nButton != -1)
		{
			_button &btn = m_buttons[nButton];
			if (btn.uStatus != CAPTION_BTN_DISABLED)
			{
				if (::GetCapture()!=pT->m_hWnd)
					pT->SetCapture();

				m_nPressed = nButton;
				bHandled = TRUE;

				if (btn.uStatus != CAPTION_BTN_CHECKED)
					btn.uStatus = CAPTION_BTN_PUSHED;

				//force repaint
				pT->SendMessage(WM_NCPAINT, 1);
			}
		}

		return 0;
	}

	///////////////////////////////////////////////////////////////////////////////////
	// WM_MOUSEMOVE message handler
	// valid only if mouse captured
	LRESULT OnMouseMove(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
	{
		bHandled = FALSE;
		T* pT = static_cast<T*>(this);

		if (::GetCapture()==pT->m_hWnd && m_nPressed != -1)
		{
			bHandled = TRUE;
			UpdateStatus();
		}

		return 0;
	}

	///////////////////////////////////////////////////////////////////////////////
	// WM_LBUTTONUP message handler
	// valid only if mouse captured
	// fire button clicked event if one button clicked
	LRESULT OnLButtonUp(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
	{
		bHandled = FALSE;
		T* pT = static_cast<T*>(this);
		if (::GetCapture()==pT->m_hWnd && m_nPressed != -1)
		{
			bHandled = TRUE;
			::ReleaseCapture();

			CPoint	pt;
			::GetCursorPos(&pt);
			CRect	rcWnd;
			pT->GetWindowRect(&rcWnd);
			pt.Offset(-rcWnd.TopLeft());

			int nButton = GetButtonAtPos(pt);
			if (m_nPressed != -1 && nButton == m_nPressed)
			{
				//fire click event
				pT->OnCaptionButtonClicked(nButton);
			}

			m_nPressed = -1;
			//force repaint
			pT->SendMessage(WM_NCPAINT, 1);
		}

		return 0;
	}

	///////////////////////////////////////////////////////////////////////////////
	// WM_ACTIVATE messsage handler
	// force repaint of non-client area
	LRESULT OnActivate(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
	{
		bHandled = FALSE;

		T* pT = static_cast<T*>(this);
		pT->SendMessage(WM_NCPAINT);

		return 0;
	}

	///////////////////////////////////////////////////////////////////////////
	// Notify TTN_GETDISPINFO handler
	// provide tooltip text dynamicly
	LRESULT OnGetDispInfo(int idCtrl, LPNMHDR lpnmHdr, BOOL &bHandled)
	{
		NMTTDISPINFO *lptt = (NMTTDISPINFO *)lpnmHdr;

		T* pT = static_cast<T*>(this);

		CPoint	pt;
		::GetCursorPos(&pt);
		CRect	rcWnd;
		pT->GetWindowRect(&rcWnd);
		pt.Offset(-rcWnd.TopLeft());

		int nButton = pT->GetButtonAtPos(pt);
		if (nButton != -1)
		{
			_button &btn = m_buttons[nButton];

			if (btn.uStatus != CAPTION_BTN_DISABLED)
				strncpy(lptt->szText, btn.szHint, 80);
		}

		lptt->uFlags = TTF_IDISHWND;
		lptt->lpszText = lptt->szText;


		return 0;
	}

protected:
	/////////////////////////////////////////////////////////////////////////////////////
	// fire button cliced event
	// can be override in derived class
	// the default implementation send WM_COMMAND with button ID
	void OnCaptionButtonClicked(int index)
	{
		T* pT = static_cast<T*>(this);

		_button &btn = m_buttons[index];
		pT->SendMessage(WM_COMMAND, MAKELONG(btn.uID, 0), 0);

	}

	///////////////////////////////////////////////////////////////////////////
	// update all button status and repaint
	void UpdateStatus()
	{
		T* pT = static_cast<T*>(this);

		CPoint	pt;
		::GetCursorPos(&pt);
		CRect	rcWnd;
		pT->GetWindowRect(&rcWnd);
		pt.Offset(-rcWnd.TopLeft());

		bool	bRepaint = false;
		for (int i=0; i<(int)m_buttons.size(); i++)
		{
			_button &btn = m_buttons[i];
			
			if (btn.uStatus != CAPTION_BTN_DISABLED)
			{
				CRect	rc = GetButtonRect(i);
				UINT	uNewStatus;
				if (rc.PtInRect(pt))
				{
					if (btn.uStatus==CAPTION_BTN_CHECKED)
						uNewStatus = CAPTION_BTN_CHECKED;
					else if (m_nPressed == i) //mouse pressed in button
						uNewStatus = CAPTION_BTN_PUSHED;
					else
						uNewStatus = CAPTION_BTN_HOVER;
				}
				else
				{
					if (btn.uStatus!=CAPTION_BTN_CHECKED) //If old status is not checked, set it normal
						uNewStatus = CAPTION_BTN_NORMAL;
					else
						uNewStatus = CAPTION_BTN_CHECKED;
				}

				if (btn.uStatus != uNewStatus)//repaint only if status changed
				{
					bRepaint = true;
					btn.uStatus = uNewStatus;
				}
			}
		}

		if (bRepaint)
		{
			pT->SendMessage(WM_NCPAINT, 1);
		}
	}

	///////////////////////////////////////////////////////////////////////////////////
	// tooltip processing
	void RelayEvent()
	{
		T* pT = static_cast<T*>(this);

		CPoint	pt;
		::GetCursorPos(&pt);
		CRect	rcWnd;
		pT->GetWindowRect(&rcWnd);
		pt.Offset(-rcWnd.TopLeft());

		MSG	msg;
		memset(&msg, 0, sizeof(msg));
		msg.hwnd		= pT->m_hWnd;
		msg.message		= WM_MOUSEMOVE;
		msg.lParam		= MAKELONG(pt.x, pt.y);
		msg.pt			= pt;

		static nLastButton = -1;
		int nButton = GetButtonAtPos(pt);
		if (nButton != nLastButton)//change hovered button
		{
			m_tooltips.Pop();
			nLastButton = nButton;
		}

		m_tooltips.RelayEvent(&msg);
	}


};

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 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


Written By
China China
I'm a chinese programer living in Shanghai, currently working for a software company whose main business is to deliver computer based testing. Software simulation for computer based testing and certifications is my main responsibility in this company. Execpt for software development, I like out-door activities and photography. I am willing to make friends in China and all over the world, so contact me if you have anything in common with meSmile | :)

Comments and Discussions