//////////////////////////////////////////////////////////////////////////// // File: CFlatHeaderCtrl.cpp // Version: 1.0.4 // // 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 // //////////////////////////////////////////////////////////////////////////// // FlatHeaderCtrl.cpp : implementation file // #include "stdafx.h" #include "FlatHeaderCtrl.h" #include #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 = 0; m_iHotIndex = -1; m_bHotItemResizable = FALSE; m_bResizing = FALSE; m_iHotDivider = -1; m_crHotDivider = 0x000000FF; m_pDropWnd = NULL; m_bDragging = FALSE; m_pDragWnd = NULL; m_nClickFlags = 0; m_bSortAscending = FALSE; m_iSortColumn = -1; m_arrayHdrItemEx.SetSize(0, 8); m_cr3DHighLight = ::GetSysColor(COLOR_3DHIGHLIGHT); m_cr3DShadow = ::GetSysColor(COLOR_3DSHADOW); m_cr3DFace = ::GetSysColor(COLOR_3DFACE); m_crText = ::GetSysColor(COLOR_BTNTEXT); } CFlatHeaderCtrl::~CFlatHeaderCtrl() { 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 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; default: return FALSE; } Invalidate(); return TRUE; } BOOL CFlatHeaderCtrl::GetItemEx(INT iPos, HDITEMEX* phditemex) const { if(iPos>=m_arrayHdrItemEx.GetSize()) return FALSE; phditemex->nStyle = m_arrayHdrItemEx[iPos].nStyle; phditemex->iMinWidth = m_arrayHdrItemEx[iPos].iMinWidth; phditemex->iMaxWidth = m_arrayHdrItemEx[iPos].iMaxWidth; phditemex->strToolTip = m_arrayHdrItemEx[iPos].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)) && (hditemex.iMinWidth<=hditemex.iMaxWidth)) { if(hditem.cxy < hditemex.iMinWidth) { hditem.cxy = hditemex.iMinWidth; bUpdate = TRUE; } if(hditem.cxy > hditemex.iMaxWidth) { hditem.cxy = hditemex.iMaxWidth; bUpdate = TRUE; } } if(bUpdate) SetItem(iPos, &hditem); m_arrayHdrItemEx.SetAt(iPos, hditemex); 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; } ///////////////////////////////////////////////////////////////////////////// // CFlatHeaderCtrl implementation 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= 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->iImageGetImageCount()); 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()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) { VERIFY(dc.SelectObject(pBitmap)); iWidth = pDC->BitBlt( point.x, point.y, pBitmapInfo->bmWidth, pBitmapInfo->bmHeight, &dc, 0, 0, SRCCOPY ) ? iWidth:0; } else iWidth = 0; */ //replaced 10/24/00 supposed resource leak //see:http://codeguru.earthweb.com/mfc/comments/17458.shtml CDC dc; if(dc.CreateCompatibleDC(pDC) == TRUE) { CBitmap* pOldBit = (CBitmap*) dc.SelectObject(pBitmap); ASSERT(pOldBit); iWidth = pDC->BitBlt( point.x, point.y, pBitmapInfo->bmWidth, pBitmapInfo->bmHeight, &dc, 0, 0, SRCCOPY ) ? iWidth:0; dc.SelectObject(pOldBit); // need to restore the bitmap } 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 = -1; 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_iHotDividerSetWindowPos(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; return CHeaderCtrl::DefWindowProc(HDM_LAYOUT, 0, lParam); } 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); } 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)); if(m_hdhtiHotItem.flags&HHT_ONHEADER) { RECT rectItem; VERIFY(GetItemRect(m_iHotIndex, &rectItem)); InvalidateRect(&rectItem); } if(m_hdhtiHotItem.flags&(HHT_ONDIVIDER|HHT_ONDIVOPEN)) { if(!m_bHotItemResizable) return; HDITEMEX hditemex; VERIFY(GetItemEx(m_iHotIndex, &hditemex)); CRect rectItem; GetItemRect(m_iHotIndex, rectItem); ClientToScreen(rectItem); if(hditemex.iMinWidth <= hditemex.iMaxWidth) { CRect rectClip; GetClipCursor(rectClip); POINT point; GetCursorPos(&point); INT iOffset = point.x - rectItem.right; rectClip.left = rectItem.left + hditemex.iMinWidth + iOffset; rectClip.right = rectItem.left + hditemex.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; if(m_iHotIndex >= 0) { CWnd* pWnd = GetParent(); if(m_hdhtiHotItem.flags&(HHT_ONDIVIDER|HHT_ONDIVOPEN)) { if(m_bResizing) { ClipCursor(NULL); m_bResizing = FALSE; } } 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=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); } void CFlatHeaderCtrl::OnMouseMove(UINT nFlags, CPoint point) { if(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 = 0; } else { pt = point; ClientToScreen(&pt); if(!(m_hDropTarget && m_rcDropTarget.PtInRect(pt))) { if(m_nDontDropCursor) SetCursor(AfxGetApp()->LoadCursor(m_nDontDropCursor)); else SetCursor(AfxGetApp()->LoadStandardCursor(IDC_NO)); m_iDropResult = -1; } else { SetCursor(AfxGetApp()->LoadStandardCursor(IDC_ARROW)); m_iDropResult = 1; } } 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(); } } int CFlatHeaderCtrl::GetHotColumn() { return m_iHotOrder; } int CFlatHeaderCtrl::GetHotIndex() { return m_iHotIndex; }