Click here to Skip to main content
15,897,371 members
Articles / Desktop Programming / MFC

DirectUI Window as in XP system folders

Rate me:
Please Sign up or sign in to vote.
4.45/5 (43 votes)
1 Aug 2004CPOL6 min read 269.2K   7.6K   170  
A control to show a list of possible tasks just as in XP.
// WndDirectUI.cpp: Implementierungsdatei
//

#include "stdafx.h"
#include "todowin.h"
#include "WndDirectUI.h"
#include "memdc.h"

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

/////////////////////////////////////////////////////////////////////////////
// CDirectUIGroup

/////////////////////////////////////////////////////////////////////////////
// 
// Credits...
// 
// John A. Johnson : DT_NOPREFIX was deleted in all the code
// Andreas Kapust  : ::OnMousemove bugfix was added to handle the scrollbar correctly
// Zebrex          : HitTest failed on collapsed group
// 

CDirectUIGroup::CDirectUIGroup(CString strName, CImageList* pList, int* pnStyle)
{
	m_strName    = strName;
	m_nHeight    = 0;
	m_pImageList = pList;
	m_pnStyle    = pnStyle;
	m_uiItemState= 0;
	m_rcExpandCollapseBtn.SetRectEmpty();
	m_bIsCollapsed = FALSE;

	CalcHeight();
}

CDirectUIGroup::~CDirectUIGroup()
{
	RemoveAllItems();
}

COLORREF CDirectUIGroup::MakeXPColor(COLORREF cl, double factor)
{
	if(factor>0.0&&factor<=1.0){
		BYTE red,green,blue,lightred,lightgreen,lightblue;
		red = GetRValue(cl);
		green = GetGValue(cl);
		blue = GetBValue(cl);
		lightred = (BYTE)((factor*(255-red)) + red);
		lightgreen = (BYTE)((factor*(255-green)) + green);
		lightblue = (BYTE)((factor*(255-blue)) + blue);
		cl = RGB(lightred,lightgreen,lightblue);
	}
	return(cl);
}

void CDirectUIGroup::OnDraw(CDC* pDC, CRect rcItem)
{
	const int nHeaderRadius = 5;

	switch (*m_pnStyle)
	{
	case CWndDirectUI::styleOffice:
		{
			// Draw Header
			LOGFONT lf;
			CFont::FromHandle((HFONT) GetStockObject(ANSI_VAR_FONT))->GetLogFont(&lf);
			CFont fnHeader;
			lf.lfWeight = FW_BOLD;
			fnHeader.CreateFontIndirect(&lf);
			pDC->SelectObject(&fnHeader);

			CRect rcHeader;
			rcHeader.SetRect(rcItem.left, rcItem.top, rcItem.right, rcItem.top + GetSystemMetrics(SM_CYCAPTION)); // Whole top area
			pDC->SetTextColor(GetSysColor(COLOR_WINDOWTEXT));
			pDC->SetBkColor(GetSysColor(COLOR_WINDOW));
			pDC->DrawText(m_strName, rcHeader, DT_LEFT|DT_SINGLELINE|DT_VCENTER|DT_END_ELLIPSIS);
			
			CPen pnSeparator;
			pnSeparator.CreatePen(PS_SOLID, 1, GetSysColor(COLOR_3DFACE));
			pDC->MoveTo(rcHeader.left, rcHeader.bottom);
			pDC->LineTo(rcHeader.BottomRight());

			// Draw Items
			CRect rcItems;
			rcItems.SetRect(rcItem.left+1, rcHeader.bottom, rcItem.right-1, rcHeader.bottom);
			if (m_pImageList) m_pImageList->SetBkColor(GetSysColor(COLOR_WINDOW));
			pDC->SetTextColor(GetSysColor(COLOR_HIGHLIGHT));
			CPen pnUnderline;
			pnUnderline.CreatePen(PS_SOLID, 1, GetSysColor(COLOR_HIGHLIGHT));
			pDC->SelectObject(&pnUnderline);

			// Draw Items
			if (!m_bIsCollapsed)
			{
				for (POSITION pos = m_lstItems.GetHeadPosition(); pos;)
				{
					rcItems.top     = rcItems.bottom;
					rcItems.bottom += MulDiv(GetSystemMetrics(SM_CYMENU), 9, 8); // add 12,5% space between the items
					m_lstItems.GetNext(pos)->OnDraw(pDC, rcItems, m_pImageList, *m_pnStyle);
				}
			}
		}
		break;
	case CWndDirectUI::styleXPclassic:
		{
			CPen   pnBorder;
			CPen   pnMarker;
			CBrush brHeader;
			CBrush brItemArea;
			COLORREF clBorder   = GetSysColor(COLOR_3DFACE);
			COLORREF clText     = GetSysColor(COLOR_BTNTEXT);
			COLORREF clItemArea = GetSysColor(COLOR_WINDOW);
			pnBorder.CreatePen(PS_SOLID, 1, clBorder);
			pnMarker.CreatePen(PS_SOLID, 1, clText);
			brHeader.CreateSolidBrush(clBorder);
			brItemArea.CreateSolidBrush(clItemArea);

			// Draw Header
			LOGFONT lf;
			CFont::FromHandle((HFONT) GetStockObject(ANSI_VAR_FONT))->GetLogFont(&lf);
			CFont fnHeader;
			lf.lfWeight = FW_BOLD;
			fnHeader.CreateFontIndirect(&lf);
			pDC->SelectObject(&fnHeader);

			// Draw Header
			CRect rcHeader;
			CRect rcHeaderTextArea;
			rcHeader.SetRect(rcItem.left, rcItem.top, rcItem.right, rcItem.top + GetSystemMetrics(SM_CYCAPTION)); // Whole top area
			pDC->SetTextColor(clText);
			pDC->SetBkColor(clBorder);
			rcHeaderTextArea.SetRect(rcHeader.left+nHeaderRadius, rcHeader.top, rcHeader.right-nHeaderRadius, rcHeader.bottom); // area between
			pDC->SelectObject(&brHeader);
			pDC->FillSolidRect(rcHeader, clBorder);
			pDC->DrawText(m_strName, rcHeaderTextArea, DT_LEFT|DT_SINGLELINE|DT_VCENTER|DT_END_ELLIPSIS);

			// Draw the Expand/Collapse button
			const int nBtnSpace = 2;
			int nBtnSize;
			nBtnSize = GetSystemMetrics(SM_CYCAPTION)-nBtnSpace-nBtnSpace;
			m_rcExpandCollapseBtn.SetRect(rcItem.right-nHeaderRadius-nBtnSize, rcItem.top +nBtnSpace, rcItem.right-nHeaderRadius, rcItem.top+nBtnSpace+nBtnSize);
			if (m_uiItemState & ODS_SELECTED) pDC->Draw3dRect(m_rcExpandCollapseBtn, GetSysColor(COLOR_3DHILIGHT), GetSysColor(COLOR_3DSHADOW));
			pDC->SelectObject(&pnMarker);
			CPoint ptCenter = m_rcExpandCollapseBtn.CenterPoint();
			CPoint ptLeft   = ptCenter +CPoint(-4, m_bIsCollapsed ? -4:4);
			CPoint ptRight  = ptCenter +CPoint( 4, m_bIsCollapsed ? -4:4);
			pDC->MoveTo(ptCenter);
			pDC->LineTo(ptLeft);
			pDC->MoveTo(ptCenter);
			pDC->LineTo(ptRight);
			ptCenter.Offset(0, m_bIsCollapsed ?3:-3);
			ptLeft.Offset(0, m_bIsCollapsed ?3:-3);
			ptRight.Offset(0, m_bIsCollapsed ?3:-3);
			pDC->MoveTo(ptCenter);
			pDC->LineTo(ptLeft);
			pDC->MoveTo(ptCenter);
			pDC->LineTo(ptRight);
			m_rcExpandCollapseBtn.left = rcHeader.left;

			// Now draw the lighter area for all items
			pDC->SelectObject(&pnBorder);
			pDC->SelectObject(&brItemArea);
			pDC->Rectangle(rcItem.left, rcHeaderTextArea.bottom-1, rcItem.right, rcItem.bottom);

			// Draw Items
			pDC->SelectObject(&pnMarker);
			if (!m_bIsCollapsed)
			{
				CRect rcItems;
				rcItems.SetRect(rcItem.left+1, rcHeaderTextArea.bottom, rcItem.right-1, rcHeaderTextArea.bottom);
				if (m_pImageList) m_pImageList->SetBkColor(clItemArea);
				for (POSITION pos = m_lstItems.GetHeadPosition(); pos;)
				{
					rcItems.top     = rcItems.bottom;
					rcItems.bottom += MulDiv(GetSystemMetrics(SM_CYMENU), 9, 8); // add 12,5% space between the items
					m_lstItems.GetNext(pos)->OnDraw(pDC, rcItems, m_pImageList, *m_pnStyle);
				}
			}
		}
		break;
	case CWndDirectUI::styleXP:
	default:
		{
			CPen   pnMarker;
			CPen   pnBorder;
			CPen   pnBtn;
			CBrush brHeader;
			CBrush brItemArea;
			COLORREF clBorder   = GetSysColor(COLOR_HIGHLIGHT);
			COLORREF clText     = GetSysColor(COLOR_HIGHLIGHTTEXT);
			COLORREF clItemArea = MakeXPColor(clBorder, 0.85); // Seems to be ok
			pnMarker.CreatePen(PS_SOLID, 1, clText);
			pnBtn   .CreatePen(PS_SOLID, 1, clItemArea);
			pnBorder.CreatePen(PS_SOLID, 1, clBorder);
			brHeader.CreateSolidBrush(clBorder);
			brItemArea.CreateSolidBrush(clItemArea);

			// Draw Header
			LOGFONT lf;
			CFont::FromHandle((HFONT) GetStockObject(ANSI_VAR_FONT))->GetLogFont(&lf);
			CFont fnHeader;
			lf.lfWeight = FW_BOLD;
			fnHeader.CreateFontIndirect(&lf);
			pDC->SelectObject(&fnHeader);

			// Calculate all area sizes
			CRect rcHeader;
			CRect rcPieLeft;
			CRect rcPieRight;
			CRect rcHeaderTextArea;
			rcHeader.SetRect(rcItem.left, rcItem.top, rcItem.right, rcItem.top + GetSystemMetrics(SM_CYCAPTION)); // Whole top area
			rcPieLeft.SetRect(rcItem.left, rcItem.top, rcItem.left+nHeaderRadius+nHeaderRadius, rcItem.top+nHeaderRadius+nHeaderRadius); // Upper-left edge
			rcPieRight.SetRect(rcItem.right-nHeaderRadius-nHeaderRadius, rcItem.top, rcItem.right, rcItem.top+nHeaderRadius+nHeaderRadius);// Upper-right edge
			rcHeaderTextArea.SetRect(rcHeader.left+nHeaderRadius, rcHeader.top, rcHeader.right-nHeaderRadius, rcHeader.bottom); // area between
			
			// Draw the "title bar"
			pDC->SelectObject(&pnBorder);
			pDC->SelectObject(&brHeader);
			pDC->Pie(rcPieLeft,  CPoint(rcPieLeft.left+nHeaderRadius, rcPieLeft.top), CPoint(rcPieLeft.left, rcPieLeft.top+nHeaderRadius));
			pDC->Pie(rcPieRight, CPoint(rcPieRight.left+nHeaderRadius, rcPieRight.top+nHeaderRadius), CPoint(rcPieRight.left+nHeaderRadius, rcPieRight.top));
			pDC->FillSolidRect(rcHeaderTextArea, clBorder);
			pDC->FillSolidRect(CRect(rcHeader.left, rcHeader.top+nHeaderRadius, rcHeader.left+nHeaderRadius, rcHeader.bottom), clBorder);
			pDC->FillSolidRect(CRect(rcHeader.right-nHeaderRadius, rcHeader.top+nHeaderRadius, rcHeader.right, rcHeader.bottom), clBorder);
			if (m_uiItemState & ODS_SELECTED) 
				pDC->SetTextColor(clItemArea);
			else
				pDC->SetTextColor(clText);
			pDC->SetBkColor(clBorder);

			// Make the text field a little smaller
			rcHeaderTextArea.left += nHeaderRadius+nHeaderRadius;
			pDC->DrawText(m_strName, rcHeaderTextArea, DT_LEFT|DT_SINGLELINE|DT_VCENTER|DT_END_ELLIPSIS);

			// Draw the Expand/Collapse button
			const int nBtnSpace = 4;
			int nBtnSize;
			nBtnSize = GetSystemMetrics(SM_CYCAPTION)-nBtnSpace-nBtnSpace;
			m_rcExpandCollapseBtn.SetRect(rcItem.right-nHeaderRadius-nBtnSize, rcItem.top +nBtnSpace, rcItem.right-nHeaderRadius, rcItem.top+nBtnSpace+nBtnSize);
			pDC->SelectStockObject(NULL_BRUSH);
			pDC->SelectObject(&pnBtn);
			pDC->Ellipse(m_rcExpandCollapseBtn);
			if (m_uiItemState & ODS_SELECTED) 
				pDC->SelectObject(&pnBtn);
			else
				pDC->SelectObject(&pnMarker);
			CPoint ptCenter = m_rcExpandCollapseBtn.CenterPoint();
			CPoint ptLeft   = ptCenter +CPoint(-4, m_bIsCollapsed ? -4:4);
			CPoint ptRight  = ptCenter +CPoint( 4, m_bIsCollapsed ? -4:4);
			pDC->MoveTo(ptCenter);
			pDC->LineTo(ptLeft);
			pDC->MoveTo(ptCenter);
			pDC->LineTo(ptRight);
			ptCenter.Offset(0, m_bIsCollapsed ?3:-3);
			ptLeft.Offset(0, m_bIsCollapsed ?3:-3);
			ptRight.Offset(0, m_bIsCollapsed ?3:-3);
			pDC->MoveTo(ptCenter);
			pDC->LineTo(ptLeft);
			pDC->MoveTo(ptCenter);
			pDC->LineTo(ptRight);
			m_rcExpandCollapseBtn.left = rcHeader.left;

			// Now draw the lighter area for all items
			pDC->SelectObject(&pnBorder);
			pDC->SelectObject(&brItemArea);
			pDC->Rectangle(rcItem.left, rcHeaderTextArea.bottom-1, rcItem.right, rcItem.bottom);

			// Draw Items
			if (!m_bIsCollapsed)
			{
				CRect rcItems;
				rcItems.SetRect(rcItem.left+1, rcHeaderTextArea.bottom, rcItem.right-1, rcHeaderTextArea.bottom);
				if (m_pImageList) m_pImageList->SetBkColor(clItemArea);
				for (POSITION pos = m_lstItems.GetHeadPosition(); pos;)
				{
					rcItems.top     = rcItems.bottom;
					rcItems.bottom += MulDiv(GetSystemMetrics(SM_CYMENU), 9, 8); // add 12,5% space between the items
					m_lstItems.GetNext(pos)->OnDraw(pDC, rcItems, m_pImageList, *m_pnStyle);
				}
			}
		}
	}
}

int CDirectUIGroup::AddItem(CDirectUIItem *pItem)
{
	m_lstItems.AddTail(pItem);
	CalcHeight();
	return m_lstItems.GetCount()-1;
}

void CDirectUIGroup::RemoveAllItems()
{
	while (m_lstItems.GetCount()) delete m_lstItems.RemoveHead();
	CalcHeight();
}

void CDirectUIGroup::CalcHeight()
{
	m_nHeight = GetSystemMetrics(SM_CYCAPTION);
	if (!m_bIsCollapsed)
		m_nHeight += (m_lstItems.GetCount()*MulDiv(GetSystemMetrics(SM_CYMENU), 9, 8)); // 12.5% Space
}

BOOL CDirectUIGroup::HitTestButton(CPoint point)
{
	if (*m_pnStyle == CWndDirectUI::styleOffice) return FALSE; // Not supported for Office style
	return (PtInRect(m_rcExpandCollapseBtn, point)) ? TRUE:FALSE;
}

CDirectUIItem* CDirectUIGroup::HitTest(CPoint point)
{
	if (m_bIsCollapsed) return FALSE;

	for (POSITION pos = m_lstItems.GetHeadPosition(); pos;)
	{
		CDirectUIItem* pItem = m_lstItems.GetNext(pos);
		if (pItem->HitTest(point)) return pItem;
	}

	return NULL;
}

void CDirectUIGroup::ParseToolbar(CToolBar *pToolbar)
{
	for (POSITION pos = m_lstItems.GetHeadPosition(); pos;)
	{
		CDirectUIItem* pItem = m_lstItems.GetNext(pos);
		pItem->ParseToolbar(pToolbar);
	}
}

BOOL CDirectUIGroup::Expand(BOOL bExpand)
{
	if (m_bIsCollapsed != bExpand) return FALSE;

	m_bIsCollapsed = !bExpand;
	CalcHeight();
	return TRUE;
}

void CDirectUIGroup::ToggleExpand()
{	
	m_bIsCollapsed = !m_bIsCollapsed;
	CalcHeight();
}


/////////////////////////////////////////////////////////////////////////////
// CDirectUIItem

CDirectUIItem::CDirectUIItem(CString strName, UINT uiCommand, int nIconIndex)
{
	m_rcItem.SetRectEmpty();
	m_strName    = strName;
	m_nIconIndex = nIconIndex;
	m_uiCommand  = uiCommand;
	m_uiItemState= 0;
}

void CDirectUIItem::OnDraw(CDC* pDC, CRect rcItem, CImageList* pImageList, int nStyle)
{
	const nBorderX     = 10;
	const nBorderIconX = 4;
	const nIconSize    = 16;

	switch (nStyle)
	{
	case CWndDirectUI::styleOffice:
		{
			m_rcItem = rcItem;

			rcItem.left += nIconSize+nBorderIconX;
			pDC->SelectStockObject(ANSI_VAR_FONT);
			pDC->DrawText(m_strName, rcItem, DT_LEFT|DT_SINGLELINE|DT_VCENTER|DT_END_ELLIPSIS);
			pDC->DrawText(m_strName, m_rcItem, DT_LEFT|DT_SINGLELINE|DT_VCENTER|DT_END_ELLIPSIS|DT_CALCRECT);
			m_rcItem.OffsetRect(nIconSize+nBorderIconX, 0);

			if ((m_nIconIndex != -1) && (pImageList))
				pImageList->Draw(pDC, m_nIconIndex, CPoint(m_rcItem.left-nIconSize-nBorderIconX, rcItem.top+((rcItem.Height()-nIconSize)/2)), ILD_NORMAL);

			if (m_uiItemState & ODS_SELECTED)
			{
				pDC->MoveTo(m_rcItem.left, m_rcItem.bottom);
				pDC->LineTo(m_rcItem.right, m_rcItem.bottom);
			}
		} 
		break;
	case CWndDirectUI::styleXP:
	default:
		{
			rcItem.left  += nBorderX;
			rcItem.right -= nBorderX;

			// When there's an icon place the text more to the right
			if (m_nIconIndex != -1)
				rcItem.left += nIconSize+nBorderIconX;
			
			m_rcItem = rcItem;

			// Item selected? Then draw it brighter
			if (m_uiItemState & ODS_SELECTED)
				pDC->SetTextColor(GetSysColor(COLOR_HIGHLIGHT));
			else
				pDC->SetTextColor(CDirectUIGroup::MakeXPColor(GetSysColor(COLOR_HIGHLIGHT), 0.15));

			pDC->SelectStockObject(ANSI_VAR_FONT);
			pDC->SetBkMode(TRANSPARENT);
			pDC->DrawText(m_strName, rcItem, DT_LEFT|DT_SINGLELINE|DT_VCENTER|DT_END_ELLIPSIS);
			pDC->DrawText(m_strName, m_rcItem, DT_LEFT|DT_SINGLELINE|DT_VCENTER|DT_END_ELLIPSIS|DT_CALCRECT);

			// Item selected? Then underline it (doing it this way I don't need to create an underlined font)
			if (m_uiItemState & ODS_SELECTED)
			{
				pDC->MoveTo(m_rcItem.left, m_rcItem.bottom);
				pDC->LineTo(m_rcItem.right, m_rcItem.bottom);
			}

			if ((m_nIconIndex != -1) && (pImageList))
			{
				// Finally draw the icon
				m_rcItem.left -= (nBorderIconX+nIconSize);
				if (m_uiItemState & ODS_SELECTED)
					pImageList->Draw(pDC, m_nIconIndex, CPoint(m_rcItem.left, rcItem.top+((rcItem.Height()-nIconSize)/2)), ILD_SELECTED);
				else
					pImageList->Draw(pDC, m_nIconIndex, CPoint(m_rcItem.left, rcItem.top+((rcItem.Height()-nIconSize)/2)), ILD_NORMAL);
			}
		}
	}
}

BOOL CDirectUIItem::HitTest(CPoint point)
{
	return (PtInRect(m_rcItem, point)) ? TRUE:FALSE;
}

void CDirectUIItem::ParseToolbar(CToolBar *pToolbar)
{
	ASSERT(pToolbar);
	int nBitmap = pToolbar->SendMessage(TB_GETBITMAP, m_uiCommand, 0);
	if (nBitmap > 0) 
		m_nIconIndex = nBitmap; 
	else 
		m_nIconIndex = -1;
}


/////////////////////////////////////////////////////////////////////////////
// CWndDirectUI

BEGIN_MESSAGE_MAP(CWndDirectUI, CWnd)
	//{{AFX_MSG_MAP(CWndDirectUI)
	ON_WM_PAINT()
	ON_WM_ERASEBKGND()
	ON_WM_SETCURSOR()
	ON_WM_LBUTTONDOWN()
	ON_WM_MOUSEMOVE()
	ON_WM_LBUTTONUP()
	ON_WM_VSCROLL()
	//}}AFX_MSG_MAP
END_MESSAGE_MAP()


/////////////////////////////////////////////////////////////////////////////
// Behandlungsroutinen f�r Nachrichten CWndDirectUI 

CWndDirectUI::CWndDirectUI() 
{
	m_pLastHitItem  = NULL;
	m_pLastHitGroup = NULL;
	m_hHand         = AfxGetApp()->LoadCursor(IDC_MYHAND);
	VERIFY(m_hHand);  // If asserts, add this cursor to your resource
	m_nStyle        = styleXP;
	m_lstImages.Create(1, 1, ILC_COLOR,1, 1);
}

CWndDirectUI::~CWndDirectUI() 
{
	RemoveAll();
}

void CWndDirectUI::OnPaint() 
{
	const int nSpaceX = 10;
	const int nSpaceY = 10;

	CPaintDC dc(this); // device context for painting

	CRect rc;
	CRect rcItem;
	GetWindowRect(&rc);
	rc -= rc.TopLeft();

	BOOL bHasScrollbar;
	BOOL bNeedScrollbar;
	int  nOffsetY;

	bHasScrollbar  = ::GetWindowLong(m_hWnd, GWL_STYLE) & WS_VSCROLL ? TRUE:FALSE;
	if (bHasScrollbar) 
	{
		rc.right -= GetSystemMetrics(SM_CXHTHUMB);
		nOffsetY = GetScrollPos(SB_VERT);
	}
	else
		nOffsetY = 0;

	rc.bottom +=nOffsetY; 
	CMemDC MemDC(&dc, &rc);

	dc.SetWindowOrg(0, nOffsetY);
	
	switch (m_nStyle)
	{
	case styleXPclassic:
	case styleOffice:
		MemDC.FillSolidRect(rc, GetSysColor(COLOR_WINDOW));
		break;
	case styleXP:
	default:
		MemDC.FillSolidRect(rc, CDirectUIGroup::MakeXPColor(GetSysColor(COLOR_HIGHLIGHT), 0.4));
	}

	rcItem.SetRect(nSpaceX, 0, rc.right - nSpaceX, 0);
	for (POSITION pos = m_lstGroups.GetHeadPosition(); pos;)
	{
		CDirectUIGroup* pGroup = m_lstGroups.GetNext(pos);
		rcItem.top = rcItem.bottom + nSpaceY;
		rcItem.bottom = rcItem.top + pGroup->m_nHeight;
		pGroup->OnDraw(&MemDC, rcItem);
	}

	// Show Scrollbar if needed
	bNeedScrollbar = ((rc.Height()-nOffsetY) < (rcItem.bottom+nSpaceY)) ? TRUE:FALSE;
	if (bNeedScrollbar != bHasScrollbar)
	{
		if (bNeedScrollbar) 
			ShowScrollBar(SB_VERT, TRUE);
		else
			ShowScrollBar(SB_VERT, FALSE);
		Invalidate();
	}
	
	if (bNeedScrollbar)
	{
		// Set Scrollbar range
		SCROLLINFO si;
		si.cbSize = sizeof(si);
		si.fMask  = SIF_PAGE|SIF_RANGE;
		si.nMin   = 0;
		si.nMax   = rcItem.bottom+nSpaceY;
		si.nPage  = rc.Height()-nOffsetY;
		SetScrollInfo(SB_VERT, &si, TRUE);
	}
}

BOOL CWndDirectUI::OnEraseBkgnd(CDC* pDC) 
{
	return TRUE;
}

void CWndDirectUI::RemoveAll()
{
	while (m_lstGroups.GetCount()) delete m_lstGroups.RemoveHead();
}

int CWndDirectUI::AddGroup(CString strName)
{
	CDirectUIGroup* pGroup = new CDirectUIGroup(strName, &m_lstImages, &m_nStyle);
	m_lstGroups.AddTail(pGroup);

	InvalidateIfPossible();
	
	return m_lstGroups.GetCount()-1;
}

BOOL CWndDirectUI::RemoveGroup(int nGroup)
{
	CDirectUIGroup* pGroup = GetGroupByNumber(nGroup);
	if (!pGroup) return FALSE;

	POSITION pos = m_lstGroups.Find(pGroup);
	ASSERT(pos);
	if (!pos) return FALSE;

	m_lstGroups.RemoveAt(pos);
	delete pGroup;

	InvalidateIfPossible();
	return TRUE;
}

CString& CWndDirectUI::GetGroupName(int nGroup)
{
	static CString strNull;
	CDirectUIGroup* pGroup = GetGroupByNumber(nGroup);
	if (!pGroup) return strNull;

	return pGroup->m_strName;
}

BOOL CWndDirectUI::ChangeGroupName(int nGroup, CString strName)
{
	CDirectUIGroup* pGroup = GetGroupByNumber(nGroup);
	if (!pGroup) return FALSE;

	pGroup->m_strName = strName;
	InvalidateIfPossible();

	return TRUE;
}

BOOL CWndDirectUI::ExpandGroup(int nGroup, BOOL bExpand)
{
	CDirectUIGroup* pGroup = GetGroupByNumber(nGroup);
	if (!pGroup) return FALSE;

	if (pGroup->Expand(bExpand)) InvalidateIfPossible();

	return TRUE;
}

BOOL CWndDirectUI::InitFromMenu(UINT id)
{
	// Main funtion! Use a 2D Menu
	RemoveAll();
	CMenu mnuGroups;
	MENUITEMINFO info;

	if (!mnuGroups.LoadMenu(id)) return FALSE;
	
	int i = 0;
	int j = 0;
	while (mnuGroups.GetSubMenu(i))
	{
		CMenu* pMenu = mnuGroups.GetSubMenu(i);
		CString strName;
		mnuGroups.GetMenuString(i, strName, MF_BYPOSITION);
		CDirectUIGroup* pGroup = new CDirectUIGroup(strName, &m_lstImages, &m_nStyle);

		info.cbSize = sizeof (MENUITEMINFO);
		info.fMask = MIIM_ID;

		j = 0;
		while (pMenu->GetMenuString(j, strName, MF_BYPOSITION))
		{
			pMenu->GetMenuItemInfo(j, &info, TRUE);
			pGroup->AddItem(new CDirectUIItem(strName, info.wID));
			j++;
		}

		m_lstGroups.AddTail(pGroup);
		i++;
	}

	InvalidateIfPossible();
	return TRUE;
}

BOOL CWndDirectUI::OnSetCursor(CWnd* pWnd, UINT nHitTest, UINT message) 
{
	if (((m_pLastHitItem != NULL) | (m_pLastHitGroup != NULL)) & (nHitTest == HTCLIENT))
	{
		SetCursor(m_hHand);
		return TRUE;
	}
	else
		return CWnd::OnSetCursor(pWnd, nHitTest, message);
}

void CWndDirectUI::OnLButtonDown(UINT nFlags, CPoint point) 
{
	CWnd::OnLButtonDown(nFlags, point);
	if (m_pLastHitGroup)
	{
		m_pLastHitGroup->ToggleExpand();
		Invalidate();
	}
}

void CWndDirectUI::OnMouseMove(UINT nFlags, CPoint point) 
{
	// Do the hoover thing
	CDirectUIItem*  pItem;
	CDirectUIGroup* pCurrentGroup;
	CDirectUIGroup* pGroup;
	pItem  = NULL;
	pGroup = NULL;

	if (::GetWindowLong(m_hWnd, GWL_STYLE) & WS_VSCROLL)
		point.y += GetScrollPos(SB_VERT);

	for (POSITION pos = m_lstGroups.GetHeadPosition(); pos;)
	{
		pCurrentGroup = m_lstGroups.GetNext(pos);
		if (pCurrentGroup->HitTestButton(point))
		{
			pGroup = pCurrentGroup;
			break;
		}
		pItem   = pCurrentGroup->HitTest(point);
		if (pItem != NULL) break;
	}

	if (pGroup != m_pLastHitGroup)
	{
		if (m_pLastHitGroup) m_pLastHitGroup->m_uiItemState &= ~ODS_SELECTED;
		m_pLastHitGroup = pGroup;
		if (m_pLastHitGroup) m_pLastHitGroup->m_uiItemState |= ODS_SELECTED;
		Invalidate();
	}
	else if (pItem  != m_pLastHitItem)	
	{
		// Draw Item
		if (m_pLastHitItem) m_pLastHitItem->m_uiItemState &= ~ODS_SELECTED;
		m_pLastHitItem = pItem;
		if (m_pLastHitItem) m_pLastHitItem->m_uiItemState |= ODS_SELECTED;
		Invalidate();
	}

	CWnd::OnMouseMove(nFlags, point);
}

void CWndDirectUI::OnLButtonUp(UINT nFlags, CPoint point) 
{
	if (m_pLastHitItem)
		AfxGetMainWnd()->PostMessage(WM_COMMAND, m_pLastHitItem->m_uiCommand, 0);
	
	CWnd::OnLButtonUp(nFlags, point);
}

CDirectUIGroup* CWndDirectUI::GetGroupByNumber(int nPos)
{
	if (nPos < 0) return NULL;

	CDirectUIGroup* pGroup;
	for (POSITION pos = m_lstGroups.GetHeadPosition(); pos;)
	{
		pGroup = m_lstGroups.GetNext(pos);
		if (nPos == 0) return pGroup;
		nPos--;
	}
	return NULL;
}

void CWndDirectUI::InvalidateIfPossible()
{
	if (IsWindow(m_hWnd)) Invalidate();
}

BOOL CWndDirectUI::SetToolbarImages(UINT uiToolbar)
{
	// Use this function after inserting all items and groups

	// Create an invisible toolbar and let it do all the work
	CToolBar tbToolbar;
	if (!tbToolbar.Create(this, WS_CHILD, 42)) return FALSE;
	if (!tbToolbar.LoadToolBar(uiToolbar)) return FALSE;

	m_lstImages.Detach();
	if (!m_lstImages.Create(CImageList::FromHandle((HIMAGELIST)tbToolbar.SendMessage(TB_GETIMAGELIST, 0, 0)))) return FALSE;
	
	for (POSITION pos = m_lstGroups.GetHeadPosition(); pos;)
		m_lstGroups.GetNext(pos)->ParseToolbar(&tbToolbar);

	InvalidateIfPossible();
	return TRUE;
}

BOOL CWndDirectUI::AddItem(int nGroup, CString strItem, UINT uiCommand)
{
	CDirectUIGroup* pGroup = GetGroupByNumber(nGroup);
	if (!pGroup) return FALSE;
	pGroup->AddItem(new CDirectUIItem(strItem, uiCommand));
	InvalidateIfPossible();
	return TRUE;
}

BOOL CWndDirectUI::SetStyle(int nStyle)
{
	m_nStyle = nStyle;
	
	for (POSITION pos = m_lstGroups.GetHeadPosition(); pos;)
		m_lstGroups.GetNext(pos)->CalcHeight();

	InvalidateIfPossible();
	return TRUE;
}

int CWndDirectUI::GetStyle()
{
	return m_nStyle;
}


void CWndDirectUI::OnVScroll(UINT nSBCode, UINT nPos, CScrollBar* pScrollBar) 
{
	switch (nSBCode)
	{
	case SB_BOTTOM:
		nPos = GetScrollLimit(SB_VERT);
		break;
	case SB_ENDSCROLL:   
		return;
	case SB_LINEDOWN:
		nPos = GetScrollPos(SB_VERT) +1;
		break;
	case SB_LINEUP:
		nPos = GetScrollPos(SB_VERT) -1;
		break;
	case SB_PAGEDOWN:
		nPos = GetScrollPos(SB_VERT) + MulDiv(GetSystemMetrics(SM_CYMENU), 9, 8);
		break;
	case SB_PAGEUP:
		nPos = GetScrollPos(SB_VERT) - MulDiv(GetSystemMetrics(SM_CYMENU), 9, 8);
		break;
	case SB_THUMBPOSITION:
	case SB_THUMBTRACK:
		break;
	case SB_TOP:
		nPos = 0;
		break;
	}
	Invalidate();
	SetScrollPos(SB_VERT, nPos, TRUE);
}

BOOL CWndDirectUI::Create(const RECT &rect, CWnd *pParentWnd, UINT nID)
{
	return CWnd::Create(NULL, _T("WndDirectUI"), WS_CHILD|WS_VISIBLE, rect, pParentWnd, nID);
}

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)


Written By
Web Developer
Germany Germany
Ok, a few words about me:

I started programming on the C64 by the "trial and error" method. Years later my parents got their first PC (Atari PC4, AT 8 MHz) where I started with Turbo Pascal. The next steps led to Turbo Pascal for Windows, Visual C++ 1.52c, and finally Visual C++ 6.

I had several chances to code larger projects, e.g.
* stand-alone disc copy station software (Win 3.1) with automatic reboot, disc encryption, ...
* government-used strategic decision system
* MLM marketing tool (www.upline.de)
* maintenance for show planning system and other TV software (www.hse24.de)

A lot of code, tipps and help came from this site for my later projects. Thanks again to all who helped me, even if they don't know it. I'm trying to share code (which is worth sharing) so others can get the help I got (and still need) for everyday problems.

Well, I think that's enough.

Comments and Discussions