/*++ Copyright (c) 1994-1999 Microsoft Corporation Module Name : odlbox.cpp Abstract: Owner draw listbox/combobox base classes Author: Ronald Meijer (ronaldm) Project: Internet Services Manager (cluster edition) Revision History: --*/ // // Include Files // #include "stdafx.h" #include "common.h" #ifdef _DEBUG #undef THIS_FILE static char BASED_CODE THIS_FILE[] = __FILE__; #endif #define new DEBUG_NEW #define BMP_LEFT_OFFSET (1) // Space allotted to the left of bitmap #define BMP_RIGHT_OFFSET (3) // Space allotted to the right of bitmap // // Ellipses are shown when column text doesn't fit in the display // const TCHAR g_szEllipses[] = _T("..."); int g_nLenEllipses = (sizeof(g_szEllipses) / sizeof(g_szEllipses[0])) - 1; // // Registry value for columns // const TCHAR g_szRegColumns[] = _T("Columns"); // // Column Value Separator // const TCHAR g_szColValueSep[] = _T(" "); void GetDlgCtlRect( IN HWND hWndParent, IN HWND hWndControl, OUT LPRECT lprcControl ) /*++ Routine Description: Get the control rectangle coordinates relative to its parent. This can then be used in e.g. SetWindowPos() Arguments: HWND hWndParent : Parent window handle HWND hWndControl : Control window handle LPRECT lprcControl : Control rectangle to be filled in Return Value: None --*/ { #define MapWindowRect(hwndFrom, hwndTo, lprc)\ MapWindowPoints((hwndFrom), (hwndTo), (POINT *)(lprc), 2) ::GetWindowRect(hWndControl, lprcControl); ::MapWindowRect(NULL, hWndParent, lprcControl); } void FitPathToControl( IN CWnd & wndControl, IN LPCTSTR lpstrString, IN BOOL bIsFilePath ) /*++ Routine Description: Display the given path in the given control, using ellipses to ensure that the path fits within the control. Arguments: CWnd & wndControl : Control to display on LPCTSTR lpstrString : Path Return Value: None --*/ { CString strDisplay(lpstrString); UINT uLength = strDisplay.GetLength() + 4; // Account for ell. LPTSTR lp = strDisplay.GetBuffer(uLength); if (lp) { CDC * pDC = wndControl.GetDC(); ASSERT_PTR(pDC); if (pDC != NULL) { CRect rc; wndControl.GetClientRect(&rc); if (bIsFilePath) { pDC->DrawText(lp, uLength, &rc, DT_PATH_ELLIPSIS | DT_MODIFYSTRING); } else { pDC->DrawText(lp, uLength, &rc, DT_END_ELLIPSIS | DT_MODIFYSTRING); } wndControl.ReleaseDC(pDC); } strDisplay.ReleaseBuffer(); wndControl.SetWindowText(strDisplay); } } void ActivateControl( IN CWnd & wndControl, IN BOOL fShow ) /*++ Routine Description: Show/hide _AND_ enable/disable control window Arguments: CWnd & wndControl : Window in question BOOL fShow : TRUE to show/enable, FALSE to hide/disable Return Value: None Notes: Merely hiding a window does not disable it. Use this function instead of ShowWindow() to do that. --*/ { wndControl.ShowWindow(fShow ? SW_SHOW : SW_HIDE); wndControl.EnableWindow(fShow); } BOOL VerifyState() /*++ Routine Description: Verify keyboard state Arguments: None Return Value: TRUE if keyboard is in specified state FALSE otherwise. --*/ { SHORT s1, s2; s1 = GetKeyState(VK_SHIFT); s2 = GetKeyState(VK_CONTROL); return (s1 & 0x8000) && (s2 & 0x8000); } BOOL CMappedBitmapButton::LoadMappedBitmaps( IN UINT nIDBitmapResource, IN UINT nIDBitmapResourceSel, IN UINT nIDBitmapResourceFocus, IN UINT nIDBitmapResourceDisabled ) /*++ Routine Description: LoadBitmaps will load one, two, three or all four bitmaps returns TRUE if all specified images are loaded. This will map the buttons to the default colours Arguments: UINT nIDBitmapResource : Standard button UINT nIDBitmapResourceSel : Selected button UINT nIDBitmapResourceFocus : Button with focus UINT nIDBitmapResourceDisabled : Disabled button --*/ { // // delete old bitmaps (if present) // m_bitmap.DeleteObject(); m_bitmapSel.DeleteObject(); m_bitmapFocus.DeleteObject(); m_bitmapDisabled.DeleteObject(); if (!m_bitmap.LoadMappedBitmap(nIDBitmapResource)) { TRACEEOLID("Failed to load bitmap for normal image."); return FALSE; // need this one image } BOOL bAllLoaded = TRUE; if (nIDBitmapResourceSel != 0) { if (!m_bitmapSel.LoadMappedBitmap(nIDBitmapResourceSel)) { TRACEEOLID("Failed to load bitmap for selected image."); bAllLoaded = FALSE; } } if (nIDBitmapResourceFocus != 0) { if (!m_bitmapFocus.LoadMappedBitmap(nIDBitmapResourceFocus)) { bAllLoaded = FALSE; } } if (nIDBitmapResourceDisabled != 0) { if (!m_bitmapDisabled.LoadMappedBitmap(nIDBitmapResourceDisabled)) { bAllLoaded = FALSE; } } return bAllLoaded; } CRMCListBoxResources::CRMCListBoxResources( IN int bmId, IN int nBitmaps, IN COLORREF rgbBackground ) /*++ Routine Description: Constructor Arguments: int bmId : Bitmap resource ID int nBitmaps : Number of bitmaps COLORREF rgbBackground : Background colour to mask out Return Value: N/A --*/ : m_idBitmap(bmId), m_rgbColorTransparent(rgbBackground), m_nBitmaps(nBitmaps), m_nBitmapHeight(0), m_nBitmapWidth(-1), // Set Later m_fInitialized(FALSE) { ASSERT(m_nBitmaps > 0); GetSysColors(); PrepareBitmaps(); } CRMCListBoxResources::~CRMCListBoxResources() /*++ Routine Description: Destructor Arguments: N/A Return Value: N/A --*/ { UnprepareBitmaps(); } void CRMCListBoxResources::UnprepareBitmaps() /*++ Routine Description: Free up bitmap resources Arguments: N/A Return Value: N/A --*/ { ASSERT(m_fInitialized); if (m_fInitialized) { CBitmap * pBmp = (CBitmap *)CGdiObject::FromHandle(m_hOldBitmap); ASSERT_READ_PTR(pBmp); VERIFY(m_dcFinal.SelectObject(pBmp)); VERIFY(m_dcFinal.DeleteDC()); VERIFY(m_bmpScreen.DeleteObject()); m_fInitialized = FALSE; } } void CRMCListBoxResources::PrepareBitmaps() /*++ Routine Description: Prepare 2 rows of bitmaps. One with the selection colour background, and one with the ordinary listbox background. Arguments: None Return Value: None --*/ { ASSERT(m_idBitmap); // // Clean up if we were already initialised // if (m_fInitialized) { UnprepareBitmaps(); } // // create device contexts compatible with screen // CDC dcImage; CDC dcMasks; VERIFY(dcImage.CreateCompatibleDC(0)); VERIFY(dcMasks.CreateCompatibleDC(0)); VERIFY(m_dcFinal.CreateCompatibleDC(0)); CBitmap bitmap; VERIFY(bitmap.LoadBitmap(m_idBitmap)); BITMAP bm; VERIFY(bitmap.GetObject(sizeof(BITMAP), &bm)); // // Each bitmap is assumed to be the same size. // m_nBitmapWidth = bm.bmWidth / m_nBitmaps; ASSERT(m_nBitmapWidth > 0); const int bmWidth = bm.bmWidth; const int bmHeight = bm.bmHeight; m_nBitmapHeight = bmHeight; CBitmap * pOldImageBmp = dcImage.SelectObject(&bitmap); ASSERT_PTR(pOldImageBmp); CBitmap bmpMasks; VERIFY(bmpMasks.CreateBitmap(bmWidth, bmHeight * 2, 1, 1, NULL)); CBitmap * pOldMaskBmp = (CBitmap *)dcMasks.SelectObject(&bmpMasks); ASSERT_PTR(pOldMaskBmp); // // create the foreground and object masks // COLORREF crOldBk = dcImage.SetBkColor(m_rgbColorTransparent); dcMasks.BitBlt(0, 0, bmWidth, bmHeight, &dcImage, 0, 0, SRCCOPY); dcMasks.BitBlt(0, 0, bmWidth, bmHeight, &dcImage, 0, bmHeight, SRCAND); dcImage.SetBkColor(crOldBk); dcMasks.BitBlt(0, bmHeight, bmWidth, bmHeight, &dcMasks, 0, 0, NOTSRCCOPY); // // create DC to hold final image // VERIFY(m_bmpScreen.CreateCompatibleBitmap(&dcImage, bmWidth, bmHeight * 2)); CBitmap * pOldBmp = (CBitmap*)m_dcFinal.SelectObject(&m_bmpScreen); ASSERT_PTR(pOldBmp); m_hOldBitmap = pOldBmp->m_hObject; CBrush b1, b2; VERIFY(b1.CreateSolidBrush(m_rgbColorHighlight)); VERIFY(b2.CreateSolidBrush(m_rgbColorWindow)); m_dcFinal.FillRect(CRect(0, 0, bmWidth, bmHeight), &b1); m_dcFinal.FillRect(CRect(0, bmHeight, bmWidth, bmHeight * 2), &b2); // // mask out the object pixels in the destination // m_dcFinal.BitBlt(0, 0, bmWidth, bmHeight, &dcMasks, 0, 0, SRCAND); // // mask out the background pixels in the image // dcImage.BitBlt(0, 0, bmWidth, bmHeight, &dcMasks, 0, bmHeight, SRCAND); // // XOR the revised image into the destination // m_dcFinal.BitBlt(0, 0, bmWidth, bmHeight, &dcImage, 0, 0, SRCPAINT); // // mask out the object pixels in the destination // m_dcFinal.BitBlt(0, bmHeight, bmWidth, bmHeight, &dcMasks, 0, 0, SRCAND); // // XOR the revised image into the destination // m_dcFinal.BitBlt(0, bmHeight, bmWidth, bmHeight, &dcImage, 0, 0, SRCPAINT); VERIFY(dcMasks.SelectObject(pOldMaskBmp)); VERIFY(dcImage.SelectObject(pOldImageBmp)); // // The result of all of this mucking about is a bitmap identical with the // one loaded from the resources but with the lower row of bitmaps having // their background changed from transparent1 to the window background // and the upper row having their background changed from transparent2 to // the highlight colour. A derived CRMCListBox can BitBlt the relevant part // of the image into an item's device context for a transparent bitmap // effect which does not take any extra time over a normal BitBlt. // m_fInitialized = TRUE; } void CRMCListBoxResources::SysColorChanged() /*++ Routine Description: Respond to change in system colours by rebuilding the resources Arguments: None Return Value: None --*/ { // // Reinitialise bitmaps and syscolors. This should be called from // the parent of the CRMCListBoxResources object from // the OnSysColorChange() function. // GetSysColors(); PrepareBitmaps(); } void CRMCListBoxResources::GetSysColors() /*++ Routine Description: Get sytem colours Arguments: None Return Value: None --*/ { m_rgbColorWindow = ::GetSysColor(COLOR_WINDOW); m_rgbColorHighlight = ::GetSysColor(COLOR_HIGHLIGHT); m_rgbColorWindowText = ::GetSysColor(COLOR_WINDOWTEXT); m_rgbColorHighlightText = ::GetSysColor(COLOR_HIGHLIGHTTEXT); } CRMCListBoxDrawStruct::CRMCListBoxDrawStruct( IN CDC * pDC, IN RECT * pRect, IN BOOL sel, IN DWORD_PTR item, IN int itemIndex, IN const CRMCListBoxResources * pres ) /*++ Routine Description: Constructor Arguments: CDC * pdc : Device context RECT * pRect : Rectange to paint into BOOL sel : TRUE if selected DWORD item : item int itemIndex : item index const CRMCListBoxResources * pres : Pointer to resources Return Value: N/A --*/ : m_pDC(pDC), m_Sel(sel), m_ItemData(item), m_ItemIndex(itemIndex), m_pResources(pres) { m_Rect.CopyRect(pRect); } CODLBox::CODLBox() /*++ Routine Description: Constructor for CODLBox -- abstract base class for both CRMCComboBox, and CRMCListBox Arguments: None Return Value: N/A --*/ : m_lfHeight(0), m_pResources(NULL), m_auTabs(), m_pWnd(NULL) { } CODLBox::~CODLBox() /*++ Routine Description: Destructor Arguments: N/A Return Value: N/A --*/ { } /* virtual */ BOOL CODLBox::Initialize() /*++ Routine Description: Listbox/combobox is being created Arguments: None Return Value: TRUE for success, FALSE for failure --*/ { // // Derived control must be attached at this point // ASSERT_PTR(m_pWnd); // // GetFont returns non NULL when the control is in a dialog box // CFont * pFont = m_pWnd->GetFont(); if(pFont == NULL) { LOGFONT lf; CFont fontTmp; ::GetObject(::GetStockObject(SYSTEM_FONT), sizeof(LOGFONT), &lf); fontTmp.CreateFontIndirect(&lf); CalculateTextHeight(&fontTmp); } else { CalculateTextHeight(pFont); } return TRUE; } BOOL CODLBox::ChangeFont( IN CFont * pFont ) /*++ Routine Description: Change the control font the specified font Arguments: CFont * pFont : Pointer to the new font to be used Return Value: TRUE for success, FALSE for failure --*/ { ASSERT_PTR(m_pResources); ASSERT_PTR(m_pWnd); if( pFont == NULL || m_pResources == NULL || m_pWnd == NULL || m_pWnd->m_hWnd == NULL ) { TRACEEOLID("Invalid state of the control. Can't change font"); return FALSE; } // // Don't reflect changes immediately // m_pWnd->SetRedraw(FALSE); m_pWnd->SetFont(pFont, TRUE); CalculateTextHeight(pFont); int nItems = __GetCount(); int bmHeight = m_pResources->BitmapHeight(); int nHeight = bmHeight > m_lfHeight ? bmHeight : m_lfHeight; for(int i = 0; i < nItems; ++i) { __SetItemHeight(i, nHeight); } // // Now reflect the change visually // m_pWnd->SetRedraw(TRUE); m_pWnd->Invalidate(); return TRUE; } void CODLBox::AttachResources( IN const CRMCListBoxResources * pRes ) /*++ Routine Description: Attach the bitmaps Arguments: const CRMCListBoxResources * pRes : pointer to resources to be attached Return Value: None --*/ { if(pRes != m_pResources) { ASSERT_READ_PTR(pRes); m_pResources = pRes; if(m_pWnd != NULL && m_pWnd->m_hWnd != NULL) { // // if window was created already, redraw everything. // m_pWnd->Invalidate(); } } } /* static */ int CODLBox::GetRequiredWidth( IN CDC * pDC, IN const CRect & rc, IN LPCTSTR lpstr, IN int nLength ) /*++ Routine Description: Determine required display width of the string Arguments: CDC * pDC : Pointer to device context to use const CRect & rc : Starting rectangle LPCTSTR lpstr : String whose width is to be displayed int nLength : Length (in characters of the string Return Value: The display width that the string would need to be displayed on the given device context --*/ { #ifdef _DEBUG pDC->AssertValid(); #endif // _DEBUG CRect rcTmp(rc); pDC->DrawText( lpstr, nLength, &rcTmp, DT_CALCRECT | DT_LEFT | DT_SINGLELINE | DT_NOPREFIX | DT_VCENTER ); return rcTmp.Width(); } /* static */ BOOL CODLBox::ColumnText( IN CDC * pDC, IN int nLeft, IN int nTop, IN int nRight, IN int nBottom, IN LPCTSTR lpstr ) /*++ Routine Description: Display text limited by a rectangle. Use ellipses if the text is too wide to fit inside the given dimensions. Arguments: CDC * pDC : Pointer to display context to use int nLeft : Left coordinate int nTop : Top coordinate int nRight : Right coordinate int nBottom : Bottom coordinate LPCTSTR lpstr : String to be displayed Return Value: TRUE for success, FALSE for failure --*/ { BOOL fSuccess = TRUE; #ifdef _DEBUG pDC->AssertValid(); #endif // _DEBUG CString str; CRect rc(nLeft, nTop, nRight, nBottom); int nAvailWidth = rc.Width(); int nLength = ::lstrlen(lpstr); try { if (GetRequiredWidth(pDC, rc, lpstr, nLength) <= nAvailWidth) { // // Sufficient space, display as is. // str = lpstr; } else { // // Build a string with ellipses until it // fits // LPTSTR lpTmp = str.GetBuffer(nLength + g_nLenEllipses); while (nLength) { ::lstrcpyn(lpTmp, lpstr, nLength); ::lstrcpy(lpTmp + nLength - 1, g_szEllipses); if (GetRequiredWidth(pDC, rc, lpTmp, nLength + g_nLenEllipses) <= nAvailWidth) { break; } --nLength; } str = lpTmp; } pDC->DrawText( str, &rc, DT_LEFT | DT_SINGLELINE | DT_NOPREFIX | DT_VCENTER ); } catch(CMemoryException * e) { // // Mem failure // fSuccess = FALSE; e->ReportError(); e->Delete(); } return fSuccess; } void CODLBox::ComputeMargins( IN CRMCListBoxDrawStruct & ds, IN int nCol, OUT int & nLeft, OUT int & nRight ) /*++ Routine Description: Compute the left and right margins of the given column. Arguments: CRMCListBoxDrawStruct & ds : Drawing structure int nCol : Column whose margins we're interested in int & nLeft : Left column int & nRight : Right column Return Value: None --*/ { nLeft = ds.m_Rect.left; nRight = ds.m_Rect.right; // // Find tab value associated with column index (0-based), // and adjust left and right // ASSERT(nCol <= NumTabs()); if (nCol > 0) { if (nCol <= NumTabs()) { nLeft += m_auTabs[nCol-1]; } } if (nCol < NumTabs()) { nRight = m_auTabs[nCol]; } } BOOL CODLBox::DrawBitmap( IN CRMCListBoxDrawStruct & ds, IN int nCol, IN int nID ) /*++ Routine Description: Draw a bitmap in the given column. Bitmap are always placed on the leftmost side of the column if there is sufficient space. Arguments: CRMCListBoxDrawStruct & ds : Drawing structure int nCol : Column to place bitmap in int nID : Bitmap ID (offset within the bitmap resources) Return Value: None --*/ { CDC * pBmpDC = (CDC *)&ds.m_pResources->dcBitMap(); #ifdef _DEBUG pBmpDC->AssertValid(); #endif // _DEBUG // // Select the bitmap with either a selection or // a regular background // int bm_h = ds.m_Sel ? 0 : ds.m_pResources->BitmapHeight(); int bm_w = ds.m_pResources->BitmapWidth() * nID; int nLeft, nRight; ComputeMargins(ds, nCol, nLeft, nRight); nLeft += BMP_LEFT_OFFSET; // // Check to make sure there's enough room before // drawing the bitmap. // if (nRight - nLeft >= ds.m_pResources->BitmapWidth()) { ds.m_pDC->BitBlt( nLeft, ds.m_Rect.top, ds.m_pResources->BitmapWidth(), ds.m_pResources->BitmapHeight(), pBmpDC, bm_w, bm_h, SRCCOPY ); } return TRUE; } BOOL CODLBox::ColumnText( IN CRMCListBoxDrawStruct & ds, IN int nCol, IN BOOL fSkipBitmap, IN LPCTSTR lpstr ) /*++ Routine Description: Draw column text. Arguments: CRMCListBoxDrawStruct & ds : Drawing structure int nCol : Column to place bitmap in BOOL fSkipBitmap : If TRUE, increment lefthand column by the width of a bitmap LPCTSTR lpstr : String to be displayed. May be truncated as necessary Return Value: TRUE for success, FALSE for failure --*/ { int nLeft, nRight; ComputeMargins(ds, nCol, nLeft, nRight); // // Optionally adjust for bitmap // if (fSkipBitmap) { nLeft += (ds.m_pResources->BitmapWidth() + BMP_RIGHT_OFFSET); } return CODLBox::ColumnText( ds.m_pDC, nLeft, ds.m_Rect.top, nRight, ds.m_Rect.bottom, lpstr ); } void CODLBox::CalculateTextHeight( IN CFont * pFont ) /*++ Routine Description: Calculate and set the text height of font Arguments: CFont * pFont : Pointer to the font to be used. Return Value: None --*/ { ASSERT_PTR(m_pWnd); CClientDC dc(m_pWnd); CFont * pOldFont = dc.SelectObject(pFont); TEXTMETRIC tm; dc.GetTextMetrics(&tm); m_lfHeight = tm.tmHeight; dc.SelectObject(pOldFont); } int CODLBox::AddTab( IN UINT uTab ) /*++ Routine Description: Add a tab to the end of the list (e.g the right side of the header) Arguments: UINT uTab : Tab value to set Return Value: The index of the new tab --*/ { return (int)m_auTabs.Add(uTab); } int CODLBox::AddTabFromHeaders( IN CWnd & wndLeft, IN CWnd & wndRight ) /*++ Routine Description: Add a tab to the end of the list (e.g the right side of the header), but compute the tab by taking the difference in left-hand coordinat of two window controls (usually static header text) Arguments: CWnd & wndLeft : Left window CWnd & wndRight : Right window Return Value: The index of the new tab --*/ { CRect rcLeft, rcRight; wndLeft.GetWindowRect(&rcLeft); wndRight.GetWindowRect(&rcRight); ASSERT(rcRight.left > rcLeft.left); return AddTab(rcRight.left - rcLeft.left - 1); } int CODLBox::AddTabFromHeaders( IN UINT idLeft, IN UINT idRight ) /*++ Routine Description: Similar to the function above, but use the control IDs. The parent window is assumed to be the same as the parent window of the listbox Arguments: UINT idLeft : ID of the left control UINT idRight : ID of the right control Return Value: The index of the new tab or -1 in case of failure --*/ { ASSERT_PTR(m_pWnd); if (m_pWnd == NULL) { // // Should have associated window handle by now // return -1; } CWnd * pLeft = m_pWnd->GetParent()->GetDlgItem(idLeft); CWnd * pRight = m_pWnd->GetParent()->GetDlgItem(idRight); ASSERT_READ_PTR(pLeft); ASSERT_READ_PTR(pRight); if (!pLeft || !pRight) { // // One or both control IDs were not valid // return -1; } return AddTabFromHeaders(*pLeft, *pRight); } void CODLBox::InsertTab( IN int nIndex, IN UINT uTab ) /*++ Routine Description: Insert a tab at the given index Arguments: int nIndex : Column index at which the tab is to be inserted UINT uTab : Tab value to set Return Value: None --*/ { m_auTabs.InsertAt(nIndex, uTab); } void CODLBox::RemoveTab( IN int nIndex, IN int nCount ) /*++ Routine Description: Remove one or more tabs Arguments: int nIndex : Column index at which to start removing tabs int nCount : Number of tabs to be removed Return Value: None --*/ { m_auTabs.RemoveAt(nIndex, nCount); } void CODLBox::RemoveAllTabs() /*++ Routine Description: Remove all tabs Arguments: None Return Value: None --*/ { m_auTabs.RemoveAll(); } void CODLBox::__DrawItem( IN LPDRAWITEMSTRUCT lpDIS ) /*++ Routine Description: Draw an item. This will draw the focus and selection state, and then call out to the derived class to draw the item. Arguments: LPDRAWITEMSTRUCT lpDIS : The drawitem structure Return Value: None --*/ { // // Need to attach resources before creation/adding items // ASSERT_PTR(m_pResources); CDC * pDC = CDC::FromHandle(lpDIS->hDC); #ifdef _DEBUG pDC->AssertValid(); #endif // _DEBUG // // Draw focus rectangle when no items in listbox // if(lpDIS->itemID == (UINT)-1) { if(lpDIS->itemAction & ODA_FOCUS) { // // rcItem.bottom seems to be 0 for variable height list boxes // lpDIS->rcItem.bottom = m_lfHeight; pDC->DrawFocusRect(&lpDIS->rcItem); } return; } else { BOOL fSelChange = (lpDIS->itemAction & ODA_SELECT) != 0; BOOL fFocusChange = (lpDIS->itemAction & ODA_FOCUS) != 0; BOOL fDrawEntire = (lpDIS->itemAction & ODA_DRAWENTIRE) != 0; if(fSelChange || fDrawEntire) { BOOL fSel = (lpDIS->itemState & ODS_SELECTED) != 0; COLORREF hlite = (fSel ? (m_pResources->ColorHighlight()) : (m_pResources->ColorWindow())); COLORREF textcol = (fSel ? (m_pResources->ColorHighlightText()) : (m_pResources->ColorWindowText())); pDC->SetBkColor(hlite); pDC->SetTextColor(textcol); // // fill the rectangle with the background colour. // pDC->ExtTextOut(0, 0, ETO_OPAQUE, &lpDIS->rcItem, NULL, 0, NULL); CRMCListBoxDrawStruct ds(pDC, (RECT *)&lpDIS->rcItem, fSel, (DWORD_PTR)lpDIS->itemData, lpDIS->itemID, m_pResources ); // // Now call the draw function of the derived class // DrawItemEx(ds); } if (fFocusChange || (fDrawEntire && (lpDIS->itemState & ODS_FOCUS))) { pDC->DrawFocusRect(&lpDIS->rcItem); } } } void CODLBox::__MeasureItem( IN OUT LPMEASUREITEMSTRUCT lpMIS ) /*++ Routine Description: Provide dimensions of given item Arguments: LPMEASUREITEMSTRUCT lpMIS : Measure item structure Return Value: None --*/ { ASSERT_PTR(m_pResources); // int h = lpMIS->itemHeight; int ch = TextHeight(); int bmHeight = m_pResources->BitmapHeight(); lpMIS->itemHeight = ch < bmHeight ? bmHeight : ch; } CRMCListBoxHeader::CRMCListBoxHeader( IN DWORD dwStyle ) /*++ Routine Description: Constructor. Arguments: DWORD dwStyle : Style bits Return Value: N/A --*/ : m_pHCtrl(NULL), m_pListBox(NULL), m_dwStyle(dwStyle), m_fRespondToColumnWidthChanges(TRUE) { m_pHCtrl = new CHeaderCtrl; } CRMCListBoxHeader::~CRMCListBoxHeader() /*++ Routine Description: Destructor. Arguments: N/A Return Value: N/A --*/ { // // Kill the header control and the // font // if (m_pHCtrl) { delete m_pHCtrl; } // // Leave the listbox pointer alone, as we don't // own it, but are merely associated with it. // } // // Message Map // BEGIN_MESSAGE_MAP(CRMCListBoxHeader, CStatic) //{{AFX_MSG_MAP(CRMCListBoxHeader) ON_WM_DESTROY() ON_WM_SETFOCUS() //}}AFX_MSG_MAP ON_NOTIFY_RANGE(HDN_ENDTRACK, 0, 0xFFFF, OnHeaderEndTrack) ON_NOTIFY_RANGE(HDN_ITEMCHANGED, 0, 0xFFFF, OnHeaderItemChanged) ON_NOTIFY_RANGE(HDN_ITEMCLICK, 0, 0xFFFF, OnHeaderItemClick) END_MESSAGE_MAP() void CRMCListBoxHeader::OnSetFocus(CWnd * pWnd) { m_pListBox->SetFocus(); } void CRMCListBoxHeader::OnDestroy() /*++ Routine Description: WM_DESTROY message handler. When the control is being destroyed, also destroy the invisible static control. Arguments: None Return Value: None --*/ { // // Destroy optional header control // if (m_pHCtrl) { m_pHCtrl->DestroyWindow(); } CStatic::OnDestroy(); } BOOL CRMCListBoxHeader::Create( IN DWORD dwStyle, IN const RECT & rect, IN CWnd * pParentWnd, IN CHeaderListBox * pListBox, IN UINT nID ) /*++ Routine Description: Create the control. This will first create an invisible static window, which is to take up the entire area of the listbox. This static window then will be the parent to the listbox as well as this header control. Arguments: DWORD dwStyle : Creation style bits const RECT & rect : Rectangle in which the header is to be created CWnd * pParentWnd : Parent window CHeaderListBox * pListBox : Associated listbox UINT nID : Control ID of the header Return Value: TRUE for success, FALSE for failure --*/ { // // Make sure the real header control exists by now // if (m_pHCtrl == NULL) { return FALSE; } // // Make sure there's an associated listbox // m_pListBox = pListBox; if (m_pListBox == NULL) { return FALSE; } // // Create the controlling static window as do-nothing window // if (!CStatic::Create(NULL, WS_VISIBLE | WS_TABSTOP | SS_BITMAP | WS_CHILD, rect, pParentWnd, 0xFFFF)) { return FALSE; } // // Now create the header control. Its parent // window is this static control we just created // CRect rc(0, 0, 0 ,0); dwStyle |= (UseButtons() ? HDS_BUTTONS : 0L); VERIFY(m_pHCtrl->Create(dwStyle, rc, this, nID)); m_pHCtrl->ModifyStyle(HDS_DRAGDROP, 0); // // Place header control as per style bits, // compute the desired layout, and move it // HD_LAYOUT hdl; WINDOWPOS wp; GetClientRect(&rc); hdl.prc = &rc; hdl.pwpos = ℘ m_pHCtrl->Layout(&hdl); m_pHCtrl->SetWindowPos(m_pListBox, wp.x, wp.y, wp.cx, wp.cy, wp.flags | SWP_SHOWWINDOW); // // And move our associated listbox just below it // ::GetDlgCtlRect(GetParent()->m_hWnd, m_pListBox->m_hWnd, &rc); rc.top += wp.cy - 1; // // Adjust if header is bigger than the entire listbox // if (rc.top > rc.bottom) { rc.top = rc.bottom; } // Fix for theme support. Make listbox and header children of the same static control m_pListBox->SetParent(this); GetParent()->ClientToScreen(&rc); ScreenToClient(&rc); m_pListBox->MoveWindow(rc.left, rc.top, rc.Width(), rc.Height()); // // Make sure the header uses the right font // m_pHCtrl->SetFont( CFont::FromHandle((HFONT)::GetStockObject(DEFAULT_GUI_FONT)), FALSE ); return TRUE; } void CRMCListBoxHeader::OnHeaderEndTrack( IN UINT nId, IN NMHDR * pnmh, OUT LRESULT * pResult ) /*++ Routine Description: User has finished dragging the column divider. If we're supposed to ensure that the last column is a stretch column, turn off the redraw now -- it will get turned back on after the column width changes have all been completed. This will reduce the flash effect. Arguments: UINT nId : Control ID NMHDR * pnmh : Notification header structure LRESULT * pResult : Result. Will be set to 0 if the message was handled Return Value: None (handled in pResult) --*/ { pnmh; nId; if (DoesRespondToColumnWidthChanges() && UseStretch()) { // // This will get turned back on in OnHeaderItemChanged // SetRedraw(FALSE); } *pResult = 0; } void CRMCListBoxHeader::SetColumnWidth( IN int nCol, IN int nWidth ) /*++ Routine Description: Set the given column to the given width Arguments: int nCol : Column number int nWidth : New width Return Value: None --*/ { ASSERT(nCol < QueryNumColumns()); if (nCol >= QueryNumColumns()) { return; } TRACEEOLID("Setting width of column " << nCol << " to " << nWidth); HD_ITEM hdItem; hdItem.mask = HDI_WIDTH; hdItem.cxy = nWidth; VERIFY(SetItem(nCol, &hdItem)); } void CRMCListBoxHeader::OnHeaderItemChanged( IN UINT nId, IN NMHDR *pnmh, OUT LRESULT *pResult ) /*++ Routine Description: Handle change in header column width. Note: we're actually tracking the HDN_ITEMCHANGED notification, not the HDN_ENDDRAG one, because the latter is sent out before the column widths in the structure have changed. Arguments: UINT nId : Control ID NMHDR * pnmh : Notification header structure LRESULT * pResult : Result. Will be set to 0 if the message was handled Return Value: None (handled in pResult) --*/ { nId; // // Adjust tabs in associate listbox if // column widths have changed // HD_NOTIFY * pNotify = (HD_NOTIFY *)pnmh; if (DoesRespondToColumnWidthChanges() && pNotify->pitem->mask & HDI_WIDTH) { ASSERT_PTR(m_pListBox); // // Stretch the last column // if (UseStretch()) { // // Turn this off, as we don't want // to get in an infinite loop // RespondToColumnWidthChanges(FALSE); // // Compute available space // CRect rc; GetClientRect(&rc); // // See how much is taken up by preceding // columns // int nTotalWidth = 0; int cColumns = QueryNumColumns(); int nLastCol = cColumns - 1; ASSERT(nLastCol >= 0); for (int nCol = 0; nCol < nLastCol; ++nCol) { int nWidth = GetColumnWidth(nCol); // // Each column must be at least one pixel wide // int nMaxWidth = rc.Width() - nTotalWidth - (nLastCol - nCol); if (nWidth > nMaxWidth) { nWidth = nMaxWidth; SetColumnWidth(nCol, nWidth); } nTotalWidth += nWidth; } // // Make sure the last column takes up the rest // if (rc.Width() > nTotalWidth) { SetColumnWidth(nLastCol, rc.Width() - nTotalWidth); } // // Turn this back on again // RespondToColumnWidthChanges(TRUE); // // Redraw will have been turned off in // OnHeaderEndTrack, now that all column // movement has completed, turn it back // on to draw the control in its current // state. // SetRedraw(TRUE); Invalidate(); } // // Recompute tabs on associate listbox, // and force redraw on it. // m_pListBox->SetRedraw(FALSE); SetTabsFromHeader(); m_pListBox->SetRedraw(TRUE); m_pListBox->Invalidate(); } *pResult = 0; } void CRMCListBoxHeader::OnHeaderItemClick( IN UINT nId, IN NMHDR *pnmh, OUT LRESULT *pResult ) /*++ Routine Description: A button has been clicked in the header control. Pass it on to the real parent window. Arguments: UINT nId : Control ID NMHDR * pnmh : Notification header structure LRESULT * pResult : Result. Will be set to 0 if the message was handled Return Value: None (handled in pResult) --*/ { // // Pass notification on to parent // ASSERT(GetParent()); GetParent()->SendMessage(WM_NOTIFY, (WPARAM)nId, (LPARAM)pnmh); *pResult = 0; } void CRMCListBoxHeader::SetTabsFromHeader() /*++ Routine Description: Set the tabs (which are cumulative) from the header control columns (which are not) Arguments: None Return Value: None --*/ { // // Must have the same number of tabs // as header columns // ASSERT_PTR(m_pListBox); ASSERT(GetItemCount() == m_pListBox->NumTabs()); int nTab = 0; for (int n = 0; n < m_pListBox->NumTabs(); ++n) { m_pListBox->SetTab(n, nTab += GetColumnWidth(n)); } } int CRMCListBoxHeader::GetItemCount() const /*++ Routine Description: Get the number of items in the header Arguments: None Return Value: The number of items in the header (e.g. the number of columns) --*/ { ASSERT_PTR(m_pHCtrl); return m_pHCtrl->GetItemCount(); } BOOL CRMCListBoxHeader::GetItem( IN int nPos, OUT HD_ITEM * pHeaderItem ) const /*++ Routine Description: Get information on specific position (column index) Arguments: int nPos : Column index HD_ITEM * pHeaderItem : Header item information Return Value: TRUE for success, FALSE for failure (bad column index) --*/ { ASSERT_PTR(m_pHCtrl); return m_pHCtrl->GetItem(nPos, pHeaderItem); } int CRMCListBoxHeader::GetColumnWidth( IN int nPos ) const /*++ Routine Description: Get column width of a specific column Arguments: int nPos : Column index Return Value: The column width of the given colum, or -1 in case of failure (bad column index) --*/ { HD_ITEM hi; hi.mask = HDI_WIDTH; if (GetItem(nPos, &hi)) { return hi.cxy; } return -1; } BOOL CRMCListBoxHeader::SetItem( IN int nPos, IN HD_ITEM * pHeaderItem ) /*++*** Routine Description: Set information on specific position (column index) Arguments: int nPos : Column index HD_ITEM * pHeaderItem : Header item information Return Value: TRUE for success, FALSE for failure (bad column index) --*/ { ASSERT_PTR(m_pHCtrl); ASSERT_PTR(m_pListBox); if (!m_pHCtrl->SetItem(nPos, pHeaderItem)) { return FALSE; } if (pHeaderItem->mask & HDI_WIDTH) { SetTabsFromHeader(); } return TRUE; } int CRMCListBoxHeader::InsertItem( IN int nPos, IN HD_ITEM * pHeaderItem ) /*++ Routine Description: insert information in specific position (column index) Arguments: int nPos : Column index HD_ITEM * pHeaderItem : Header item information Return Value: The new index, or -1 in case of failure. --*/ { ASSERT_PTR(m_pHCtrl); ASSERT_PTR(m_pListBox); int nCol = m_pHCtrl->InsertItem(nPos, pHeaderItem); if (nCol != -1) { // // Set 0-width tab, as tabs get recomputed anyway // m_pListBox->InsertTab(nPos, 0); SetTabsFromHeader(); } return nCol; } BOOL CRMCListBoxHeader::DeleteItem( IN int nPos ) /*++ Routine Description: Delete the given item (i.e. column) Arguments: int nPos : Column index Return Value: TRUE for success, FALSE for failure (bad column index) --*/ { ASSERT_PTR(m_pHCtrl); ASSERT_PTR(m_pListBox); if (!m_pHCtrl->DeleteItem(nPos)) { return FALSE; } m_pListBox->RemoveTab(nPos, 1); return TRUE; } IMPLEMENT_DYNAMIC(CRMCListBoxHeader, CStatic); CRMCListBox::CRMCListBox() /*++ Routine Description: Constructor Arguments: None Return Value: N/A --*/ : m_fInitialized(FALSE), m_fMultiSelect(FALSE) { } CRMCListBox::~CRMCListBox() /*++ Routine Description: Destructor Arguments: N/A Return Value: N/A --*/ { } // // Message Map // BEGIN_MESSAGE_MAP(CRMCListBox, CListBox) //{{AFX_MSG_MAP(CRMCListBox) ON_WM_CREATE() ON_WM_DESTROY() //}}AFX_MSG_MAP END_MESSAGE_MAP() /* virtual */ BOOL CRMCListBox::Initialize() /*++ Routine Description: This function should be called directly when subclassing an existing listbox, otherwise OnCreate will take care of it. Arguments: None Return Value: TRUE for success, FALSE for failure --*/ { // // Make sure we're only initialized once // if (m_fInitialized) { return TRUE; } // // Ensure the base class knows our window // handle // AttachWindow(this); if (!CODLBox::Initialize()) { return FALSE; } m_fInitialized = TRUE; DWORD dwStyle = GetStyle(); m_fMultiSelect = (dwStyle & (LBS_EXTENDEDSEL | LBS_MULTIPLESEL)) != 0; return m_fInitialized; } void CRMCListBox::MeasureItem( IN LPMEASUREITEMSTRUCT lpMIS ) /*++ Routine Description: CListBox override to ODL base class Arguments: LPMEASUREITEMSTRUCT lpMIS : Measure item structure Return Value: None --*/ { CODLBox::__MeasureItem(lpMIS); } void CRMCListBox::DrawItem( IN LPDRAWITEMSTRUCT lpDIS ) /*++ Routine Description: CListBox override to ODL base class Arguments: LPDRAWITEMSTRUCT lpDIS : Drawing item structure Return Value: None --*/ { CODLBox::__DrawItem(lpDIS); } /* virtual */ void CRMCListBox::DrawItemEx( IN CRMCListBoxDrawStruct & dw ) /*++ Routine Description: Do-nothing extended draw function, which should be provided by the derived class. This one will ASSERT, and should never be called. Arguments: CRMCListBoxDrawStruct & dw : Draw Structure Return Value: None --*/ { dw; ASSERT_MSG("Derived class did not provide DrawItemEx"); } /* virtual */ int CRMCListBox::__GetCount() const /*++ Routine Description: Provide GetCount() to ODL base class Arguments: None Return Value: Count of items in the listbox --*/ { return GetCount(); } /* virtual */ int CRMCListBox::__SetItemHeight( IN int nIndex, IN UINT cyItemHeight ) /*++ Routine Description: Provide SetItemHeight() to ODL base class Arguments: None Return Value: LB_ERR if the index or height is invalid. --*/ { return SetItemHeight(nIndex, cyItemHeight); } int CRMCListBox::OnCreate( IN LPCREATESTRUCT lpCreateStruct ) /*++ Routine Description: Listbox is being created Arguments: LPCREATESTRUCT lpCreateStruct : Creation structure Return Value: -1 for failure, 0 for success --*/ { if (CListBox::OnCreate(lpCreateStruct) == -1) { return -1; } Initialize(); return 0; } int CRMCListBox::GetCurSel() const /*++ Routine Description: Get the index of the current selected item Arguments: None Return Value: On multi-selection listbox, it will return the index of an item, iff that is the only item selected. On single-selection listbox, it behaves as normal. --*/ { if (IsMultiSelect()) { // // We only like it if one item is selected // int nCurSel = LB_ERR; if (CListBox::GetSelCount() == 1) { if (CListBox::GetSelItems(1, &nCurSel) != 1) { nCurSel = LB_ERR; } } return nCurSel; } // // Single select listbox // return CListBox::GetCurSel(); } int CRMCListBox::SetCurSel( IN int nSelect ) /*++ Routine Description: Select an item. On a multi-select listbox, this will deselect everything except the given item. Arguments: int nSelect : Index of the item to be selected, or -1 to reset all selections. Return Value: LB_ERR in case of error. --*/ { if (IsMultiSelect()) { // // Reset all selections // int nReturn = SelItemRange(FALSE, 0, GetCount() - 1); if (nSelect >= 0) { // // Ensure item is visible // nReturn = CListBox::SetSel(nSelect, TRUE); CListBox::SetCaretIndex(nSelect, 0); } return nReturn; } return CListBox::SetCurSel(nSelect); } int CRMCListBox::GetSel( IN int nSel ) const /*++ Routine Description: Determine if the given item is selected or not Works for both single and multi-select listboxes Arguments: int nSel : Item whose state to check Return Value: LB_ERR in case of error, 0 if the item in question is not selected, a positive number if it is. --*/ { if (IsMultiSelect()) { return CListBox::GetSel(nSel); } // // Some magic for single select // if (nSel < 0 || nSel >= CListBox::GetCount()) { return LB_ERR; } return nSel == CListBox::GetCurSel() ? TRUE : FALSE; } int CRMCListBox::GetSelCount() const /*++ Routine Description: Return count of selected items. Works for both single and multi select (in the former case, it will return zero or one only) Arguments: None Return Value: Count of selected items --*/ { if (IsMultiSelect()) { return CListBox::GetSelCount(); } return GetCurSel() != LB_ERR ? 1 : 0; } void * CRMCListBox::GetSelectedListItem( OUT int * pnSel OPTIONAL ) /*++ Routine Description: Return the single selected item in the list or NULL Arguments: int * pnSel : Optionally returns the selected index Returns: The currently selected (single) item, or NULL if 0 or more than one items is selected. Works for both multi-select and single select. --*/ { void * pItem = NULL; int nCurSel = GetCurSel(); if (nCurSel >= 0) { // // Get item properties // pItem = GetItemDataPtr(nCurSel); if (pnSel) { *pnSel = nCurSel; } } return pItem; } void * CRMCListBox::GetNextSelectedItem( IN OUT int * pnStartingIndex ) /*++ Routine Description: Return the next selected item starting at a specific index. Arguments: int *pnStartingIndex : Starting index (>= 0) Return Value: Pointer to next selected item, or NULL if there are none left. The starting index will be updated to reflect the current index, LB_ERR if no more selected items remain. --*/ { ASSERT_READ_WRITE_PTR(pnStartingIndex); if (!pnStartingIndex) { return NULL; } ASSERT(*pnStartingIndex >= 0); if (*pnStartingIndex < 0) { *pnStartingIndex = 0; } if (IsMultiSelect()) { // // Multi-select -- loop through // until found // BOOL fFoundItem = FALSE; while (*pnStartingIndex < GetCount()) { if (CListBox::GetSel(*pnStartingIndex) > 0) { ++fFoundItem; break; } ++(*pnStartingIndex); } if (!fFoundItem) { *pnStartingIndex = LB_ERR; } } else { // // Single select listbox, so there's no // looping through -- either the selected item // (if any) is in range or it isn't. // int nCurSel = CListBox::GetCurSel(); *pnStartingIndex = (nCurSel >= *pnStartingIndex) ? nCurSel : LB_ERR; } return (*pnStartingIndex != LB_ERR) ? GetItemDataPtr(*pnStartingIndex) : NULL; } BOOL CRMCListBox::SelectItem( IN void * pItemData ) /*++ Routine Description: Select the listbox item with the given data pointer Arguments: void * pItemData : Item to search for Return Value: TRUE if the item was found and selected, FALSE otherwise Notes: On a multi-select listbox, this will unselect all other items in the listbox. --*/ { if (pItemData != NULL) { for (int n = 0; n < GetCount(); ++n) { if (pItemData == GetItemDataPtr(n)) { SetCurSel(n); return TRUE; } } } if (!IsMultiSelect()) { // // Set no selection // SetCurSel(-1); } return FALSE; } void CRMCListBox::InvalidateSelection( IN int nSel ) /*++ Routine Description: Force a repaint of the given selection Arguments: int nSel : Index of the item to be repainted Return Value: None --*/ { CRect rc; if (GetItemRect(nSel, &rc) != LB_ERR) { InvalidateRect(&rc, TRUE); } } IMPLEMENT_DYNAMIC(CRMCListBox,CListBox); CHeaderListBox::CHeaderListBox( IN DWORD dwStyle, IN LPCTSTR lpRegKey OPTIONAL ) /*++ Routine Description: Constructor Arguments: DWORD dwStyle : Style bits (see HLS_*) LPCTSTR lpRegKey : If specified, the registry key where the column sizes will be stored. Return Value: None --*/ : m_strRegKey(), m_fInitialized(FALSE) { m_pHeader = new CRMCListBoxHeader(dwStyle); if (lpRegKey) { GenerateRegistryKey(m_strRegKey, lpRegKey); } } CHeaderListBox::~CHeaderListBox() /*++ Routine Description: Destructor Arguments: N/A Return Value: N/A --*/ { // // Clean up header control // ASSERT_PTR(m_pHeader); if (m_pHeader != NULL) { delete m_pHeader; } } // // Message map // BEGIN_MESSAGE_MAP(CHeaderListBox, CRMCListBox) //{{AFX_MSG_MAP(CHeaderListBox) ON_WM_CREATE() ON_WM_DESTROY() ON_WM_SETFOCUS() //}}AFX_MSG_MAP END_MESSAGE_MAP() /* virtual */ BOOL CHeaderListBox::Initialize() /*++ Routine Description: This function should be called directly when subclassing an existing listbox, otherwise OnCreate will take care of it, and this function should not be called Arguments: None Return Value: TRUE for success, FALSE for failure --*/ { // // Make sure we're only initialized once // if (m_fInitialized) { return TRUE; } if (!CRMCListBox::Initialize()) { return FALSE; } // // Create header control // ASSERT_PTR(m_pHeader); if (m_pHeader) { TRACEEOLID("Creating Header"); // // Create it in our location exactly // CRect rc; ::GetDlgCtlRect(GetParent()->m_hWnd, m_hWnd, &rc); // // Make sure the header control shares the same parent // as we do, // ASSERT(GetParent()); #ifndef CCS_NOHILITE #define CCS_NOHILITE 0x00000010L #endif DWORD dwStyle = WS_VISIBLE | WS_TABSTOP | CCS_TOP | CCS_NODIVIDER | WS_BORDER | HDS_HORZ; if (!m_pHeader->Create(dwStyle, rc, GetParent(), this, 0xFFFF)) { return FALSE; } } m_fInitialized = TRUE; return TRUE; } int CHeaderListBox::QueryColumnWidth( IN int nCol ) const /*++ Routine Description: Get the width of the specified column Arguments: int nCol : The column Return Value: The width of the column, or -1 if the column index was out of range --*/ { ASSERT(nCol < QueryNumColumns()); if (nCol >= QueryNumColumns()) { return -1; } HD_ITEM hdItem; hdItem.mask = HDI_WIDTH; VERIFY(GetHeaderItem(nCol, &hdItem)); return hdItem.cxy; } BOOL CHeaderListBox::SetColumnWidth( IN int nCol, IN int nWidth ) /*++ Routine Description: Set the width of the specified column Arguments: int nCol : The column int nWidth : New width Return Value: TRUE for success, FALSE for failure --*/ { ASSERT(nCol < QueryNumColumns()); if (nCol >= QueryNumColumns()) { return FALSE; } TRACEEOLID("Setting width of column " << nCol << " to " << nWidth); HD_ITEM hdItem; hdItem.mask = HDI_WIDTH; hdItem.cxy = nWidth; VERIFY(SetHeaderItem(nCol, &hdItem)); return TRUE; } BOOL CHeaderListBox::SetWidthsFromReg() /*++ Routine Description: Attempt to set the column widths from the registry value we were initialized with. Arguments: None Return Value: TRUE if the column widths were succesfully set from the registry, FALSE otherwise --*/ { if (m_strRegKey.IsEmpty()) { // // No reg key specified // return FALSE; } // // Try to read the current column sizes from the registry // CRegKey rkUser; if (ERROR_SUCCESS != rkUser.Create(HKEY_CURRENT_USER, m_strRegKey)) { // // Path doesn't exist -- no problem. // return FALSE; } // // Don't auto adjust // m_pHeader->RespondToColumnWidthChanges(FALSE); CRect rc; m_pHeader->GetClientRect(&rc); CError err; try { TCHAR buf[MAX_PATH]; DWORD count = MAX_PATH; int nTotalWidth = 0; err = rkUser.QueryValue(buf, g_szRegColumns, &count); if (err.Succeeded() && lstrlen(buf) > 0) { LPTSTR lpstrValue = buf; LPTSTR lpWidth = _tcstok(lpstrValue, g_szColValueSep); for (int n = 0; n < QueryNumColumns(); ++n) { ASSERT_PTR(lpWidth); if (lpWidth == NULL) { err = ERROR_INVALID_PARAMETER; break; } // // Sanity check // int nWidth = _ttoi(lpWidth); if (nWidth <= 0 || (nTotalWidth + nWidth > rc.Width())) { ASSERT_MSG("column width invalid"); return FALSE; } nTotalWidth += nWidth; VERIFY(SetColumnWidth(n, nWidth)); lpWidth = _tcstok(NULL, g_szColValueSep); } } } catch(CMemoryException * e) { err = ERROR_NOT_ENOUGH_MEMORY; e->Delete(); } // // Turn auto-adjust back on // m_pHeader->RespondToColumnWidthChanges(TRUE); // if (err.Win32Error() == ERROR_FILE_NOT_FOUND) // { // // No problem, it is first run. We will set defaults. // return err; // } return err; } void CHeaderListBox::DistributeColumns() /*++ Routine Description: Proportion the column widths of over the entire width of the header control while maintaining relative proportions. Arguments: None Return Value: None --*/ { // // Obtain available width // ASSERT_PTR(m_pHeader); CRect rc; m_pHeader->GetClientRect(&rc); // // Get current total width // int nTotalWeight = 0; int nCol; for (nCol = 0; nCol < QueryNumColumns(); ++nCol) { nTotalWeight += QueryColumnWidth(nCol); } // // And spread out the width, maintaining the same // proportions // // // Temporarily ignore changes // m_pHeader->RespondToColumnWidthChanges(FALSE); int cColumns = QueryNumColumns(); for (nCol = 0; nCol < cColumns; ++nCol) { int nWidth = QueryColumnWidth(nCol); nWidth = rc.Width() * nWidth / nTotalWeight; VERIFY(SetColumnWidth(nCol, nWidth)); } // // Turn changes back on // m_pHeader->RespondToColumnWidthChanges(TRUE); } int CHeaderListBox::InsertColumn( IN int nCol, IN int nWeight, IN UINT nStringID, IN HINSTANCE hResInst ) /*++ Routine Description: Insert column. The width of the column is actually a relative "weight" of the column which needs to be adjusted later. The return value is the column number or -1 if the column is not inserted. Arguments: int nCol : Column number int nWeight : Relative weight of column UINT nStringID : Resource string ID Return Value: Index of the column, or -1 in case of failure --*/ { CString strColName; HD_ITEM hdItem; HINSTANCE hInst = AfxGetResourceHandle(); AfxSetResourceHandle(hResInst); VERIFY(strColName.LoadString(nStringID)); AfxSetResourceHandle(hInst); hdItem.mask = HDI_FORMAT | HDI_WIDTH | HDI_TEXT; hdItem.fmt = HDF_STRING | HDF_LEFT; hdItem.pszText = (LPTSTR)(LPCTSTR)strColName; hdItem.cchTextMax = strColName.GetLength(); hdItem.cxy = nWeight; return InsertHeaderItem(nCol, &hdItem); } int CHeaderListBox::OnCreate( IN LPCREATESTRUCT lpCreateStruct ) /*++ Routine Description: Listbox is being created Arguments: LPCREATESTRUCT lpCreateStruct : Creation structure Return Value: 0 for success, -1 for failure --*/ { if (CRMCListBox::OnCreate(lpCreateStruct) == -1) { return -1; } Initialize(); return 0; } BOOL CHeaderListBox::EnableWindow( IN BOOL bEnable ) /*++ Routine Description: Enable/disable the control. Arguments: BOOL bEnable : TRUE to enable the control, FALSE to disable Return Value: Indicates the state before the EnableWindow member function was called. The return value is nonzero if the window was previously disabled. The return value is 0 if the window was previously enabled or an error occurred. --*/ { if (m_pHeader) { m_pHeader->EnableWindow(bEnable); } return CRMCListBox::EnableWindow(bEnable); } BOOL CHeaderListBox::ShowWindow( IN int nCmdShow ) /*++ Routine Description: Show/hide the window Arguments: int nCmdShow : SW_ flag such as SW_SHOW or SW_HIDE Return Value: If the window was previously visible, the return value is TRUE. If the window was previously hidden, the return value is FALSE. --*/ { if (m_pHeader) { m_pHeader->ShowWindow(nCmdShow); } return CRMCListBox::ShowWindow(nCmdShow); } void CHeaderListBox::OnDestroy() /*++ Routine Description: Handle destruction of the control Arguments: None Return Value: None --*/ { // // Destroy optional header control // ASSERT_PTR(m_pHeader); if (m_pHeader) { if (!m_strRegKey.IsEmpty()) { // // Try to write the current column sizes to the registry // CError err; CRegKey rkUser; rkUser.Create(HKEY_CURRENT_USER, m_strRegKey); int nWidth; TCHAR szValue[32]; CString strValue; try { for (int n = 0; n < GetHeaderItemCount(); ++n) { if (n > 0) { // // Put in field separator // strValue += g_szColValueSep; } nWidth = m_pHeader->GetColumnWidth(n); strValue += ::_itot(nWidth, szValue, 10); } err = rkUser.SetValue(strValue, g_szRegColumns); } catch(CMemoryException * e) { err = ERROR_NOT_ENOUGH_MEMORY; e->Delete(); } err.MessageBoxOnFailure(m_hWnd); } m_pHeader->DestroyWindow(); } CRMCListBox::OnDestroy(); } IMPLEMENT_DYNAMIC(CHeaderListBox, CRMCListBox); CRMCComboBox::CRMCComboBox() /*++ Routine Description: Constructor Arguments: None Return Value: N/A --*/ : m_fInitialized(FALSE) { } CRMCComboBox::~CRMCComboBox() /*++ Routine Description: Destructor Arguments: N/A Return Value: N/A --*/ { } // // Message Map // BEGIN_MESSAGE_MAP(CRMCComboBox, CComboBox) //{{AFX_MSG_MAP(CRMCComboBox) ON_WM_CREATE() //}}AFX_MSG_MAP END_MESSAGE_MAP() /* virtual */ BOOL CRMCComboBox::Initialize() /*++ Routine Description: This function should be called directly when subclassing an existing combobox, otherwise OnCreate will take care of it. Arguments: None Return Value: TRUE for success, FALSE for failure --*/ { // // Make sure we're only initialized once // if (m_fInitialized) { return TRUE; } // // Ensure the base class knows our window // handle // AttachWindow(this); if (!CODLBox::Initialize()) { return FALSE; } m_fInitialized = TRUE; return TRUE; } void CRMCComboBox::MeasureItem( IN LPMEASUREITEMSTRUCT lpMIS ) /*++ Routine Description: CComboBox override to ODL base class Arguments: LPMEASUREITEMSTRUCT lpMIS : Measure item structure Return Value: None --*/ { CODLBox::__MeasureItem(lpMIS); } void CRMCComboBox::DrawItem( IN LPDRAWITEMSTRUCT lpDIS ) /*++ Routine Description: CListBox override to ODL base class Arguments: LPDRAWITEMSTRUCT lpDIS : Drawing item structure Return Value: None --*/ { CODLBox::__DrawItem(lpDIS); } /* virtual */ void CRMCComboBox::DrawItemEx( IN CRMCListBoxDrawStruct & dw ) /*++ Routine Description: Do-nothing extended draw function, which should be provided by the derived class. This one will ASSERT, and should never be called. Arguments: CRMCListBoxDrawStruct & dw : Draw Structure Return Value: None --*/ { dw; ASSERT_MSG("Derived class did not provide DrawItemEx"); } int CRMCComboBox::OnCreate( IN LPCREATESTRUCT lpCreateStruct ) /*++ Routine Description: Combo box is being created Arguments: LPCREATESTRUCT lpCreateStruct : Creation structure Return Value: -1 for failure, 0 for success --*/ { if (CComboBox::OnCreate(lpCreateStruct) == -1) { return -1; } Initialize(); return 0; } /* virtual */ int CRMCComboBox::__GetCount() const /*++ Routine Description: Provide CComboBox::GetCount() functionality to base class Arguments: None Return Value: Get the count of items in the combo box --*/ { return GetCount(); } /* virtual */ int CRMCComboBox::__SetItemHeight( IN int nIndex, IN UINT cyItemHeight ) /*++ Routine Description: Provide CListBox::SetItemHeight() functionality to base class. Arguments: int nIndex : Index of the item UINT cyItemHeight : Height of the item Return Value: SetItemHeight return value. --*/ { return SetItemHeight(nIndex, cyItemHeight); } IMPLEMENT_DYNAMIC(CRMCComboBox,CComboBox);