Click here to Skip to main content
15,892,059 members
Articles / Desktop Programming / MFC

Report control - an Outlook 2000 style SuperGrid Report Control

Rate me:
Please Sign up or sign in to vote.
4.90/5 (44 votes)
14 Jun 2001 1.5M   8.7K   152  
An Outlook 98/2000 Style SuperGrid Report Control that tries to overcome some of the weaknesses of other implementations.
////////////////////////////////////////////////////////////////////////////
//	File:		CFlatHeaderCtrl.cpp
//	Version:	1.0.7
//
//	Author:		Maarten Hoeben
//	E-mail:		hoeben@nwn.com
//
//	Implementation of the CFlatHeaderCtrl and associated classes.
//
//	This code may be used in compiled form in any way you desire. This
//	file may be redistributed unmodified by any means PROVIDING it is 
//	not sold for profit without the authors written consent, and 
//	providing that this notice and the authors name and all copyright 
//	notices remains intact. 
//
//	An email letting me know how you are using it would be nice as well. 
//
//	This file is provided "as is" with no expressed or implied warranty.
//	The author accepts no liability for any damage/loss of business that
//	this product may cause.
//
//	Version history
//
//	1.0.0	- Initial release
//	1.0.1	- Fixed FHDragWnd destroy warning (thanks Philippe Terrier)
//			- Fixed double sent HDN_ITEMCLICK
//			- Added a property that adjusts for ListCtrls that use a static
//			  border for flat look.
//	1.0.2	- Fixed another destroy warning
//			- Fixed InsertItem array exception handling
//			- Fixed incorrect header width painting
//			- Changed DrawItem argument passing
//			- Changed HDITEMEX struct item names
//			- Added handler for HDM_SETIMAGELIST (precalculate image dimensions)
//			- Changed DrawImage to clip images
//			- Changed InsertItem ASSERT check to position limitation
//			- Added new-style "HotDivider" arrows
//			- Fixed some GDI objects
//			- Added 'don't drop cursor' support to indicate drag&drop 
//			  outside control
//			- Added drag&drop target window support
//			- Changed CFHDragWnd to support externally created items
//			- Removed topmost-style from CFHDropWnd
//			- Fixed OnSetHotDivider order bug
//			- Added extended styles
//			- Added item width estimation function
//	1.0.3	- Added WM_CANCELMODE handler
//	1.0.4	- Changed copyright message
//			- Added tooltip style
//	1.0.5	- Added persistent style
//			- Added definitions for drop result
//			- Added height manipulation functions
//	1.0.6	- Fixed bitmap drawing resource leak
//			- Added proper font handling.
//	1.0.7	- Little changes to Min/Max implementation
//			- Fixed ghost drag problem related to button down on header
//			  area outside valid items.
//			- Added Get/SetItemExStyle() functions.
//			- Added code for auto column sizing, enable through
//			  FH_PROPERTY_ENABLEAUTOSIZE property.
//			- Added AutoSizeItems() function to force auto column resizing
//			  programatically.
//
////////////////////////////////////////////////////////////////////////////


// FlatHeaderCtrl.cpp : implementation file
//

#include "stdafx.h"
#include "FlatHeaderCtrl.h"

#include <afxpriv.h>

#include "MemDC.h"

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

/////////////////////////////////////////////////////////////////////////////
// CFHDragWnd

CFHDragWnd::CFHDragWnd()
{
	// Register the window class if it has not already been registered.
	WNDCLASS wndclass;
	HINSTANCE hInst = AfxGetInstanceHandle();

	if(!(::GetClassInfo(hInst, FHDRAGWND_CLASSNAME, &wndclass)))
	{
		// otherwise we need to register a new class
		wndclass.style = CS_SAVEBITS ;
		wndclass.lpfnWndProc = ::DefWindowProc;
		wndclass.cbClsExtra = wndclass.cbWndExtra = 0;
		wndclass.hInstance = hInst;
		wndclass.hIcon = NULL;
		wndclass.hCursor = LoadCursor( hInst, IDC_ARROW);
		wndclass.hbrBackground = (HBRUSH)(COLOR_3DFACE + 1); 
		wndclass.lpszMenuName = NULL;
		wndclass.lpszClassName = FHDRAGWND_CLASSNAME;
		if (!AfxRegisterClass(&wndclass))
			AfxThrowResourceException();
	}

	m_pFlatHeaderCtrl = NULL;
	m_iItem = -1;
	m_lphdiItem = NULL;
}

CFHDragWnd::~CFHDragWnd()
{
}


BEGIN_MESSAGE_MAP(CFHDragWnd, CWnd)
	//{{AFX_MSG_MAP(CFHDragWnd)
	ON_WM_PAINT()
	ON_WM_ERASEBKGND()
	//}}AFX_MSG_MAP
END_MESSAGE_MAP()


/////////////////////////////////////////////////////////////////////////////
// CFHDragWnd message handlers

BOOL CFHDragWnd::Create(CRect rect, CFlatHeaderCtrl* pFlatHeaderCtrl, INT iItem, LPHDITEM lphdiItem)
{
	ASSERT_VALID(pFlatHeaderCtrl);
	ASSERT(pFlatHeaderCtrl->IsKindOf(RUNTIME_CLASS(CFlatHeaderCtrl)));

	m_pFlatHeaderCtrl = pFlatHeaderCtrl;
	m_iItem = iItem;
	m_lphdiItem = lphdiItem;

	DWORD dwStyle = WS_POPUP|WS_DISABLED;
	DWORD dwExStyle = WS_EX_TOOLWINDOW|WS_EX_TOPMOST;

	return CreateEx(dwExStyle, FHDRAGWND_CLASSNAME, NULL, dwStyle, 
		rect.left, rect.top, rect.Width(), rect.Height(),
		NULL, NULL, NULL );
}


void CFHDragWnd::OnPaint() 
{
	CPaintDC dc(this);
	
    if(m_pFlatHeaderCtrl->m_bDoubleBuffer)
    {
        CMemDC MemDC(&dc);
        OnDraw(&MemDC);
    }
    else
        OnDraw(&dc);
}

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

void CFHDragWnd::OnDraw(CDC* pDC)
{
	CRect rect;
	GetClientRect(rect);

    pDC->FillSolidRect(rect, m_pFlatHeaderCtrl->m_cr3DFace);
	pDC->Draw3dRect(rect, m_pFlatHeaderCtrl->m_cr3DHighLight, m_pFlatHeaderCtrl->m_cr3DShadow);

	CPen* pPen = pDC->GetCurrentPen();
	CFont* pFont = pDC->SelectObject(m_pFlatHeaderCtrl->GetFont());

	pDC->SetBkColor(m_pFlatHeaderCtrl->m_cr3DFace);
	pDC->SetTextColor(m_pFlatHeaderCtrl->m_crText);

	rect.DeflateRect(m_pFlatHeaderCtrl->m_iSpacing, 0);
	m_pFlatHeaderCtrl->DrawItem(
		pDC,
		rect,
		m_lphdiItem,
		m_pFlatHeaderCtrl->m_iSortColumn == m_iItem,
		m_pFlatHeaderCtrl->m_bSortAscending
	);

	pDC->SelectObject(pFont);
	pDC->SelectObject(pPen);
}

void CFHDragWnd::PostNcDestroy() 
{
	CWnd::PostNcDestroy();
	delete this;
}

/////////////////////////////////////////////////////////////////////////////
// CFHDropWnd

CFHDropWnd::CFHDropWnd(COLORREF crColor)
{
	m_brush.CreateSolidBrush(crColor);

	// Register the window class if it has not already been registered.
	WNDCLASS wndclass;
	HINSTANCE hInst = AfxGetInstanceHandle();

	if(!(::GetClassInfo(hInst, FHDROPWND_CLASSNAME, &wndclass)))
	{
		// otherwise we need to register a new class
		wndclass.style = CS_SAVEBITS ;
		wndclass.lpfnWndProc = ::DefWindowProc;
		wndclass.cbClsExtra = wndclass.cbWndExtra = 0;
		wndclass.hInstance = hInst;
		wndclass.hIcon = NULL;
		wndclass.hCursor = LoadCursor( hInst, IDC_ARROW );
		wndclass.hbrBackground = (HBRUSH)m_brush; 
		wndclass.lpszMenuName = NULL;
		wndclass.lpszClassName = FHDROPWND_CLASSNAME;
		if (!AfxRegisterClass(&wndclass))
			AfxThrowResourceException();
	}
}

CFHDropWnd::~CFHDropWnd()
{
}


BEGIN_MESSAGE_MAP(CFHDropWnd, CWnd)
	//{{AFX_MSG_MAP(CFHDropWnd)
	ON_WM_ERASEBKGND()
	//}}AFX_MSG_MAP
END_MESSAGE_MAP()


/////////////////////////////////////////////////////////////////////////////
// CFHDropWnd message handlers

BOOL CFHDropWnd::Create(INT iHeight)
{
	m_iHeight = iHeight + 20;

	DWORD dwStyle = WS_POPUP|WS_DISABLED;
	DWORD dwExStyle = WS_EX_TOOLWINDOW ;

	BOOL bResult = CreateEx(dwExStyle, FHDROPWND_CLASSNAME, NULL, dwStyle, 
		0, 0, 12, m_iHeight,
		NULL, NULL, NULL );

	CRgn rgn1, rgn2;
	POINT ptArrow[7];

	ptArrow[0].x = 8;	ptArrow[0].y = 0;
	ptArrow[1].x = 8;	ptArrow[1].y = 4;
	ptArrow[2].x = 11;	ptArrow[2].y = 4;
	ptArrow[3].x = 6;	ptArrow[3].y = 9;
	ptArrow[4].x = 1;	ptArrow[4].y = 4;
	ptArrow[5].x = 4;	ptArrow[5].y = 4;
	ptArrow[6].x = 4;	ptArrow[6].y = 0;
	rgn1.CreatePolygonRgn(ptArrow, 7, ALTERNATE);


	ptArrow[0].x = 4;	ptArrow[0].y = m_iHeight;
	ptArrow[1].x = 4;	ptArrow[1].y = m_iHeight-4;
	ptArrow[2].x = 0;	ptArrow[2].y = m_iHeight-4;
	ptArrow[3].x = 6;	ptArrow[3].y = m_iHeight-10;
	ptArrow[4].x = 12;	ptArrow[4].y = m_iHeight-4;
	ptArrow[5].x = 8;	ptArrow[5].y = m_iHeight-4;
	ptArrow[6].x = 8;	ptArrow[6].y = m_iHeight;
	rgn2.CreatePolygonRgn(ptArrow, 7, ALTERNATE);

	m_rgn.CreateRectRgn(0, 0, 12, iHeight);
	m_rgn.CombineRgn(&rgn1, &rgn2, RGN_OR);
	SetWindowRgn(m_rgn, FALSE);

	rgn1.DeleteObject();
	rgn2.DeleteObject();

	return bResult;
}


void CFHDropWnd::PostNcDestroy() 
{
	m_rgn.DeleteObject();

	CWnd::PostNcDestroy();
	delete this;
}

BOOL CFHDropWnd::OnEraseBkgnd(CDC* pDC) 
{
	pDC->FillRect(CRect(0, 0, 12, m_iHeight), &m_brush);
	return TRUE;
}

void CFHDropWnd::SetWindowPos(INT x, INT y)
{
	CWnd::SetWindowPos(
		&wndTop, 
		x-6, y-(m_iHeight/2),
		0, 0, SWP_NOSIZE|SWP_SHOWWINDOW|SWP_NOACTIVATE
	);
}

/////////////////////////////////////////////////////////////////////////////
// CFlatHeaderCtrl

IMPLEMENT_DYNCREATE(CFlatHeaderCtrl, CHeaderCtrl)

CFlatHeaderCtrl::CFlatHeaderCtrl()
{
	m_bDoubleBuffer = TRUE;
	m_iSpacing = 6;
	m_sizeArrow.cx = 8;
	m_sizeArrow.cy = 8;
	m_sizeImage.cx = 0;
	m_sizeImage.cy = 0;
	m_bStaticBorder = FALSE;
	m_nDontDropCursor = 0;
	m_hDropTarget = NULL;
	m_rcDropTarget.SetRectEmpty();
	m_iDropResult = FHDR_ONHEADER;

	m_iHotIndex = -1;
	m_bHotItemResizable = FALSE;

	m_bAutoSize = FALSE;
	m_bAutoSizing = FALSE;
	m_bResizing = FALSE;
	m_iResizingWidth = 0;

	m_iHotDivider = -1;
	m_crHotDivider = 0x000000FF;
	m_pDropWnd = NULL;

	m_bButtonDownOnItem = FALSE;

	m_bDragging = FALSE;
	m_pDragWnd = NULL;

	m_nClickFlags = 0;

	m_bSortAscending = FALSE;
	m_iSortColumn = -1;
	m_arrayHdrItemEx.SetSize(0, 8);

	m_iHeight = -1;

	m_cr3DHighLight = ::GetSysColor(COLOR_3DHIGHLIGHT);
	m_cr3DShadow = ::GetSysColor(COLOR_3DSHADOW);
	m_cr3DFace = ::GetSysColor(COLOR_3DFACE);
	m_crText = ::GetSysColor(COLOR_BTNTEXT);

	m_font.CreateStockObject(DEFAULT_GUI_FONT);
}

CFlatHeaderCtrl::~CFlatHeaderCtrl()
{
    if(m_font.m_hObject)
		m_font.DeleteObject();

	if(m_pDropWnd != NULL)
	{
		m_pDropWnd->DestroyWindow();
		m_pDropWnd = NULL;
	}

	if(m_pDragWnd != NULL)
	{
		m_pDragWnd->DestroyWindow();
		m_pDragWnd = NULL;
	}
}

BEGIN_MESSAGE_MAP(CFlatHeaderCtrl, CHeaderCtrl)
	//{{AFX_MSG_MAP(CFlatHeaderCtrl)
	ON_MESSAGE(HDM_INSERTITEMA, OnInsertItem)
	ON_MESSAGE(HDM_INSERTITEMW, OnInsertItem)
	ON_MESSAGE(HDM_DELETEITEM, OnDeleteItem)
	ON_MESSAGE(HDM_SETIMAGELIST, OnSetImageList)
	ON_MESSAGE(HDM_SETHOTDIVIDER, OnSetHotDivider)
	ON_MESSAGE(HDM_LAYOUT, OnLayout)
	ON_NOTIFY_EX(TTN_NEEDTEXTA, 0, OnToolTipNotify)
	ON_NOTIFY_EX(TTN_NEEDTEXTW, 0, OnToolTipNotify)
	ON_WM_NCHITTEST()
	ON_WM_SETCURSOR()
	ON_WM_LBUTTONDOWN()
	ON_WM_LBUTTONDBLCLK()
	ON_WM_PAINT()
	ON_WM_SYSCOLORCHANGE()
	ON_WM_ERASEBKGND()
	ON_WM_LBUTTONUP()
	ON_WM_MOUSEMOVE()
	ON_WM_CANCELMODE()
	//}}AFX_MSG_MAP
    ON_MESSAGE(WM_SETFONT, OnSetFont)
    ON_MESSAGE(WM_GETFONT, OnGetFont)
END_MESSAGE_MAP()

/////////////////////////////////////////////////////////////////////////////
// CFlatHeaderCtrl attributes

BOOL CFlatHeaderCtrl::ModifyProperty(WPARAM wParam, LPARAM lParam)
{
	switch(wParam)
	{
	case FH_PROPERTY_SPACING:
		m_iSpacing = (INT)lParam;
		break;

	case FH_PROPERTY_ARROW:
		m_sizeArrow.cx = LOWORD(lParam);
		m_sizeArrow.cy = HIWORD(lParam);
		break;

	case FH_PROPERTY_STATICBORDER:
		m_bStaticBorder = (BOOL)lParam;
		break;

	case FH_PROPERTY_DONTDROPCURSOR:
		m_nDontDropCursor = (UINT)lParam;
		break;

	case FH_PROPERTY_DROPTARGET:
		m_hDropTarget = (HWND)lParam;
		break;

	case FH_PROPERTY_ENABLETOOLTIPS:
		EnableToolTips((BOOL)lParam);
		break;

	case FH_PROPERTY_ENABLEAUTOSIZE:
		m_bAutoSize = (BOOL)lParam;

		if(m_bAutoSize == TRUE)
			AutoSizeItems();
		break;

	default:
		return FALSE;
	}

	Invalidate();
	return TRUE;
}

BOOL CFlatHeaderCtrl::GetItemEx(INT iPos, HDITEMEX* phditemex) const
{
	if(iPos >= m_arrayHdrItemEx.GetSize())
		return FALSE;

	HDITEM hditem;
	hditem.mask = HDI_WIDTH;
	VERIFY(GetItem(iPos, &hditem));

	HDITEMEX& hditemex = m_arrayHdrItemEx[iPos];

	phditemex->nStyle = hditemex.nStyle;
	phditemex->iMinWidth = hditemex.nStyle&HDF_EX_FIXEDWIDTH ? hditem.cxy:hditemex.iMinWidth;
	phditemex->iMaxWidth = hditemex.nStyle&HDF_EX_FIXEDWIDTH ? hditem.cxy:hditemex.iMaxWidth;
	phditemex->strToolTip = hditemex.strToolTip;
	return TRUE;
}

BOOL CFlatHeaderCtrl::SetItemEx(INT iPos, HDITEMEX* phditemex)
{
	if(iPos>=m_arrayHdrItemEx.GetSize())
		return FALSE;

	BOOL bUpdate = FALSE;

	HDITEM hditem;
	hditem.mask = HDI_WIDTH;
	if(!GetItem(iPos, &hditem))
		return FALSE;

	HDITEMEX hditemex = *phditemex;

	if(hditemex.nStyle&HDF_EX_AUTOWIDTH)
	{
		TCHAR szText[FLATHEADER_TEXT_MAX];

		HDITEM hdi;
		hdi.mask = HDI_WIDTH|HDI_FORMAT|HDI_TEXT|HDI_IMAGE|HDI_BITMAP;
		hdi.pszText = szText;
		hdi.cchTextMax = sizeof(szText);
		VERIFY(GetItem(iPos, &hdi));

		hditem.cxy = GetItemWidth(&hdi, hditemex.nStyle&HDF_EX_INCLUDESORT ? TRUE:FALSE);
		bUpdate = TRUE;
	}

	if(!(hditemex.nStyle&HDF_EX_FIXEDWIDTH))
	{
		if(hditem.cxy < hditemex.iMinWidth)
		{
			hditem.cxy = hditemex.iMinWidth;
			bUpdate = TRUE;
		}

		if(hditem.cxy > hditemex.iMaxWidth && hditemex.iMaxWidth > 0)
		{
			hditem.cxy = hditemex.iMaxWidth;
			bUpdate = TRUE;
		}
	}

	if(bUpdate)
		SetItem(iPos, &hditem);

	m_arrayHdrItemEx.SetAt(iPos, hditemex);
	return TRUE;
}

UINT CFlatHeaderCtrl::GetItemExStyle(INT iPos)
{
	ASSERT(iPos < m_arrayHdrItemEx.GetSize());
	return m_arrayHdrItemEx[iPos].nStyle;
}

BOOL CFlatHeaderCtrl::SetItemExStyle(INT iPos, UINT nStyle)
{
	if(iPos >= m_arrayHdrItemEx.GetSize())
		return FALSE;

	m_arrayHdrItemEx[iPos].nStyle = nStyle;
	return TRUE;
}

INT CFlatHeaderCtrl::GetItemWidth(LPHDITEM lphdi, BOOL bIncludeSort)
{
	INT iWidth = 0;

	CBitmap* pBitmap = NULL;
	BITMAP biBitmap;
	if(lphdi->fmt&HDF_BITMAP)
	{
		ASSERT(lphdi->mask&HDI_BITMAP);
		ASSERT(lphdi->hbm);

		pBitmap = CBitmap::FromHandle(lphdi->hbm);
		if(pBitmap)
			VERIFY(pBitmap->GetObject(sizeof(BITMAP), &biBitmap));
	}

	iWidth += m_iSpacing;
	iWidth += lphdi->fmt&HDF_IMAGE ? m_sizeImage.cx+m_iSpacing:0;
	iWidth += lphdi->fmt&HDF_BITMAP ? biBitmap.bmWidth+m_iSpacing:0;
	iWidth += bIncludeSort ? m_sizeArrow.cx+m_iSpacing:0;

	if(lphdi->mask&HDI_TEXT && lphdi->fmt&HDF_STRING)
	{
		CClientDC dc(this);
		CFont* pFont = dc.SelectObject(GetFont());

		iWidth += dc.GetTextExtent(lphdi->pszText).cx + m_iSpacing;

		dc.SelectObject(pFont);
	}

	return iWidth;
}

void CFlatHeaderCtrl::SetSortColumn(INT iPos, BOOL bSortAscending)
{
	ASSERT(iPos < GetItemCount());

	m_bSortAscending = bSortAscending;
	m_iSortColumn = iPos;
	Invalidate();
}

INT CFlatHeaderCtrl::GetSortColumn(BOOL* pbSortAscending)
{
	if(pbSortAscending)
		*pbSortAscending = m_bSortAscending;

	return m_iSortColumn;
}

INT CFlatHeaderCtrl::GetDropResult()
{
	return m_iDropResult;
}

INT CFlatHeaderCtrl::GetHeight()
{
	if(m_iHeight <= 0)
	{
		CRect rect;
		GetWindowRect(rect);

		return rect.Height();
	}
	else
		return m_iHeight;
}

void CFlatHeaderCtrl::SetHeight(INT iHeight)
{
	m_iHeight = iHeight;
}

BOOL CFlatHeaderCtrl::IsAutoSizing()
{
	return m_bAutoSizing;
}

/////////////////////////////////////////////////////////////////////////////
// CFlatHeaderCtrl operations

void CFlatHeaderCtrl::AutoSizeItems()
{
	AutoSizeItemsImpl();
}

/////////////////////////////////////////////////////////////////////////////
// CFlatHeaderCtrl implementation

#define MAXWIDTH(x) (x>0 ? x:16384)

typedef struct structFHAutoSize
{
	INT iIndex;
	INT iWidth;
	INT iMinWidth;
	INT iMaxWidth;
} FHAUTOSIZE, LPFHAUTOSIZE;

void CFlatHeaderCtrl::AutoSizeItemsImpl(INT iItem)
{
	ASSERT(iItem >= -1);

	INT i, iItems = GetItemCount(), iTemp;
	INT iOrder = -1;

	if(iItems <= 0)
		return;

	HDITEM hditem;
	HDITEMEX hditemex;

	if(iItem >= 0)
	{
		hditem.mask = HDI_ORDER;
		GetItem(iItem, &hditem);

		iOrder = hditem.iOrder;

		if(iOrder == iItems-1)
		{
			iItem = -1;
			iOrder = -1;
		}
	}

	CArray<FHAUTOSIZE, FHAUTOSIZE&> arrayItems;
	arrayItems.SetSize(iItems);

	INT iWidth = 0;
	INT iMinWidth = 0;
	INT iMaxWidth = 0;

	for(i=iOrder+1; i<iItems; i++)
	{
		INT iIndex = OrderToIndex(i);

		hditem.mask = HDI_WIDTH;
		VERIFY(GetItem(iIndex, &hditem));
		VERIFY(GetItemEx(iIndex, &hditemex));

		FHAUTOSIZE fhas;
		fhas.iIndex = iIndex;
		fhas.iWidth = hditem.cxy;
		fhas.iMinWidth = hditemex.iMinWidth;
		fhas.iMaxWidth = hditemex.iMaxWidth;
		arrayItems[i] = fhas;

		iWidth += fhas.iWidth;
		iMinWidth += fhas.iMinWidth;
		iMaxWidth += MAXWIDTH(fhas.iMaxWidth);
	}

	ASSERT(GetParent() != NULL);

	CRect rectClient;
	GetParent()->GetClientRect(rectClient);
	if(rectClient.Width() <= 0)
		return;

	INT iClientWidth = max(rectClient.Width(), iMinWidth);

	if(iItem >= 0)
	{
		CRect rectItem;
		GetItemRect(iItem, rectItem);

		iClientWidth -= rectItem.right;
	}

	if(iOrder >= 0)
	{
		FHAUTOSIZE& fhas = arrayItems[iOrder];

		iTemp = min(fhas.iWidth, iClientWidth - iMinWidth);
		iTemp = max(fhas.iMinWidth, iTemp);
		iTemp = min(MAXWIDTH(fhas.iMaxWidth), iTemp);
		arrayItems[iOrder].iWidth = iTemp;

		iWidth -= iTemp;
		iClientWidth -= iTemp;
	}
	iWidth = max(1, iWidth);

	INT iResult = 0;

	for(i=iOrder+1; i<iItems; i++)
	{
		FHAUTOSIZE& fhas = arrayItems[i];

		iTemp = max(fhas.iMinWidth, (fhas.iWidth * iClientWidth)/iWidth);
		iTemp = min(MAXWIDTH(fhas.iMaxWidth), iTemp);

		arrayItems[i].iWidth = iTemp;
		iResult += iTemp;
	}

	if(iResult != iClientWidth)
	{
		INT iLast = -1;

		i = iOrder+1;
		while(iResult != iClientWidth && iResult != iLast)
		{
			FHAUTOSIZE& fhas = arrayItems[i];

			iTemp = fhas.iWidth;
			if(iResult < iClientWidth && iTemp < MAXWIDTH(fhas.iMaxWidth))
			{
				iTemp = min(iTemp+1, MAXWIDTH(fhas.iMaxWidth));

				iResult += iTemp - fhas.iWidth;
			}
			else if(iResult > iClientWidth && iTemp > fhas.iMinWidth)
			{
				iTemp = max(iTemp-1, fhas.iMinWidth);

				iResult -= fhas.iWidth - iTemp;
			}

			iLast = iResult;

			arrayItems[i].iWidth = iTemp;
			i = i+1 < iItems ? i+1:iOrder+1;
		}
	}

	m_bAutoSizing = TRUE;

	for(i=iOrder+1; i<iItems; i++)
	{
		hditem.mask = HDI_WIDTH;
		hditem.cxy = arrayItems[i].iWidth;
		VERIFY(SetItem(arrayItems[i].iIndex, &hditem));
	}

	m_bAutoSizing = FALSE;
}

void CFlatHeaderCtrl::DrawCtrl(CDC* pDC)
{
	CRect rectClip;
	if (pDC->GetClipBox(&rectClip) == ERROR)
		return;

	CRect rectClient, rectItem;
	GetClientRect(&rectClient);

    pDC->FillSolidRect(rectClip, m_cr3DFace);

	INT iItems = GetItemCount();
	ASSERT(iItems >= 0);

	CPen penHighLight(PS_SOLID, 1, m_cr3DHighLight);
	CPen penShadow(PS_SOLID, 1, m_cr3DShadow);
	CPen* pPen = pDC->GetCurrentPen();

	CFont* pFont = pDC->SelectObject(GetFont());

	pDC->SetBkColor(m_cr3DFace);
	pDC->SetTextColor(m_crText);

	INT iWidth = 0;

	for(INT i=0;i<iItems;i++)
	{
		INT iItem = OrderToIndex(i);

		TCHAR szText[FLATHEADER_TEXT_MAX];

		HDITEM hditem;
		hditem.mask = HDI_WIDTH|HDI_FORMAT|HDI_TEXT|HDI_IMAGE|HDI_BITMAP;
		hditem.pszText = szText;
		hditem.cchTextMax = sizeof(szText);
		VERIFY(GetItem(iItem, &hditem));

		VERIFY(GetItemRect(iItem, rectItem));

		if (rectItem.right >= rectClip.left || rectItem.left <= rectClip.right)
		{
			if(hditem.fmt&HDF_OWNERDRAW)
			{
				DRAWITEMSTRUCT disItem;
				disItem.CtlType = ODT_BUTTON;
				disItem.CtlID = GetDlgCtrlID();
				disItem.itemID = iItem;
				disItem.itemAction = ODA_DRAWENTIRE;
				disItem.itemState = 0;
				disItem.hwndItem = m_hWnd;
				disItem.hDC = pDC->m_hDC;
				disItem.rcItem = rectItem;
				disItem.itemData = 0;

				DrawItem(&disItem);
			}
			else
			{
				rectItem.DeflateRect(m_iSpacing, 0);
				DrawItem(pDC, rectItem, &hditem, iItem == m_iSortColumn, m_bSortAscending);
				rectItem.InflateRect(m_iSpacing, 0);

				if(m_nClickFlags&MK_LBUTTON && m_iHotIndex == iItem && m_hdhtiHotItem.flags&HHT_ONHEADER)
					pDC->InvertRect(rectItem);
			}

			if(i < iItems-1)
			{
				pDC->SelectObject(&penShadow);
				pDC->MoveTo(rectItem.right-1, rectItem.top+2);
				pDC->LineTo(rectItem.right-1, rectItem.bottom-2);

				pDC->SelectObject(&penHighLight);
				pDC->MoveTo(rectItem.right, rectItem.top+2);
				pDC->LineTo(rectItem.right, rectItem.bottom-2);
			}
		}

		iWidth += hditem.cxy;
	}

	if(iWidth > 0)
	{
		rectClient.right = rectClient.left + iWidth;
		pDC->Draw3dRect(rectClient, m_cr3DHighLight, m_cr3DShadow);
	}

	pDC->SelectObject(pFont);
	pDC->SelectObject(pPen);

	penHighLight.DeleteObject();
	penShadow.DeleteObject();
}

void CFlatHeaderCtrl::DrawItem(LPDRAWITEMSTRUCT)
{
	ASSERT(FALSE);  // must override for self draw header controls
}

void CFlatHeaderCtrl::DrawItem(CDC* pDC, CRect rect, LPHDITEM lphdi, BOOL bSort, BOOL bSortAscending)
{
	ASSERT(lphdi->mask&HDI_FORMAT);

	INT iWidth = 0;

	CBitmap* pBitmap = NULL;
	BITMAP BitmapInfo;
	if(lphdi->fmt&HDF_BITMAP)
	{
		ASSERT(lphdi->mask&HDI_BITMAP);
		ASSERT(lphdi->hbm);

		pBitmap = CBitmap::FromHandle(lphdi->hbm);
		if(pBitmap)
			VERIFY(pBitmap->GetObject(sizeof(BITMAP), &BitmapInfo));
	}

	switch(lphdi->fmt&HDF_JUSTIFYMASK)
	{
	case HDF_LEFT:
		rect.left += (iWidth = DrawImage(pDC, rect, lphdi, FALSE)) ? iWidth+m_iSpacing : 0;
		if(lphdi->fmt&HDF_IMAGE && !iWidth)
			break;
		rect.right -= bSort ? m_iSpacing+m_sizeArrow.cx : 0;
		rect.left += (iWidth = DrawText(pDC, rect, lphdi)) ? iWidth+m_iSpacing : 0;
		if(bSort)
		{
			rect.right += m_iSpacing+m_sizeArrow.cx;
			rect.left += DrawArrow(pDC, rect, bSortAscending, FALSE)+m_iSpacing;
		}
		DrawBitmap(pDC, rect, lphdi, pBitmap, &BitmapInfo, TRUE);
		break;

	case HDF_CENTER:
		rect.left += (iWidth = DrawImage(pDC, rect, lphdi, FALSE)) ? iWidth+m_iSpacing : 0;
		if(lphdi->fmt&HDF_IMAGE && !iWidth)
			break;

		rect.left += bSort ? m_iSpacing+m_sizeArrow.cx : 0;
		rect.right -= (iWidth=DrawBitmap(pDC, rect, lphdi, pBitmap, &BitmapInfo, TRUE)) ? iWidth+m_iSpacing:0;
		if(bSort)
		{
			rect.left -= m_iSpacing+m_sizeArrow.cx;
			rect.right -= DrawArrow(pDC, rect, bSortAscending, TRUE)+2*m_iSpacing;
		}
		DrawText(pDC, rect, lphdi);
		break;

	case HDF_RIGHT:
		if(!(lphdi->fmt&HDF_BITMAP_ON_RIGHT))
			rect.left += (iWidth=DrawBitmap(pDC, rect, lphdi, pBitmap, &BitmapInfo, FALSE)) ? iWidth+m_iSpacing:0;

		rect.left += (iWidth = DrawImage(pDC, rect, lphdi, FALSE)) ? iWidth+m_iSpacing : 0;
		if(lphdi->fmt&HDF_IMAGE && !iWidth)
			break;

		rect.left += bSort && (lphdi->fmt&HDF_BITMAP_ON_RIGHT) ? m_iSpacing+m_sizeArrow.cx : 0;
		if(lphdi->fmt&HDF_BITMAP_ON_RIGHT)
			rect.right -= (iWidth=DrawBitmap(pDC, rect, lphdi, pBitmap, &BitmapInfo, TRUE)) ? iWidth+m_iSpacing:0;
		if(bSort)
		{
			rect.left -= (lphdi->fmt&HDF_BITMAP_ON_RIGHT) ? m_iSpacing+m_sizeArrow.cx:0;
			rect.right -= DrawArrow(pDC, rect, bSortAscending, TRUE)+2*m_iSpacing;
		}
		DrawText(pDC, rect, lphdi);
		break;
	}
}

INT CFlatHeaderCtrl::DrawImage(CDC* pDC, CRect rect, LPHDITEM lphdi, BOOL bRight)
{
	CImageList* pImageList = GetImageList();
	INT iWidth = 0;

	if(lphdi->mask&HDI_IMAGE && lphdi->fmt&HDF_IMAGE)
	{
		ASSERT(pImageList);
		ASSERT(lphdi->iImage>=0 && lphdi->iImage<pImageList->GetImageCount());

		if(rect.Width()>0)
		{
			POINT point;

			point.y = rect.CenterPoint().y - (m_sizeImage.cy>>1);

			if(bRight)
				point.x = rect.right - m_sizeImage.cx;
			else
				point.x = rect.left;

			SIZE size;
			size.cx = rect.Width()<m_sizeImage.cx ? rect.Width():m_sizeImage.cx;
			size.cy = m_sizeImage.cy;
			pImageList->DrawIndirect(pDC, lphdi->iImage, point, size, CPoint(0, 0));

			iWidth = m_sizeImage.cx;
		}
	}

	return iWidth;
}

INT CFlatHeaderCtrl::DrawBitmap(CDC* pDC, CRect rect, LPHDITEM lphdi, CBitmap* pBitmap, BITMAP* pBitmapInfo, BOOL bRight)
{
	INT iWidth = 0;

	if(pBitmap)
	{
		iWidth = pBitmapInfo->bmWidth;
		if(iWidth<=rect.Width() && rect.Width()>0)
		{
			POINT point;

			point.y = rect.CenterPoint().y - (pBitmapInfo->bmHeight>>1);

			if(bRight)
				point.x = rect.right - iWidth;
			else
				point.x = rect.left;

			CDC dc; 
			if(dc.CreateCompatibleDC(pDC) == TRUE) 
			{ 
				CBitmap* pBitmapDC = (CBitmap*)dc.SelectObject(pBitmap);
				ASSERT(pBitmapDC); 

				iWidth = pDC->BitBlt( 
					point.x, point.y, 
					pBitmapInfo->bmWidth, pBitmapInfo->bmHeight, 
					&dc, 0, 0, SRCCOPY
				) ? iWidth:0; 
			
				dc.SelectObject(pBitmapDC);
			} 
			else
				iWidth = 0; 
		}
		else
			iWidth = 0;
	}

	return iWidth;
}

INT CFlatHeaderCtrl::DrawText(CDC* pDC, CRect rect, LPHDITEM lphdi)
{
	CSize size;

	if(rect.Width()>0 && lphdi->mask&HDI_TEXT && lphdi->fmt&HDF_STRING)
	{
		size = pDC->GetTextExtent(lphdi->pszText);

		switch(lphdi->fmt&HDF_JUSTIFYMASK)
		{
		case HDF_LEFT:
		case HDF_LEFT|HDF_RTLREADING:
			pDC->DrawText(lphdi->pszText, -1, rect, DT_LEFT|DT_END_ELLIPSIS|DT_SINGLELINE|DT_VCENTER);
			break;
		case HDF_CENTER:
		case HDF_CENTER|HDF_RTLREADING:
			pDC->DrawText(lphdi->pszText, -1, rect, DT_CENTER|DT_END_ELLIPSIS|DT_SINGLELINE|DT_VCENTER);
			break;
		case HDF_RIGHT:
		case HDF_RIGHT|HDF_RTLREADING:
			pDC->DrawText(lphdi->pszText, -1, rect, DT_RIGHT|DT_END_ELLIPSIS|DT_SINGLELINE|DT_VCENTER);
			break;
		}
	}

	size.cx = rect.Width()>size.cx ? size.cx:rect.Width();
	return size.cx>0 ? size.cx:0;
}

INT CFlatHeaderCtrl::DrawArrow(CDC* pDC, CRect rect, BOOL bSortAscending, BOOL bRight)
{
	INT iWidth = 0;

	if(rect.Width()>0 && m_sizeArrow.cx<=rect.Width())
	{
		iWidth = m_sizeArrow.cx;

		rect.top += (rect.Height() - m_sizeArrow.cy - 1)>>1;
		rect.bottom = rect.top + m_sizeArrow.cy - 1;

		rect.left = bRight ? rect.right-m_sizeArrow.cy:rect.left;

		// Set up pens to use for drawing the triangle
		CPen penLight(PS_SOLID, 1, m_cr3DHighLight);
		CPen penShadow(PS_SOLID, 1, m_cr3DShadow);
		CPen *pPen = pDC->SelectObject(&penLight);

		if(bSortAscending)
		{
			// Draw triangle pointing upwards
			pDC->MoveTo(rect.left + ((m_sizeArrow.cx-1)>>1) + 1,	rect.top);
			pDC->LineTo(rect.left +  (m_sizeArrow.cx-1),			rect.top + m_sizeArrow.cy - 1);
			pDC->LineTo(rect.left,									rect.top + m_sizeArrow.cy - 1);

			pDC->SelectObject(&penShadow);
			pDC->MoveTo(rect.left + ((m_sizeArrow.cx-1)>>1),	rect.top);
			pDC->LineTo(rect.left,								rect.top + m_sizeArrow.cy - 1);
		}
		else	
		{
			// Draw triangle pointing downwards
			pDC->MoveTo(rect.left + ((m_sizeArrow.cx-1)>>1)+1,	rect.top + m_sizeArrow.cy - 1);
			pDC->LineTo(rect.left +  (m_sizeArrow.cx-1),		rect.top);

			pDC->SelectObject(&penShadow);
			pDC->MoveTo(rect.left + ((m_sizeArrow.cx-1)>>1),	rect.top + m_sizeArrow.cy - 1);
			pDC->LineTo(rect.left,								rect.top);
			pDC->LineTo(rect.left + m_sizeArrow.cx,				rect.top);
		}
		
		// Restore the pen
		pDC->SelectObject(pPen);

		penLight.DeleteObject();
		penShadow.DeleteObject();
	}

	return iWidth;
}

INT CFlatHeaderCtrl::OnToolHitTest(CPoint point, TOOLINFO* pTI) const
{
	INT iResult = CHeaderCtrl::OnToolHitTest(point, pTI);
	if(iResult != -1)
		return iResult;

	HDHITTESTINFO hdhti;
	hdhti.pt = point;
	iResult = ::SendMessage(GetSafeHwnd(), HDM_HITTEST, 0, (LPARAM)(&hdhti));
	if(iResult > -1)
	{
		GetItemRect(iResult, &pTI->rect);
		
		pTI->cbSize = sizeof(TOOLINFO);
		pTI->hwnd = GetSafeHwnd();
		pTI->uFlags = TTF_ALWAYSTIP;
		pTI->lpszText = LPSTR_TEXTCALLBACK;
		pTI->uId = FLATHEADER_TT_MAGIC;
	}

	return iResult;
}

/////////////////////////////////////////////////////////////////////////////
// CHeaderCtrl message handlers

LRESULT CFlatHeaderCtrl::OnInsertItem(WPARAM wParam, LPARAM lParam)
{
	HDITEMEX hditemex;
	hditemex.iMinWidth = 0;
	hditemex.iMaxWidth = 0;

	LRESULT lResult = -1;

	WORD wItems = m_arrayHdrItemEx.GetSize();
	wParam = wParam<=wItems ? wParam:wItems;

	try
	{
		m_arrayHdrItemEx.InsertAt(wParam, hditemex);

		lResult = Default();
		if(lResult < 0)	// Cleanup
			m_arrayHdrItemEx.RemoveAt(wParam);
	}
	catch(CMemoryException* e)
	{
		e->Delete();
	}

	return lResult;
}

LRESULT CFlatHeaderCtrl::OnDeleteItem(WPARAM wParam, LPARAM lParam)
{
	ASSERT((INT)wParam < m_arrayHdrItemEx.GetSize());
	m_arrayHdrItemEx.RemoveAt(wParam);

	return Default();
}

LRESULT CFlatHeaderCtrl::OnSetImageList(WPARAM wParam, LPARAM lParam)
{
	CImageList* pImageList;
	pImageList = CImageList::FromHandle((HIMAGELIST)lParam);

	IMAGEINFO info;
	if(pImageList->GetImageInfo(0, &info))
	{
		m_sizeImage.cx = info.rcImage.right - info.rcImage.left;
		m_sizeImage.cy = info.rcImage.bottom - info.rcImage.top;
	}

	return Default();
}

LRESULT CFlatHeaderCtrl::OnSetHotDivider(WPARAM wParam, LPARAM lParam)
{
	if(wParam)
	{
		HDHITTESTINFO hdhti;
		hdhti.pt.x = LOWORD(lParam);
		hdhti.pt.y = HIWORD(lParam);
		ScreenToClient(&hdhti.pt);

		INT iHotIndex = SendMessage(HDM_HITTEST, 0, (LPARAM)(&hdhti));
		if(iHotIndex >= 0)
		{
			HDITEM hditem;
			hditem.mask = HDI_ORDER;
			VERIFY(GetItem(iHotIndex, &hditem));
			m_iHotDivider = hditem.iOrder;

			CRect rectItem;
			VERIFY(GetItemRect(iHotIndex, rectItem));
			if(hdhti.pt.x > rectItem.CenterPoint().x)
				m_iHotDivider++;
		}
		else
			m_iHotDivider = -1;
	}
	else
		m_iHotDivider = (INT)lParam;

	RECT rect;
	GetClientRect(&rect);

	INT iItems = GetItemCount();
	if(m_iHotDivider >= 0 && m_iHotDivider<=iItems+1)
	{
		if(m_pDropWnd == NULL)
		{
			m_pDropWnd = new CFHDropWnd(m_crHotDivider);
			if(m_pDropWnd)
				m_pDropWnd->Create(rect.bottom - rect.top);
		}

		if(m_pDropWnd != NULL)
		{
			POINT pt;
			pt.y = (rect.bottom-rect.top)/2;

			if(m_iHotDivider<iItems)
			{
				GetItemRect(OrderToIndex(m_iHotDivider), &rect);
				pt.x = rect.left - 1;
			}
			else
			{
				GetItemRect(OrderToIndex(iItems-1), &rect);
				pt.x = rect.right;
			}

			ClientToScreen(&pt);
			m_pDropWnd->SetWindowPos(pt.x, pt.y);
		}
	}
	else
	{
		if(m_pDropWnd != NULL)
		{
			m_pDropWnd->DestroyWindow();
			m_pDropWnd = NULL;
		}
	}

	return(LRESULT)m_iHotDivider;
}

LRESULT CFlatHeaderCtrl::OnLayout(WPARAM wParam, LPARAM lParam)
{
	LPHDLAYOUT lphdlayout = (LPHDLAYOUT)lParam;

	if(m_bStaticBorder)
		lphdlayout->prc->right += GetSystemMetrics(SM_CXBORDER)*2;

	LRESULT lResult = CHeaderCtrl::DefWindowProc(HDM_LAYOUT, 0, lParam);

	if(m_iHeight > 0)
		lphdlayout->pwpos->cy = m_iHeight;

	if(m_bAutoSize && !m_bResizing)
		AutoSizeItemsImpl();

	return lResult;
}

BOOL CFlatHeaderCtrl::OnToolTipNotify(UINT nId, NMHDR *pNMHDR, LRESULT *pResult)
{
	TOOLTIPTEXT *pTTT = (TOOLTIPTEXT *)pNMHDR;

	if(
		pNMHDR->idFrom == (UINT)FLATHEADER_TT_MAGIC &&
		!m_arrayHdrItemEx[m_iHotIndex].strToolTip.IsEmpty()
	) {
		USES_CONVERSION;

		wcscpy((WCHAR*)pTTT->lpszText, A2W((LPCTSTR)m_arrayHdrItemEx[m_iHotIndex].strToolTip));
		pTTT->lpszText = pTTT->szText;
		return TRUE;
	}
	else 
		return FALSE;
}

/////////////////////////////////////////////////////////////////////////////
// CFlatHeaderCtrl message handlers

void CFlatHeaderCtrl::OnSysColorChange() 
{
	CHeaderCtrl::OnSysColorChange();
	
	m_cr3DHighLight = ::GetSysColor(COLOR_3DHIGHLIGHT);
	m_cr3DShadow = ::GetSysColor(COLOR_3DSHADOW);
	m_cr3DFace = ::GetSysColor(COLOR_3DFACE);
	m_crText = ::GetSysColor(COLOR_BTNTEXT);
}

LRESULT CFlatHeaderCtrl::OnGetFont(WPARAM wParam, LPARAM lParam)
{
    return (LRESULT)m_font.m_hObject;
}

LRESULT CFlatHeaderCtrl::OnSetFont(WPARAM wParam, LPARAM lParam)
{
	LRESULT lResult = Default();

	CFont *pFont = CFont::FromHandle((HFONT)wParam);
	if(pFont)
	{
		LOGFONT lf;
		pFont->GetLogFont(&lf);

		m_font.DeleteObject();
		m_font.CreateFontIndirect(&lf);
	}

	return lResult;
}

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

void CFlatHeaderCtrl::OnPaint() 
{
    CPaintDC dc(this);

    if (m_bDoubleBuffer)
    {
        CMemDC MemDC(&dc);
        DrawCtrl(&MemDC);
    }
    else
        DrawCtrl(&dc);
}

UINT CFlatHeaderCtrl::OnNcHitTest(CPoint point) 
{
	m_hdhtiHotItem.pt = point;
	ScreenToClient(&m_hdhtiHotItem.pt);

	m_iHotIndex = SendMessage(HDM_HITTEST, 0, (LPARAM)(&m_hdhtiHotItem));
	if(m_iHotIndex >= 0)
	{
		HDITEM hditem;
		hditem.mask = HDI_ORDER;
		VERIFY(GetItem(m_iHotIndex, &hditem));
		m_iHotOrder = hditem.iOrder;

		HDITEMEX hditemex;
		if(GetItemEx(m_iHotIndex, &hditemex))
			m_bHotItemResizable = hditemex.nStyle&HDF_EX_FIXEDWIDTH ? FALSE:TRUE;
	}

	return CHeaderCtrl::OnNcHitTest(point);
}


BOOL CFlatHeaderCtrl::OnSetCursor(CWnd* pWnd, UINT nHitTest, UINT message) 
{
	if(m_iHotIndex>=0 && m_hdhtiHotItem.flags&(HHT_ONDIVIDER|HHT_ONDIVOPEN) && !m_bHotItemResizable)
	{
		SetCursor(AfxGetApp()->LoadStandardCursor(IDC_ARROW));
		return TRUE;
	}

	return CHeaderCtrl::OnSetCursor(pWnd, nHitTest, message);
}

void CFlatHeaderCtrl::OnLButtonDown(UINT nFlags, CPoint point) 
{
	m_nClickFlags = nFlags;
	m_ptClickPoint = point;

	if(m_iHotIndex >= 0)
	{
		m_hdiHotItem.mask = HDI_WIDTH|HDI_FORMAT|HDI_TEXT|HDI_IMAGE|HDI_BITMAP|HDI_ORDER|HDI_LPARAM;
		m_hdiHotItem.pszText = m_szHotItemText;
		m_hdiHotItem.cchTextMax = sizeof(m_szHotItemText);
		VERIFY(GetItem(m_iHotIndex, &m_hdiHotItem));
		VERIFY(GetItemEx(m_iHotIndex, &m_hdieHotItem));

		if(m_hdhtiHotItem.flags&HHT_ONHEADER)
		{
			RECT rectItem;
			VERIFY(GetItemRect(m_iHotIndex, &rectItem));
			InvalidateRect(&rectItem);

			m_bButtonDownOnItem = TRUE;
		}

		if(m_hdhtiHotItem.flags&(HHT_ONDIVIDER|HHT_ONDIVOPEN))
		{
			if(!m_bHotItemResizable)
				return;

			m_bButtonDownOnItem = TRUE;

			HDITEMEX hditemex;
			VERIFY(GetItemEx(m_iHotIndex, &hditemex));

			CRect rectItem;
			GetItemRect(m_iHotIndex, rectItem);

			INT iMinWidth = hditemex.iMinWidth;
			INT iMaxWidth = hditemex.iMaxWidth;

			if(m_bAutoSize == TRUE)
			{
				HDITEM hditem;
				INT iItems = GetItemCount();
				if(m_iHotOrder < iItems-1)
				{
					INT i, iMin = 0, iMax = 0;

					m_iResizingWidth = 0;
					for(i=m_iHotOrder+1; i<iItems; i++)
					{
						INT iIndex = OrderToIndex(i);

						hditem.mask = HDI_WIDTH;
						VERIFY(GetItem(iIndex, &hditem));
						VERIFY(GetItemEx(iIndex, &hditemex));

						m_iResizingWidth += hditem.cxy;
						m_arrayResizing.SetAtGrow(i, hditem.cxy);

						iMin += hditemex.iMinWidth;
						iMax += MAXWIDTH(hditemex.iMaxWidth);
					}

					CRect rectClient;
					GetClientRect(rectClient);

					i = iMax;
					iMax = rectClient.right+1 - iMin;
					iMin = rectClient.right+1 - i;
					
					iMaxWidth = min(MAXWIDTH(iMaxWidth), iMax - rectItem.left);
					iMinWidth = max(iMinWidth, iMin - rectItem.left);
				}
			}

			ClientToScreen(rectItem);

			if(iMinWidth > 0 || iMaxWidth > 0)
			{
				CRect rectClip;
				GetClipCursor(rectClip);

				POINT point;
				GetCursorPos(&point);

				INT iOffset = point.x - rectItem.right;

				rectClip.left = rectItem.left + iMinWidth + iOffset;
				rectClip.right = rectItem.left + MAXWIDTH(iMaxWidth) + iOffset;

				ClipCursor(rectClip);
			}

			m_bResizing = TRUE;
		}
	}

	CHeaderCtrl::OnLButtonDown(nFlags, point);
}


void CFlatHeaderCtrl::OnLButtonDblClk(UINT nFlags, CPoint point) 
{
	if(m_iHotIndex>=0 && m_hdhtiHotItem.flags&(HHT_ONDIVIDER|HHT_ONDIVOPEN) && !m_bHotItemResizable)
		return;

	CHeaderCtrl::OnLButtonDblClk(nFlags, point);
}

void CFlatHeaderCtrl::OnLButtonUp(UINT nFlags, CPoint point) 
{
	m_nClickFlags = nFlags;
	m_ptClickPoint = point;

	m_bButtonDownOnItem = FALSE;

	if(m_iHotIndex >= 0)
	{
		CWnd* pWnd = GetParent();
		ASSERT(pWnd != NULL);

		if(m_hdhtiHotItem.flags&(HHT_ONDIVIDER|HHT_ONDIVOPEN))
		{
			if(m_bResizing)
				ClipCursor(NULL);
		}

		if(m_hdhtiHotItem.flags&HHT_ONHEADER)
		{
			if(m_bDragging)
			{
				NMHEADER nmhdr;
				nmhdr.hdr.hwndFrom = m_hWnd;
				nmhdr.hdr.idFrom = GetDlgCtrlID();
				nmhdr.hdr.code = HDN_ENDDRAG;
				nmhdr.iItem = m_iHotIndex;
				nmhdr.iButton =	0;
				nmhdr.pitem = &m_hdiHotItem;
				if(pWnd->SendMessage(WM_NOTIFY, 0, (LPARAM)&nmhdr)==FALSE && m_iHotDivider>=0)
				{
					INT iCount = GetItemCount();
					ASSERT(m_iHotOrder < iCount);
					ASSERT(m_iHotDivider <= iCount);

					LPINT piArray = new INT[iCount*2];
					if(piArray)
					{
						GetOrderArray((LPINT)piArray, iCount);

						for(INT i=0,j=0;i<iCount;i++)
						{
							if(j == m_iHotOrder)
								j++;

							if(
								(m_iHotOrder<m_iHotDivider && i == m_iHotDivider-1) ||
								(m_iHotOrder>=m_iHotDivider && i == m_iHotDivider)
							)
								piArray[iCount+i] = piArray[m_iHotOrder];
							else
								piArray[iCount+i] = piArray[j++];
						}						

						SetOrderArray(iCount, (LPINT)&piArray[iCount]);
						delete piArray;
					}
					else
						AfxThrowMemoryException();
				}

				if(m_pDragWnd != NULL)
				{
					m_pDragWnd->DestroyWindow();
					m_pDragWnd = NULL;
				}

				if (GetCapture()->GetSafeHwnd() == GetSafeHwnd())
					ReleaseCapture();

				m_bDragging = FALSE;
				OnSetHotDivider(FALSE, -1);

				Invalidate();
			}
			else
			{
				RECT rectItem;
				VERIFY(GetItemRect(m_iHotIndex, &rectItem));
				InvalidateRect(&rectItem);
			}
		}
	}

	CHeaderCtrl::OnLButtonUp(nFlags, point);

	if(m_bResizing && m_bAutoSize)
		AutoSizeItemsImpl(m_iHotIndex);

	m_bResizing = FALSE;
}

void CFlatHeaderCtrl::OnMouseMove(UINT nFlags, CPoint point) 
{
	if(m_bButtonDownOnItem && m_nClickFlags&MK_LBUTTON && m_iHotIndex>=0)
	{
		if(m_bResizing)
			CHeaderCtrl::OnMouseMove(nFlags, point);

		if(m_hdhtiHotItem.flags&HHT_ONHEADER)
		{
			if(m_bDragging)
			{
				if(m_pDragWnd != NULL)
				{
					CRect rect;
					m_pDragWnd->GetWindowRect(&rect);

					CPoint pt = point;
					ClientToScreen(&pt);

					pt.Offset(-(rect.Width()>>1), -(rect.Height()>>1));

					m_pDragWnd->SetWindowPos(
						&wndTop, 
						pt.x, pt.y,
						0, 0, SWP_NOSIZE|SWP_SHOWWINDOW|SWP_NOACTIVATE
					);

					HDHITTESTINFO hdhti;
					hdhti.pt.x = point.x;
					hdhti.pt.y = point.y;

					INT iHotOrder = -1;
					INT iHotIndex = SendMessage(HDM_HITTEST, 0, (LPARAM)(&hdhti));
					if(iHotIndex >= 0)
					{
						HDITEM hditem;
						hditem.mask = HDI_ORDER;
						VERIFY(GetItem(iHotIndex, &hditem));
						iHotOrder = hditem.iOrder;

						CRect rectItem;
						VERIFY(GetItemRect(iHotIndex, rectItem));
						if(hdhti.pt.x > rectItem.CenterPoint().x)
							iHotOrder++;

						SetCursor(AfxGetApp()->LoadStandardCursor(IDC_ARROW));
						m_iDropResult = FHDR_ONHEADER;
					}
					else
					{
						pt = point;
						ClientToScreen(&pt);

						if(m_hdieHotItem.nStyle&HDF_EX_PERSISTENT)
						{
							SetCursor(AfxGetApp()->LoadStandardCursor(IDC_NO));
							m_iDropResult = FHDR_PERSISTENT;
						}
						else
						{
							if(!(m_hDropTarget && m_rcDropTarget.PtInRect(pt)))
							{
								if(m_nDontDropCursor)
									SetCursor(AfxGetApp()->LoadCursor(m_nDontDropCursor));
								else
									SetCursor(AfxGetApp()->LoadStandardCursor(IDC_NO));

								m_iDropResult = FHDR_DROPPED;
							}
							else
							{
								SetCursor(AfxGetApp()->LoadStandardCursor(IDC_ARROW));
								m_iDropResult = FHDR_ONTARGET;
							}
						}
					}

					if(iHotOrder == m_iHotOrder || iHotOrder == m_iHotOrder+1)
						iHotOrder = -1;

					if(iHotOrder != m_iHotDivider)
						OnSetHotDivider(FALSE, iHotOrder);
				}

				return;
			}
			else if(GetStyle()&HDS_DRAGDROP)
			{
				INT iDragCX = GetSystemMetrics(SM_CXDRAG);
				INT iDragCY = GetSystemMetrics(SM_CYDRAG);
				CRect rectDrag(
					m_ptClickPoint.x-iDragCX, m_ptClickPoint.y-iDragCY,
					m_ptClickPoint.x+iDragCX, m_ptClickPoint.y+iDragCY
				);

				if(!rectDrag.PtInRect(point))
				{
					NMHEADER nmhdr;
					nmhdr.hdr.hwndFrom = m_hWnd;
					nmhdr.hdr.idFrom = GetDlgCtrlID();
					nmhdr.hdr.code = HDN_BEGINDRAG;
					nmhdr.iItem = m_iHotIndex;
					nmhdr.iButton =	1;
					nmhdr.pitem = &m_hdiHotItem;

					BOOL bBeginDrag = TRUE;
					CWnd* pWnd = GetParent();
					if(pWnd != NULL)
						bBeginDrag = pWnd->SendMessage(WM_NOTIFY, 0, (LPARAM)&nmhdr)==FALSE ? TRUE:FALSE;

					if(bBeginDrag)
					{
						ASSERT(m_pDragWnd == NULL);
						m_pDragWnd = new CFHDragWnd;
						if(m_pDragWnd)
						{
							CRect rectItem;
							VERIFY(GetItemRect(m_iHotIndex, rectItem));
							ClientToScreen(&rectItem);

							m_pDragWnd->Create(rectItem, this, m_iHotIndex, &m_hdiHotItem);
						}

						BOOL bVisible = FALSE;
						if(m_hDropTarget != NULL)
						{
							bVisible = ::GetWindowLong(m_hDropTarget, GWL_STYLE)&WS_VISIBLE ? TRUE:FALSE;

							HWND hParent = ::GetParent(m_hDropTarget);
							if(hParent)
								bVisible = ::GetWindowLong(hParent, GWL_STYLE)&WS_VISIBLE ? TRUE:FALSE;
						}

						if(m_hDropTarget != NULL && bVisible)
							VERIFY(::GetWindowRect(m_hDropTarget, m_rcDropTarget));
						else
							m_rcDropTarget.SetRectEmpty();
					}
		
					SetCapture();
					m_bDragging = TRUE;
				}
			}
		}
	}
}

void CFlatHeaderCtrl::OnCancelMode() 
{
	CWnd::OnCancelMode();

	if(m_bDragging)
	{
		m_nClickFlags = 0;

		if(m_pDragWnd != NULL)
		{
			m_pDragWnd->DestroyWindow();
			m_pDragWnd = NULL;
		}

		if (GetCapture()->GetSafeHwnd() == GetSafeHwnd())
			ReleaseCapture();

		m_bDragging = FALSE;
		OnSetHotDivider(FALSE, -1);

		Invalidate();
	}
}

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
Web Developer
Netherlands Netherlands
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions