// ========================================================================== // Class Implementation : COXGridList // ========================================================================== // Version: 9.3 // This software along with its related components, documentation and files ("The Libraries") // is © 1994-2007 The Code Project (1612916 Ontario Limited) and use of The Libraries is // governed by a software license agreement ("Agreement"). Copies of the Agreement are // available at The Code Project (www.codeproject.com), as part of the package you downloaded // to obtain this file, or directly from our office. For a copy of the license governing // this software, you may contact us at legalaffairs@codeproject.com, or by calling 416-849-8900. // ////////////////////////////////////////////////////////////////////////// #include "stdafx.h" // standard MFC include #include "OXGridList.h" // class specification //#include "OXGridListRes.h" // class resources #include "OXMainRes.h" #include "UTB64Bit.h" #ifdef _DEBUG #undef THIS_FILE static char BASED_CODE THIS_FILE[] = __FILE__; static char BASED_CODE _FILE_NAME_[] = "OXGRIDLIST"; #else #define _FILE_NAME_ __FILE__ #endif #define new DEBUG_NEW ///////////////////////////////////////////////////////////////////////////// // Definition of static members int COXGridList::m_nListEditXOffset = 0;//-1; // --- The number by which the subitem X position must be adjusted to get the edit X position int COXGridList::m_nListEditYOffset = 0;//-2; // --- The number by which the subitem Y position must be adjusted to get the edit T position int COXGridList::m_nListEditCYOffset = 0;//+7; // --- The number by which the subitem height must be adjusted to get the edit height int COXGridList::m_nListEditCXOffset = 15; // --- The number by which the edit width must be enlarged starting from the text extent COXGridList::EUseExtendedData COXGridList::m_eFirstED = COXGridList::EDNo; // --- The first valid value for the enumeration type EUseExtendedData COXGridList::EUseExtendedData COXGridList::m_eLastED = COXGridList::EDRemoving; // --- The last valid value for the enumeration type EUseExtendedData /////////////////////////////////////////////////////////////////// // Data members ------------------------------------------------------------- // protected: // BOOL m_bLastRowWasVisible; // --- Whether the last row is visible or not // BOOL m_bInitialized; // --- The standard behaviour of the grid ctrl needs some initialization // This has to be performed only once. // BOOL m_bSortable; // --- Sort when the header is clicked. // BOOL m_bCheckable; // --- Whether the control is checkable or not (shows a checkbox in front of each item) // UINT m_nCheckStyle; // --- The style used to check // BOOL m_bAutoEdit; // --- Whether the control is in auto edit mode // (Start editing the item when a valid character is typed) // EUseExtendedData m_eUseExtendedData; // --- The present state of the extended data // EDNo : No COXGridListData objects are used // EDAdding : COXGridListData objects are being added // EDYes : COXGridListData objects are being used for all items // EDRemoving : COXGridListData objects are being removed // int m_nNumOfCols; // --- Number of columns in the list ctrl // int m_nImageColumn; // --- Index of column where image(small icon will be drawn,) // CPen m_GridPen; // --- Pen used to draw gridlines with // BOOL m_bGridLines; // --- Whether to draw gridlines or not // BOOL m_bHorizontalGridLines; // BOOL m_bVerticalGridLines; // --- Whether horizontal or vertical gridlines should be shown // This setting will only have effect if m_bGridLines == TRUE // CFont m_TextFont; // --- The font to draw the ROW text with (not header text) // CImageList m_stateImages; // --- The image list of the check images // COXGridEdit m_gridEdit; // --- Contains the subclassed edit control during editing // CPoint m_lastClickPos // --- Poisition were the last mouse click occured (in this window) // Position in client coordinates // int m_nEditSubItem; // --- The index of the sub item that is being edited // (Only valid between LVN_BEGINLABELEDIT and LVN_ENDLABELEDIT) // CWordArray m_rgbEditable; // --- Wether the column is allowed to be edited or not (array of BOOL) // The size of this array always equals the number of columns of the control // private: // Member functions --------------------------------------------------------- // public: COXGridList::COXGridList() { // ... Initialize all the data members Empty(); ASSERT_VALID(this); } void COXGridList::InitGrid() { m_bInitialized = TRUE; // Reset column editing array m_rgbEditable.RemoveAll(); // General Background is window background SetBkColor(GetSysColor(COLOR_WINDOW)); // Text Background is color of standard window SetTextBkColor(GetSysColor(COLOR_WINDOW)); // Text color of text in standard window SetTextColor(GetSysColor(COLOR_WINDOWTEXT)); // Standard list control font + Invalidates everything SetTextFont(); // show selection SetShowSel(TRUE); m_pContextMenu=NULL; HWND hWnd=GetHeaderCtrlHandle(); if(hWnd) { if(!::IsWindow(m_gridHeader.GetSafeHwnd())) { m_gridHeader.SubclassWindow(hWnd); } else { ASSERT(m_gridHeader.GetSafeHwnd()==hWnd); } } } BOOL COXGridList::SetSortable(BOOL bSortable /* = TRUE */) { if (bSortable) AddExtendedData(); else { RemoveExtendedData(); m_nSortCol=-1; COXGridHeader* pHeader=(COXGridHeader*)GetHeaderCtrl(); if(pHeader!=NULL) { pHeader->SortColumn(0,0); } } m_bSortable = bSortable; return TRUE; } BOOL COXGridList::GetSortable() const { return m_bSortable; } BOOL COXGridList::SetResizing(BOOL bResizing /* = TRUE */) { ((COXGridHeader*)GetHeaderCtrl())->m_bResizing = bResizing; return TRUE; } BOOL COXGridList::GetResizing() { return ((COXGridHeader*)GetHeaderCtrl())->m_bResizing; } int COXGridList::GetSortColumn() { return m_nSortCol; } BOOL COXGridList::SortColumn(int nColumn /* = 0 */) { ASSERT((0 <= nColumn) && (nColumn < GetNumCols())); COXGridListSortInfo gridListSortInfo(this, nColumn, m_bSortAscending); BOOL bResult=SortItems(m_pCompareFunc, (LPARAM)(&gridListSortInfo)); //BOOL bResult=SortItems(GridCompareProc, (LPARAM)(&gridListSortInfo)); if(bResult) { m_nSortCol=nColumn; COXGridHeader* pHeader=(COXGridHeader*)GetHeaderCtrl(); if(pHeader!=NULL) { bResult=pHeader->SortColumn(nColumn, (m_bSortAscending ? 1 : -1)); } } return bResult; } int COXGridList::GetNumCols() const { #ifdef _DEBUG // Make sure the number of columns we have stored as data memner // is the correct value int nNumber = 0; LV_COLUMN lvc; memset(&lvc, 0, sizeof(LV_COLUMN)); lvc.mask = LVCF_FMT; while(GetColumn(nNumber, &lvc)) { ++nNumber; } ASSERT(m_nNumOfCols == nNumber); #endif return m_nNumOfCols; } BOOL COXGridList::SetEqualWidth() { ASSERT_VALID(this); // Set the columns to be of equal width CRect rectLV; GetClientRect(rectLV); // Calculate the common width and the remaining space // Total width = (common width * Number of columns) + remaining space if (GetNumCols() > 0) { int nColWidth = (rectLV.Width()) / GetNumCols(); int nRemainder = (rectLV.Width()) % GetNumCols(); for (int i = 0; i < GetNumCols(); i++) { SetColumnWidth(i, nColWidth); } // ... Let the last column take the remaining width as well SetColumnWidth(GetNumCols() - 1, nColWidth + nRemainder); return TRUE; } return FALSE; } BOOL COXGridList::SetGridLines(BOOL bGridLines /* = TRUE */, COLORREF LineColor /* = RGB(0,0,0) */, BOOL bUpdate /* = TRUE */) { m_bGridLines = bGridLines; if (m_bGridLines) { // First reset Pen m_GridPen.DeleteObject(); // Recreate pen with alternating dot - space // (PS_DOT does not give a good result, so we use // PS_COSMETIC | PS_ALTERNATE) LOGBRUSH logBrush; logBrush.lbColor = LineColor; logBrush.lbStyle = BS_SOLID; logBrush.lbHatch = HS_HORIZONTAL; if (!m_GridPen.CreatePen(PS_COSMETIC | PS_ALTERNATE, 1, &logBrush, 0, NULL)) if (!m_GridPen.CreatePen(PS_COSMETIC, 1, &logBrush, 0, NULL)) TRACE0("COXGridList::SetGridLines : Error creating the grid lines Pen\n"); } if (bUpdate) RedrawWindow(); return TRUE; } BOOL COXGridList::GetGridLines(BOOL& bGridLines, COLORREF& LineColor) const { bGridLines = m_bGridLines; //Query the Pen for its current color if (m_GridPen.GetSafeHandle() != NULL) { LOGPEN logPen; m_GridPen.GetObject(sizeof(LOGPEN), &logPen); LineColor = logPen.lopnColor; } else { // ... Color not set yet, return default LineColor = RGB(0,0,0); } return TRUE; } BOOL COXGridList::SetGridLineOrientation(BOOL bHorizontal /* = TRUE */, BOOL bVertical /* = TRUE */) { if ((bHorizontal != m_bHorizontalGridLines) || (bVertical != m_bVerticalGridLines)) { m_bHorizontalGridLines = bHorizontal ; m_bVerticalGridLines = bVertical; // ... The grid line settings changed, if they are visible : refresh the control if (m_bGridLines) Invalidate(); } return TRUE; } BOOL COXGridList::GetGridLineOrientation(BOOL& bHorizontal, BOOL& bVertical) const { ASSERT_VALID(this); bHorizontal = m_bHorizontalGridLines; bVertical = m_bVerticalGridLines; return TRUE; } int COXGridList::GetCurSel() const { ASSERT_VALID(this); return GetNextItem(-1, LVNI_SELECTED); } BOOL COXGridList::SetCurSel(int nSelectionItem, BOOL bSelect /* = TRUE */) { ASSERT_VALID(this); if (nSelectionItem == -1) { BOOL bSuccess = TRUE; int nIndex = 0; int nLastIndex = GetItemCount() - 1; while (bSuccess && (nIndex <= nLastIndex)) { bSuccess = SetItemState(nIndex++, bSelect ? LVNI_SELECTED : 0, LVNI_SELECTED); } return bSuccess; } return SetItemState(nSelectionItem, bSelect ? LVNI_SELECTED : 0, LVNI_SELECTED); } // From time to time you need to know if an item is selected or not, // when used with single selection GetCurSel function is the solution, // but when used with multiple selection lists this is not an option. // Next function is designed to resolve this problem. // BOOL COXGridList::IsSelected( int nItem ) const { ASSERT_VALID(this); ASSERT(nItem >= 0); return (GetNextItem( nItem-1, LVNI_SELECTED) == nItem) ? TRUE : FALSE; } // Returns the number of selected items in a control. // Very useful when multiple selection is set // int COXGridList::GetSelCount() const { ASSERT_VALID(this); int nSelCount = 0; int nIndex = 0; int nLastIndex = GetItemCount()-1; while (nIndex <= nLastIndex) { nSelCount += (GetItemState(nIndex++, LVNI_SELECTED) == LVNI_SELECTED); } return nSelCount; } // In some programs it's important to present to the user information // in list forms that can't have an input focus and can't have any // selection bar. To get such functionality you have to call SetShowSel(FALSE) // void COXGridList::SetShowSel(BOOL bShow /* = TRUE */) { m_bShowSel = bShow; } int COXGridList::GetCurFocus() const { ASSERT_VALID(this); return GetNextItem(-1, LVNI_FOCUSED); } BOOL COXGridList::SetCurFocus(int nFocusItem, BOOL bFocus /* = TRUE */) { ASSERT_VALID(this); if (nFocusItem == -1) // ... Can have at most one item with focus, no need to iterate nFocusItem = GetNextItem(-1, LVNI_FOCUSED); return SetItemState(nFocusItem, bFocus ? LVIS_FOCUSED : 0, LVIS_FOCUSED); } BOOL COXGridList::SetTextFont(CFont* pFont /* = NULL */, BOOL bUpdate /* = TRUE */) { m_TextFont.DeleteObject(); SetFont(NULL, bUpdate); LOGFONT logFont; // Logical font struct if (pFont != NULL) { pFont->GetObject(sizeof(LOGFONT), &logFont); // Get font attributes if (!m_TextFont.CreateFontIndirect(&logFont)) { m_TextFont.CreateStockObject(SYSTEM_FONT); // Create stock font return FALSE; } SetFont(&m_TextFont, bUpdate); } if (bUpdate) RedrawWindow(); return TRUE; } BOOL COXGridList::SetShowSelAlways(BOOL bShowSelAlways /* = TRUE */) { if (GetShowSelAlways() != bShowSelAlways) { // ... Change the window style ModifyStyle(bShowSelAlways ? 0 : LVS_SHOWSELALWAYS, bShowSelAlways ? LVS_SHOWSELALWAYS : 0); // .... Invalidate the control to reflect the change Invalidate(); } return TRUE; } BOOL COXGridList::GetShowSelAlways() const { return ((GetStyle() & LVS_SHOWSELALWAYS) == LVS_SHOWSELALWAYS); } void COXGridList::SetMultipleSelection(BOOL bMultiple /* = TRUE */) { ASSERT(::IsWindow(m_hWnd)); long dwStyle = GetStyle(); if (!bMultiple && ((dwStyle & LVS_SINGLESEL) != LVS_SINGLESEL)) { // Chenge from multiple to single selection // Remove all selection except the first one int nItem = GetNextItem(-1, LVNI_SELECTED); nItem = GetNextItem(nItem, LVNI_SELECTED); while (nItem != -1) { // ... Deselect item SetItemState(nItem, 0, LVNI_SELECTED); nItem = GetNextItem(nItem, LVNI_SELECTED); } } ModifyStyle(bMultiple ? LVS_SINGLESEL : 0, bMultiple ? 0 : LVS_SINGLESEL); ASSERT(bMultiple == GetMultipleSelection()); } BOOL COXGridList::GetMultipleSelection() const { ASSERT(::IsWindow(m_hWnd)); return !((GetStyle() & LVS_SINGLESEL) == LVS_SINGLESEL); } void COXGridList::SetEditable(BOOL bEdit /* = TRUE */, int nColumn /* = -1 */) { // Always set the control to allow editing long dwStyle = GetStyle(); if ((dwStyle & LVS_EDITLABELS) != LVS_EDITLABELS) { ModifyStyle(0, LVS_EDITLABELS); } // Keep record of which columns are allowed to be edited and which are not // ... Array must have correct size ASSERT(m_rgbEditable.GetSize() == GetNumCols()); if (nColumn != -1) { // ... Must be valid column ASSERT(0 <= nColumn); ASSERT(nColumn < m_rgbEditable.GetSize()); m_rgbEditable.SetAt(nColumn, (WORD)bEdit); } else { for (int nIndex = 0; nIndex < m_rgbEditable.GetSize(); nIndex++) { m_rgbEditable.SetAt(nIndex, (WORD)bEdit); } } } BOOL COXGridList::GetEditable(int nColumn) const { ASSERT((0 <= nColumn) && (nColumn <= GetNumCols())); return (BOOL)m_rgbEditable.GetAt(nColumn); } BOOL COXGridList::SetCheckable(BOOL bCheckable /* = TRUE */) { if (bCheckable && !m_bCheckable) { // Check whether a valid state image list is already available CImageList* pStateImageList = GetImageList(LVSIL_STATE); if(pStateImageList == NULL) VERIFY(SetCheckStateImageList()); // Uncheck all items m_bCheckable = bCheckable; SetCheck(-1, 0); } if (!bCheckable && m_bCheckable) { // Remove checks (no state image) of all items SetCheck(-1, -1); m_bCheckable = bCheckable; } return TRUE; } BOOL COXGridList::GetCheckable() const { return m_bCheckable; } void COXGridList::SetCheckStyle(UINT nStyle /* = BS_AUTOCHECKBOX */) { m_nCheckStyle = nStyle; } UINT COXGridList::GetCheckStyle() const { return m_nCheckStyle; } void COXGridList::SetCheck(int nIndex, int nCheck) { if (GetCheckable()) { if ((nCheck < -1) || (2 < nCheck)) { TRACE1("COXGridList::SetCheck : Invalid check state (%i) specified.\n", nCheck); return; } if ((nCheck == 2) && (m_nCheckStyle == BS_CHECKBOX || m_nCheckStyle == BS_AUTOCHECKBOX)) { TRACE1("COXGridList::SetCheck : Invalid check state (%i) specified.\n", nCheck); return; } int nStartIndex; int nEndIndex; if (nIndex == -1) { nStartIndex = 0; nEndIndex = GetItemCount() - 1; } else { nStartIndex = nIndex; nEndIndex = nIndex; } for (int nItemIndex = nStartIndex; nItemIndex <= nEndIndex; nItemIndex++) { SetItemState(nItemIndex, INDEXTOSTATEIMAGEMASK(nCheck + 1), ALLSTATEIMAGEMASKS); } } else TRACE0("COXGridList::SetCheck : Control is not checkable.\n"); } int COXGridList::GetCheck(int nIndex) const { if (GetCheckable()) return STATEIMAGEMASKTOINDEX(GetItemState(nIndex, ALLSTATEIMAGEMASKS)) - 1; else { TRACE0("COXGridList::GetCheck : Control is not checkable.\n"); return 0; } } BOOL COXGridList::OnCheck(int nCheckItem) { // Control must be in checkable mode ASSERT(GetCheckable()); if ((m_nCheckStyle == BS_CHECKBOX) || (m_nCheckStyle == BS_3STATE)) // No auto check, just return return TRUE; int nCheck = GetCheck(nCheckItem); nCheck++; nCheck %= (m_nCheckStyle == BS_AUTOCHECKBOX ? 2 : 3); // ... The check state must actually change ASSERT(GetCheck(nCheckItem) != nCheck); OnCheckChange(nCheckItem, nCheck); // If this item is selected, iterate all the other selected items and // check them as well if (GetItemState(nCheckItem, LVIS_SELECTED) == LVIS_SELECTED) { int nItemIndex = -1; nItemIndex = GetNextItem(nItemIndex, LVNI_SELECTED); while (nItemIndex != -1) { if ((nItemIndex != nCheckItem) && (GetCheck(nItemIndex) != nCheck)) OnCheckChange(nItemIndex, nCheck); nItemIndex = GetNextItem(nItemIndex, LVNI_SELECTED); } } return TRUE; } BOOL COXGridList::OnCheckChange(int nCheckItem, int nCheck) { // ... Control must be in checkable mode ASSERT(GetCheckable()); // ... The check state must actually change ASSERT(GetCheck(nCheckItem) != nCheck); // Parent will be informed because the state of an item changes SetCheck(nCheckItem, nCheck); return TRUE; } BOOL COXGridList::SetAutoEdit(BOOL bAutoEdit /* = TRUE */) { m_bAutoEdit = bAutoEdit; return TRUE; } BOOL COXGridList::GetAutoEdit() const { return m_bAutoEdit; } CEdit* COXGridList::EditLabel(int nItem, int nSubItem) { ASSERT(::IsWindow(m_hWnd)); // If nSubItem == -1, search the first editable subitem int nSubItemIndex = 0; while ((nSubItem == -1) && (nSubItemIndex < GetNumCols())) { if (GetEditable(nSubItemIndex)) nSubItem = nSubItemIndex; nSubItemIndex++; } if (nSubItem == -1) { TRACE0("COXGridList::EditLabel : No editable column is found, ignoring edit request\n"); return NULL; } return (CEdit*)CWnd::FromHandle( (HWND)::SendMessage(m_hWnd, LVM_EDITLABEL, nItem, nSubItem)); } BOOL COXGridList::SetImageColumn(int nColumnIndex, BOOL bUpdate /* = TRUE */) { ASSERT(nColumnIndex < GetNumCols()); if (GetNumCols() <= nColumnIndex) return FALSE; m_nImageColumn = nColumnIndex; if (bUpdate) RedrawWindow(); return TRUE; } int COXGridList::GetImageColumn() const { return m_nImageColumn; } DWORD COXGridList::GetDlgBaseUnits(CDC* pDC) { ASSERT_VALID(this); CString stAlpaChars = _T("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"); DWORD dwBaseUnits; CFont* pFont = GetFont(); // Is it a system font? if (pFont == NULL) { dwBaseUnits = ::GetDialogBaseUnits(); } else { CFont* pOldFont = pDC->SelectObject(pFont); ASSERT(pOldFont); // Get the horizontal base units CSize stSize = pDC->GetTextExtent(stAlpaChars, stAlpaChars.GetLength()); WORD xBaseUnits = WORD(MulDiv(1, stSize.cx, stAlpaChars.GetLength())); // Get the vertical base units TEXTMETRIC tm; pDC->GetTextMetrics(&tm); WORD yBaseUnits = (WORD)tm.tmHeight; dwBaseUnits = MAKELONG(xBaseUnits, yBaseUnits); // Make sure to restore the old font pDC->SelectObject(pOldFont); } return dwBaseUnits; } BOOL COXGridList::IsLastRowVisible() const { BOOL bResult; bResult = GetItemCount() <= GetTopIndex() + GetCountPerPage(); return bResult; } void COXGridList::OnLastRowAppear() { // Default implementation does nothing } void COXGridList::DrawItem(LPDRAWITEMSTRUCT lpDIS) { ASSERT_VALID(this); ASSERT(m_bInitialized); // Note that 'lpDIS->itemID' = item id of the current row being // painted. if(lpDIS->itemID == -1) return; // You should get the pointer to the device context from the "lpDIS" ptr. CDC* pDC = CDC::FromHandle(lpDIS->hDC); switch(lpDIS->itemAction) { case ODA_SELECT: case ODA_DRAWENTIRE: { // 1 - Set the forground and background colors. For // selected = bkgd(COLOR_HIGHLIGHT), // fgd(COLOR_HIGHLIGHTTEXT) // otherwise = bkgd(GetTextBkColor()), // fgd(GetTextColor()) // 2 - Save the "forground" and "background" // colors of the device context in the "crlXXXXXX" variables. // // YOU MUST RESTORE THE DEVICE CONTEXT TO ITS ORIGINAL // STATE by restoring the old colors. BOOL bShowSelAlways = GetShowSelAlways(); BOOL bFocus = (::GetFocus() == m_hWnd); BOOL bShowSel = m_bShowSel ? bShowSelAlways | bFocus : FALSE; BOOL bEnabled = IsWindowEnabled(); BOOL bShowItemSel = bShowSel && ((lpDIS->itemState & ODS_SELECTED) == ODS_SELECTED); COLORREF clrForground, clrBackground; clrForground = pDC->SetTextColor(bShowItemSel ? GetSysColor(COLOR_HIGHLIGHTTEXT) : GetTextColor()); if (bEnabled) clrBackground = pDC->SetBkColor(bShowItemSel ? ::GetSysColor(COLOR_HIGHLIGHT) : GetTextBkColor()); else // ... Use disabled background color clrBackground = pDC->SetBkColor(::GetSysColor(COLOR_INACTIVEBORDER)); // Populate the listview with column text SetRowText(lpDIS, bShowItemSel); // REQUIRED: Restore the original device context colors pDC->SetTextColor(clrForground); pDC->SetBkColor(clrBackground); //////// // Is the item selected? Then draw a rectangle around the // entire selection. Note that I'm using 'lpDIS->rcItem'. //////// if ((lpDIS->itemState & ODS_FOCUS) && bFocus && m_bShowSel) { m_SelectedRect = lpDIS->rcItem; pDC->DrawFocusRect(&lpDIS->rcItem); } if (m_bGridLines) // Draw all the gridlines DrawGridLines(pDC, lpDIS); break; } default: TRACE1("COXGridList::DrawItem : Unexpected case in switch : %i\n", lpDIS->itemAction); ASSERT(FALSE); } } BOOL COXGridList::EnsureVisible(int nItem, int nSubItem, BOOL bPartialOK) { // First make item visible if (!CListCtrl::EnsureVisible(nItem, bPartialOK)) return FALSE; if ((nSubItem < 0) || (GetNumCols() <= nSubItem)) // Cannot make nonexistant subitem visible return FALSE; // Then make subitem visible; // ... Get the rect of the subitem and its possible images CRect subItemRect = GetRectFromSubItem(nItem, nSubItem, TRUE); CRect clientRect; CRect intersectRect; GetClientRect(clientRect); BOOL bIntersect = intersectRect.IntersectRect(subItemRect, clientRect); if (bIntersect && bPartialOK) // The subitem rect is partially visible and that is enough return TRUE; else { // Scroll subitem into view as best as possible CSize scrollSize(0,0); // First check whether right side is visible if (clientRect.right < subItemRect.right) { scrollSize.cx += subItemRect.right - clientRect.right; subItemRect += scrollSize; } // Then check whether left side is visible // this may override the previous scroll size of the right side check if (subItemRect.left < clientRect.left) { scrollSize.cx += subItemRect.left - clientRect.left; subItemRect += scrollSize; } // Now do the actual scrolling ASSERT(scrollSize.cy == 0); if (scrollSize.cx != 0) Scroll(scrollSize); // We did our best to make the most of the specified subitem visible return TRUE; } } BOOL COXGridList::GetSubItemFromPoint(CPoint pos, int& nItem, int& nSubItem, CRect& rect) const { nItem = -1; nSubItem = -1; rect.SetRectEmpty(); // First locate the index of the item containing the point nItem = HitTest(pos); if (nItem < 0) { TRACE0("COXGridList::GetSubItemFromPoint : Point outside valid item range\n"); return FALSE; } // Then locate the sub index containing the point BOOL bValidColumn = TRUE; BOOL bSubItemFound = FALSE; int nCol = 0; int nColWidth; CRect subItemRect; LV_COLUMN lvColumn; lvColumn.mask = LVCF_WIDTH; // Get the size of the images that can be shown in front of the label CRect boundsRect; CRect labelRect; VERIFY(GetItemRect(nItem, boundsRect, LVIR_BOUNDS)); VERIFY(GetItemRect(nItem, labelRect, LVIR_LABEL)); int nImagesWidth = labelRect.left - boundsRect.left; CImageList* pStateImageList = GetImageList(LVSIL_STATE); if ((pStateImageList != NULL) && (STATEIMAGEMASKTOINDEX(GetItemState(nItem, ALLSTATEIMAGEMASKS)) == 0)) { // The state image list exists, but this item has a state of 0 (do not show state image) // So we subtract the size of the state image from the images width IMAGEINFO imageInfo; pStateImageList->GetImageInfo(0, &imageInfo); nImagesWidth -= imageInfo.rcImage.right; } // Get the rect of the label (without incorporating the image widths) subItemRect = boundsRect; subItemRect.right = boundsRect.left + GetColumnWidth(0); if (m_nImageColumn == nCol) subItemRect.left += nImagesWidth; bSubItemFound = subItemRect.PtInRect(pos); while (!bSubItemFound && bValidColumn) { nCol++; bValidColumn = GetColumn(nCol, &lvColumn); if (bValidColumn) { nColWidth = GetColumnWidth(nCol); subItemRect.left = subItemRect.right; subItemRect.right += nColWidth; if (m_nImageColumn == nCol) subItemRect.left += nImagesWidth; bSubItemFound = subItemRect.PtInRect(pos); } } if (bSubItemFound) { nSubItem = nCol; rect = subItemRect; } else { TRACE0("COXGridList::GetSubItemFromPoint : Point outside valid subitem range\n"); return FALSE; } return TRUE; } CRect COXGridList::GetRectFromSubItem(int nItem, int nSubItem, BOOL bIncludeImages /* = FALSE */) const { // Then locate the sub index containing the point CRect resultRect(0,0,0,0); BOOL bValidColumn = TRUE; int nCol = 0; int nColWidth; CRect subItemRect; LV_COLUMN lvColumn; lvColumn.mask = LVCF_WIDTH; // Get the size of the images that can be shown in front of the label CRect boundsRect; CRect labelRect; VERIFY(GetItemRect(nItem, boundsRect, LVIR_BOUNDS)); VERIFY(GetItemRect(nItem, labelRect, LVIR_LABEL)); int nImagesWidth = labelRect.left - boundsRect.left; CImageList* pStateImageList = GetImageList(LVSIL_STATE); if ((pStateImageList != NULL) && (STATEIMAGEMASKTOINDEX(GetItemState(nItem, ALLSTATEIMAGEMASKS)) == 0)) { // The state image list exists, but this item has a state of 0 (do not show state image) // So we subtract the size of the state image from the images width IMAGEINFO imageInfo; pStateImageList->GetImageInfo(0, &imageInfo); nImagesWidth -= imageInfo.rcImage.right; } // Get the rect of the label (without incorporating the image widths) subItemRect = boundsRect; subItemRect.right = boundsRect.left + GetColumnWidth(0); if (!bIncludeImages && (m_nImageColumn == nCol)) subItemRect.left += nImagesWidth; while ((nCol != nSubItem) && bValidColumn) { nCol++; bValidColumn = GetColumn(nCol, &lvColumn); if (bValidColumn) { nColWidth = GetColumnWidth(nCol); subItemRect.left = subItemRect.right; subItemRect.right += nColWidth; if (!bIncludeImages && (m_nImageColumn == nCol)) subItemRect.left += nImagesWidth; } } if (nCol == nSubItem) resultRect = subItemRect; #ifdef _DEBUG else { TRACE2("COXGridList::GetRectFromSubItem : Item : %i and subitem : %i not found\n", nItem, nSubItem); } #endif // _DEBUG return resultRect; } CHeaderCtrl* COXGridList::GetHeaderCtrl() { ASSERT_VALID(this); HWND hHeaderWnd = GetHeaderCtrlHandle(); if (hHeaderWnd != NULL) { // CHeaderCtrl does not have additional members than those already present // in CWnd. So we just cast it to CHeaderCtrl*. ASSERT(sizeof(CHeaderCtrl) == sizeof(CWnd)); return (CHeaderCtrl*)CWnd::FromHandle(hHeaderWnd); } else return NULL; } HWND COXGridList::GetHeaderCtrlHandle() { ASSERT_VALID(this); if (m_hWnd == NULL) // ... This grid list has not been created yet return NULL; // Get the first child of the list control // Normally the list only has one child window : the header control // HWND hHeaderWnd = ::GetWindow(m_hWnd, GW_CHILD); HWND hHeaderWnd = ::GetDlgItem(m_hWnd, 0); if (hHeaderWnd != NULL) { #ifdef _DEBUG // Make extra sure we actually have a header ctrl const int nMaxClassNameLength = 50; TCHAR szClass[nMaxClassNameLength + 1]; ::GetClassName(hHeaderWnd, szClass, nMaxClassNameLength); ASSERT(_tcscmp(szClass, _T("SysHeader32")) == 0); #endif // _DEBUG return hHeaderWnd; } else { TRACE0("COXGridList::GetHeaderCtrlHandle : No child window found\n"); return NULL; } } #ifdef _DEBUG void COXGridList::AssertValid() const { CListCtrl::AssertValid(); } void COXGridList::Dump(CDumpContext& dc) const { CListCtrl::Dump(dc); dc << _T("\nm_bLastRowWasVisible: ") << m_bLastRowWasVisible; dc << _T("\nm_bInitialized: ") << m_bInitialized; dc << _T("\nm_bSortable: ") << m_bSortable; dc << _T("\nm_bCheckable: ") << m_bCheckable; dc << _T("\nm_nCheckStyle: ") << m_nCheckStyle; dc << _T("\nm_eUseExtendedData: ") << (DWORD)m_eUseExtendedData; dc << _T("\nm_nNumOfCols: ") << m_nNumOfCols; dc << _T("\nm_nImageColumn: ") << m_nImageColumn; dc << _T("\nm_GridPen: ") << m_GridPen; dc << _T("\nm_bGridLines: ") << m_bGridLines; dc << _T("\nm_bHorizontalGridLines: ") << m_bHorizontalGridLines; dc << _T("\nm_bVerticalGridLines: ") << m_bVerticalGridLines; dc << _T("\nm_SelectedRect: ") << m_SelectedRect; dc << _T("\nm_TextFont: ") << (void*)&m_TextFont; dc << _T("\nm_stateImages: ") << (void*)&m_stateImages; dc << _T("\nm_gridEdit: ") << (void*)&m_gridEdit; dc << _T("\nm_lastClickPos: ") << m_lastClickPos; dc << _T("\nm_nEditSubItem: ") << m_nEditSubItem; dc << _T("\nm_rgbEditable: ") << (void*)&m_rgbEditable; dc << _T("\nm_nListEditXOffset: ") << m_nListEditXOffset; dc << _T("\nm_nListEditYOffset: ") << m_nListEditYOffset; dc << _T("\nm_nListEditCYOffset: ") << m_nListEditCYOffset; dc << _T("\nm_nListEditCXOffset: ") << m_nListEditCXOffset; dc << _T("\n"); } #endif //_DEBUG COXGridList::~COXGridList() { ASSERT_VALID(this); m_GridPen.DeleteObject(); m_TextFont.DeleteObject(); } BEGIN_MESSAGE_MAP(COXGridList, CListCtrl) //{{AFX_MSG_MAP(COXGridList) ON_WM_VSCROLL() ON_WM_ERASEBKGND() ON_WM_CREATE() ON_WM_LBUTTONDOWN() ON_WM_DESTROY() ON_WM_KEYDOWN() ON_WM_KEYUP() ON_WM_LBUTTONDBLCLK() ON_NOTIFY_REFLECT_EX(LVN_BEGINLABELEDIT, OnBeginlabeledit) ON_NOTIFY_REFLECT_EX(LVN_COLUMNCLICK, OnColumnclick) ON_NOTIFY_REFLECT_EX(LVN_ENDLABELEDIT, OnEndlabeledit) ON_NOTIFY_REFLECT_EX(LVN_BEGINDRAG, OnListCtrlNotify) ON_WM_CHAR() ON_MESSAGE(LVM_INSERTITEM, OnInsertItem) ON_MESSAGE(LVM_INSERTCOLUMN, OnInsertColumn) ON_MESSAGE(LVM_DELETECOLUMN, OnDeleteColumn) ON_MESSAGE(LVM_DELETEALLITEMS, OnDeleteAllItems) ON_MESSAGE(LVM_DELETEITEM, OnDeleteItem) ON_MESSAGE(LVM_FINDITEM, OnFindItem) ON_MESSAGE(LVM_GETITEM, OnGetItem) ON_MESSAGE(LVM_SETITEM, OnSetItem) ON_MESSAGE(LVM_EDITLABEL, OnEditLabel) ON_MESSAGE(LVM_SETCOLUMN, OnSetColumn) ON_NOTIFY_REFLECT_EX(LVN_BEGINRDRAG, OnListCtrlNotify) ON_NOTIFY_REFLECT_EX(LVN_DELETEALLITEMS, OnListCtrlNotify) ON_NOTIFY_REFLECT_EX(LVN_DELETEITEM, OnListCtrlNotify) ON_NOTIFY_REFLECT_EX(LVN_GETDISPINFO, OnListCtrlNotify) ON_NOTIFY_REFLECT_EX(LVN_INSERTITEM, OnListCtrlNotify) ON_NOTIFY_REFLECT_EX(LVN_ITEMCHANGED, OnListCtrlNotify) ON_NOTIFY_REFLECT_EX(LVN_ITEMCHANGING, OnListCtrlNotify) ON_NOTIFY_REFLECT_EX(LVN_KEYDOWN, OnListCtrlNotify) ON_NOTIFY_REFLECT_EX(LVN_SETDISPINFO, OnListCtrlNotify) ON_NOTIFY_REFLECT_EX(NM_SETFOCUS, OnSetFocus) ON_NOTIFY_REFLECT_EX(NM_KILLFOCUS, OnLostFocus) ON_WM_PARENTNOTIFY() ON_WM_CONTEXTMENU() //}}AFX_MSG_MAP END_MESSAGE_MAP() void COXGridList::Empty() // --- In : // --- Out : // --- Returns : // --- Effect : Initializes all the data members // This function should only be called at specific moments in time // (e.g. right after construction) { m_bLastRowWasVisible = FALSE; m_bGridLines = FALSE; m_bHorizontalGridLines = TRUE; m_bVerticalGridLines = TRUE; m_bInitialized = FALSE; m_bSortable = FALSE; m_bCheckable = FALSE; m_nCheckStyle = BS_AUTOCHECKBOX; m_bAutoEdit = FALSE; m_eUseExtendedData = EDNo; m_nNumOfCols = 0; m_nImageColumn = 0; m_lastClickPos = CPoint(0,0); m_nEditSubItem = 0; m_SelectedRect.SetRectEmpty(); m_bSortAscending=FALSE; m_nSortCol=-1; m_pCompareFunc = GridCompareProc; } void COXGridList::SetGridCompareFunc( PFNLVCOMPARE pCompareFunc ) { m_pCompareFunc = pCompareFunc; } int CALLBACK COXGridList::GridCompareProc(LPARAM lParam1, LPARAM lParam2, LPARAM lParamSort) // --- In : lParam1 : lParam of the first item to compare // lParam2 : lParam of the second item to compare // lParamSort : parem of the sort object (COXGridListSortInfo) // --- Out : // --- Returns : A negative value if the first item should precede the second, // a positive value if the first item should follow the second, // or zero if the two items are equivalent. // --- Effect : Compares two items of the control // The COXGridListSortInfo contains information about which subitem to use { #if defined (_WINDLL) #if defined (_AFXDLL) AFX_MANAGE_STATE(AfxGetAppModuleState()); #else AFX_MANAGE_STATE(AfxGetStaticModuleState()); #endif #endif COXGridListSortInfo* pGridListSortInfo = (COXGridListSortInfo*)lParamSort; ASSERT(pGridListSortInfo != NULL); ASSERT(AfxIsValidAddress(pGridListSortInfo, sizeof(COXGridListSortInfo))); int nIndex1, nIndex2; CString sItemText1, sItemText2; // Lookup the first and second Item nIndex1 = pGridListSortInfo->m_pThis->FindOriginalItemData((DWORD_PTR)lParam1); nIndex2 = pGridListSortInfo->m_pThis->FindOriginalItemData((DWORD_PTR)lParam2); // query the text sItemText1 = pGridListSortInfo->m_pThis->GetItemText(nIndex1, pGridListSortInfo->m_nSubIndex); sItemText2 = pGridListSortInfo->m_pThis->GetItemText(nIndex2, pGridListSortInfo->m_nSubIndex); int iResult; // Compare both string on a No Case basis iResult = sItemText1.CompareNoCase(sItemText2); iResult = pGridListSortInfo->m_bSortAscending ? iResult : -iResult; return(iResult); } BOOL COXGridList::SetCheckStateImageList() // --- In : // --- Out : // --- Returns : // --- Effect : Sets the state image list of this control to three images : // unchecked checkbox, checked checkbox and grayed (3-state) checkbox // Their size is 13 (width) by 11 (height) each // You can easily change this by using // SetImageList(pStateImages, LVSIL_STATE) with your own list pStateImages { // The bitmap is read from resource now // ... Must find the resource // (Make sure OXGridList.rc is included in your resource file) ASSERT(AfxFindResourceHandle(MAKEINTRESOURCE(IDB_OX_STATEIMAGELIST), RT_BITMAP) != NULL); // ... Create it BOOL bSuccess = m_stateImages.Create(IDB_OX_STATEIMAGELIST,13,0,RGB(255,0,255)); if (bSuccess) { m_stateImages.SetBkColor(GetSysColor(COLOR_WINDOW)); /// ... Attach image list to list control SetImageList(&m_stateImages, LVSIL_STATE); } else TRACE0("COXGridList::SetCheckStateImageList : Failed to create the state image list\n"); return bSuccess; } BOOL COXGridList::DrawGridLines(CDC* pDC, LPDRAWITEMSTRUCT lpDIS) // --- In : pDC : the device context to draw on // lpDIS : A long pointer to a DRAWITEMSTRUCT structure that contains // information about the type of drawing required. // --- Out : // --- Returns : succeeded or not // --- Effect : Draws the vertical and horizontal gridlines with a certain color { CPen* pOldPen; pOldPen = pDC->SelectObject(&m_GridPen); // Paint vertical lines if (m_bVerticalGridLines) { int xPos = GetColumnWidth(0); int nCol = 0; // The line will be drawn in an PS_ALTERNATE style // To have a better visual effect let them start on an even vertical coordinate int nTop = lpDIS->rcItem.top + (lpDIS->rcItem.top % 2); int nWidth = lpDIS->rcItem.right - lpDIS->rcItem.left; pDC->SetWindowOrg(0,0); while(xPos <= nWidth && nCol < m_nNumOfCols) { pDC->MoveTo(xPos - 1 + lpDIS->rcItem.left, nTop); pDC->LineTo(xPos - 1 + lpDIS->rcItem.left, lpDIS->rcItem.bottom); nCol++; xPos += GetColumnWidth(nCol); } } // Paint horizontal lines if (m_bHorizontalGridLines) { if (lpDIS->rcItem.bottom != 0) { pDC->MoveTo(0, lpDIS->rcItem.bottom - 1); pDC->LineTo(lpDIS->rcItem.right, lpDIS->rcItem.bottom - 1); } } pDC->SelectObject(pOldPen); return TRUE; } void COXGridList::SetRowText(LPDRAWITEMSTRUCT lpDIS, BOOL bShowItemSel) // --- In : lpDIS : A long pointer to a DRAWITEMSTRUCT structure that contains // information about the type of drawing required. // bShowItemSel : Whether the item whould be shown as selected or not // --- Out : // --- Returns : // --- Effect : Draws the text line of a entire row { ASSERT_VALID(this); CString sItemText; // Retrieve the item rectangle size. CRect rectText = lpDIS->rcItem; // Now, start displaying the columns text. CDC* pDC = CDC::FromHandle(lpDIS->hDC); int iColumn = 0; LV_COLUMN lvc; lvc.mask = LVCF_FMT; while(iColumn < GetNumCols() && GetColumn(iColumn, &lvc)) { // get the text to be drawn and calculate its bounding rectangle sItemText = GetItemText(lpDIS->itemID, iColumn); rectText.right = rectText.left + GetColumnWidth(iColumn); if (pDC->RectVisible(rectText)) { // draws the image of the imagelist attached to the Listcontrol // adjusts the rectText rectangle to draw the text behind the image if (iColumn == m_nImageColumn) DrawImage(pDC, rectText, lpDIS->itemID, bShowItemSel); SetColItemText(pDC, sItemText, rectText, lvc.fmt); } // move the left side of the previous rect already forward to // be good for the next Text. The right of the previous bounding // rect is the left of the following rectText.left = rectText.right; iColumn++; } // Back to the old state lpDIS->rcItem.right = rectText.right; lpDIS->rcItem.bottom = rectText.bottom; } BOOL COXGridList::DrawImage(CDC* pDC, CRect& rectText, int iItemID, BOOL bSelected) // --- In : pDC : DC to draw on // rectText : Rect in which to draw the images // iItemID : Index of the item to draw // bSelected : Whether this item is selected or not // --- Out : rectText : Rect in which to draw the text // --- Returns : Whether the images could be drawn successfully // --- Effect : Draws the state image and the small image of this item { // only small images can be shown in report mode CImageList* pSmallImageList = GetImageList(LVSIL_SMALL); CImageList* pStateImageList = GetImageList(LVSIL_STATE); LV_ITEM lvI; lvI.mask = LVIF_IMAGE | LVIF_STATE; lvI.iItem = iItemID; lvI.iSubItem = 0; lvI.state = 0; lvI.stateMask = ALLSTATEIMAGEMASKS; CSize stateImageSize(0,0); CSize smallImageSize(0,0); // Query the image and state this item is linked with if (GetItem(&lvI)) { // We need the width of the images. It will be used to determine from // where we can begin writing the text IMAGEINFO imageInfo; // First get the size of the small image and the state image // ... Convert from one-based to zero-based index int nStateIndex = STATEIMAGEMASKTOINDEX(lvI.state) - 1; if ((pStateImageList != NULL) && (0 <= nStateIndex)) { pStateImageList->GetImageInfo(nStateIndex, &imageInfo); stateImageSize = CRect(imageInfo.rcImage).Size(); } if (pSmallImageList != NULL) { pSmallImageList->GetImageInfo(lvI.iImage, &imageInfo); smallImageSize = CRect(imageInfo.rcImage).Size(); } // Draw a rectangle under both images because it is possible // that one (or both) of the images does not occupy the entire space CRect imagesRect(rectText.left, rectText.top, rectText.left + smallImageSize.cx + stateImageSize.cx, rectText.bottom); pDC->FillSolidRect(imagesRect, pDC->GetBkColor()); // Draw the state image if ((pStateImageList != NULL) && (0 <= nStateIndex)) { // Because imageList could be shared between different controls, we have to // restore its original background color. COLORREF oldColor = pStateImageList->GetBkColor(); pStateImageList->SetBkColor(pDC->GetBkColor()); CPoint pos = rectText.TopLeft(); // Align the image to the center of the line if (stateImageSize.cy < rectText.Height()) pos.y += (rectText.Height() - stateImageSize.cy) / 2; VERIFY(pStateImageList->Draw(pDC, nStateIndex, pos, ILD_NORMAL)); pStateImageList->SetBkColor(oldColor); // ... to set the startpoint for text output rectText.left += stateImageSize.cx; } // Draw the small image if (pSmallImageList != NULL) { // Because imageList could be shared between different controls, we have to // restore its original background color. COLORREF oldColor = pSmallImageList->GetBkColor(); pSmallImageList->SetBkColor(pDC->GetBkColor()); CPoint pos = rectText.TopLeft(); // Align the image to the center of the line if (smallImageSize.cy < rectText.Height()) pos.y += (rectText.Height() - smallImageSize.cy) / 2; pSmallImageList->Draw(pDC, lvI.iImage, pos, bSelected ? ILD_BLEND50 : ILD_NORMAL); pSmallImageList->SetBkColor(oldColor); // ... to set the startpoint for text output rectText.left += smallImageSize.cx; } } else { TRACE0("COXGridList::DrawImage : Failed to get item info\n"); return FALSE; } return TRUE; } BOOL COXGridList::ChangeItemText(int nItem, int nSubItem, LPCTSTR pszText) // --- In : nItem: Index of the item // nSubItem : Index of the subitem // pszText : Text to set // --- Out : // --- Returns : Whether the txet of the specified subitem could be set successfully // --- Effect : This functions sets the text of a subitem by calling SetItemText() // and it also takes care of the pre- and post-notifications : // LVN_ITEMCHANGING and LVN_ITEMCHANGED { ASSERT((0 <= nItem) && (nItem < GetItemCount())); ASSERT((0 <= nSubItem) && (nSubItem < GetNumCols())); ASSERT(pszText != NULL); // Windows only send a notification if the label text changes, // not if the text of another subitem changes. // We will add the latter functionality here BOOL bSuccess = FALSE; if (nSubItem != 0) { // Notify that item is about to be changed NM_LISTVIEW nmListView; memset(&nmListView, 0, sizeof(NM_LISTVIEW)); nmListView.hdr.hwndFrom = m_hWnd; nmListView.hdr.idFrom = GetDlgCtrlID(); nmListView.hdr.code = LVN_ITEMCHANGING; nmListView.iItem = nItem; nmListView.iSubItem = nSubItem; nmListView.uChanged = LVIF_TEXT; nmListView.lParam = GetItemData(nItem); bSuccess = !GetParent()->SendMessage(WM_NOTIFY, (WPARAM)nmListView.hdr.idFrom, (LPARAM)&nmListView); if (!bSuccess) return FALSE; } // Now change the item bSuccess = SetItemText(nItem, nSubItem, pszText); if (bSuccess && (nSubItem != 0)) { // Notify that item has changed NM_LISTVIEW nmListView; memset(&nmListView, 0, sizeof(NM_LISTVIEW)); nmListView.hdr.hwndFrom = m_hWnd; nmListView.hdr.idFrom = GetDlgCtrlID(); nmListView.hdr.code = LVN_ITEMCHANGED; nmListView.iItem = nItem; nmListView.iSubItem = nSubItem; nmListView.uChanged = LVIF_TEXT; nmListView.lParam = GetItemData(nItem); GetParent()->SendMessage(WM_NOTIFY, (WPARAM)nmListView.hdr.idFrom, (LPARAM)&nmListView); } return bSuccess; } DWORD_PTR COXGridList::GetOriginalItemData(int nItem) // --- In : nItem: index of the item // --- Out : // --- Returns : The original data associated with this item or 0 when nItem is not valid // --- Effect : This functions calls the underlying window procedure directly // instead of sending a message { ASSERT(::IsWindow(m_hWnd)); LV_ITEM lvi; memset(&lvi, 0, sizeof(LV_ITEM)); lvi.iItem = nItem; lvi.mask = LVIF_PARAM; if(DefWindowProc(LVM_GETITEM, 0, (LPARAM)&lvi)) return (DWORD_PTR)lvi.lParam; else // ... Not a valid nItem, return 0 return 0; } BOOL COXGridList::SetOriginalItemData(int nItem, DWORD_PTR dwData) // --- In : nItem: index of the item // dwData : Data to set // --- Out : // --- Returns : Success or not // --- Effect : This functions calls the underlying window procedure directly // instead of sending a message { ASSERT(::IsWindow(m_hWnd)); LV_ITEM lvi; memset(&lvi, 0, sizeof(LV_ITEM)); lvi.iItem = nItem; lvi.mask = LVIF_PARAM; lvi.lParam = (LPARAM)dwData; return (BOOL)DefWindowProc(LVM_SETITEM, 0, (LPARAM)&lvi); } int COXGridList::FindOriginalItemData(DWORD_PTR dwItemData) // --- In : dwItemData : Original data to search for // --- Out : // --- Returns : THe index of the item that has this original data associated with it // --- Effect : This functions calls the underlying window procedure directly // instead of sending a message { ASSERT(::IsWindow(m_hWnd)); LV_FINDINFO lvfi; memset(&lvfi, 0, sizeof(LV_FINDINFO)); lvfi.flags = LVFI_PARAM; lvfi.lParam = (LPARAM)dwItemData; return (int) DefWindowProc(LVM_FINDITEM, (WPARAM)-1, (LPARAM)&lvfi); } int COXGridList::GetCheckItemFromPoint(const CPoint& point) const // --- In : point : Coordinate th check // --- Out : // --- Returns : The item of which the check box contains the specified point // or -1 otherwise // --- Effect : { if (!GetCheckable()) // Checkability not enabled, can never have checked anything return -1; CImageList* pStateImageList = GetImageList(LVSIL_STATE); if (pStateImageList != NULL) { IMAGEINFO imageInfo; pStateImageList->GetImageInfo(0, &imageInfo); CRect checkImageRect; // ... Get the left if(!GetItemRect(0, checkImageRect, LVIR_BOUNDS)) { // ... Cannot even get the rect of the first item : no items in control ASSERT(GetItemCount() == 0); return -1; } // ... Adjust the width checkImageRect.right = checkImageRect.left + (imageInfo.rcImage.right - imageInfo.rcImage.left); // Adjust the begin position if the images are not displayed in the label column for (int nIndex = 0; nIndex < m_nImageColumn; nIndex++) { checkImageRect += CPoint(GetColumnWidth(nIndex), 0); } // ... Check left and right limits (top and bottom are not important) if ((checkImageRect.left <= point.x) && (point.x < checkImageRect.right)) return HitTest(point); } return -1; } BOOL COXGridList::PostEditLabel(int nItem, int nSubItem) // --- In : nItem : The Item to edit // nSubItem : The subitem to edit // --- Out : // --- Returns : Whether the posting was successful // --- Effect : Post a message to begins in-place editing of the specified list view subitem // To edit the first editable subitem of a specified item use // nSubItem = -1 { ASSERT(::IsWindow(m_hWnd)); // If nSubItem == -1, search the first editable subitem int nSubItemIndex = 0; while ((nSubItem == -1) && (nSubItemIndex < GetNumCols())) { if (GetEditable(nSubItemIndex)) nSubItem = nSubItemIndex; nSubItemIndex++; } if (nSubItem == -1) { TRACE0("COXGridList::PostEditLabel : No editable column is found, ignoring edit request\n"); return FALSE; } return (BOOL)::PostMessage(m_hWnd, LVM_EDITLABEL, nItem, nSubItem); } BOOL COXGridList::SearchNextEditItem(int nItemOffset, int nSubItemOffset, int& nItem, int& nSubItem) // --- In : nItemOffset : In which direction to change the item index // nSubItemOffset : In which direction to change the sub item index // nItem : The current item index // nSubItem : The current subitem index // --- Out : nItem : The new item index // nSubItem : The new subitem index // --- Returns : Whether a new and different item or subitem was found // --- Effect : Computes the next item and subitem { ASSERT((-1 <= nItemOffset) && (nItemOffset <= 1)); ASSERT((-1 <= nSubItemOffset) && (nSubItemOffset <= 1)); int nNextItem = -1; int nNextSubItem = -1; // Find next item if (nItemOffset != 0) { nNextItem = nItem + nItemOffset; if (nNextItem < 0) // ... Wrap to end nNextItem = GetItemCount() - 1; if (GetItemCount() <= nNextItem) // ... Wrap to begin nNextItem = 0; if (nNextItem == nItem) // ... Item index must really have changed, otherwise ignore movement nNextItem = -1; } // Find next subitem if (nSubItemOffset != 0) { // Get the next (or previous) editable subitem (wrap around the end) int nSubItemIndex = nSubItem + nSubItemOffset; while ((nNextSubItem == -1) && (nSubItemIndex < GetNumCols()) && (0 <= nSubItemIndex)) { if (GetEditable(nSubItemIndex)) nNextSubItem = nSubItemIndex; nSubItemIndex += nSubItemOffset; } // Wrap around to begin if (nSubItemOffset == 1) nSubItemIndex = 0; else nSubItemIndex = GetNumCols() - 1; // Search until we reach the current edited subitem while ((nNextSubItem == -1) && (nSubItemIndex != m_nEditSubItem)) { if (GetEditable(nSubItemIndex)) nNextSubItem = nSubItemIndex; nSubItemIndex += nSubItemOffset; } } // Fill in result if (nNextItem != -1) nItem = nNextItem; if (nNextSubItem != -1) nSubItem = nNextSubItem; // Have item or subitem changed return ((nNextItem != -1) || (nNextSubItem != -1)); } void COXGridList::OnVScroll(UINT nSBCode, UINT nPos, CScrollBar* pScrollBar) { // ... Store the old state BOOL bLastRowWasVisible = IsLastRowVisible(); // ... Call base class implementation CListCtrl::OnVScroll(nSBCode, nPos, pScrollBar); if (!bLastRowWasVisible && IsLastRowVisible()) // ... Last row has just appeared OnLastRowAppear(); } void COXGridList::OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags) { if (GetCheckable() && (nChar == VK_SPACE)) { int nIndex = GetCurFocus(); if (0 <= nIndex) OnCheck(nIndex); } if (nChar == VK_INSERT) { // Edit first editable subitem of the focussed item int nIndex = GetCurFocus(); if (0 <= nIndex) EditLabel(nIndex, -1); } // ... Store the old state BOOL bLastRowWasVisible = IsLastRowVisible(); // ... Call base class implementation CListCtrl::OnKeyDown(nChar, nRepCnt, nFlags); m_bLastRowWasVisible = IsLastRowVisible(); if (!bLastRowWasVisible && m_bLastRowWasVisible) { // ... Last row has just appeared OnLastRowAppear(); m_bLastRowWasVisible = TRUE; } } void COXGridList::OnKeyUp(UINT nChar, UINT nRepCnt, UINT nFlags) { // ... Store the old state BOOL bLastRowWasVisible = IsLastRowVisible(); // ... Call base class implementation CListCtrl::OnKeyUp(nChar, nRepCnt, nFlags); if ((!bLastRowWasVisible || !m_bLastRowWasVisible) && IsLastRowVisible()) { // ... Last row has just appeared OnLastRowAppear(); m_bLastRowWasVisible = TRUE; } } BOOL COXGridList::OnEraseBkgnd(CDC* pDC) { // When using the ListCtrl we do not need the space covered by // the added rows to be painted by the OnEraseBkgnd of the CListCtrl. // This avoids some annoying flicker // Exclude the area occupied by the rows from the region to be erased // we are drawing all that stuff ourselves int nCount; BOOL bReturn=FALSE; CRect ItemRect; CRect topItemRect; CRect ClipRect; nCount=GetItemCount(); if(nCount>0) { pDC->GetClipBox(ClipRect); int nFirstVisibleItemIndex=GetTopIndex(); ASSERT(nFirstVisibleItemIndex!=-1); GetItemRect(nCount-1,ItemRect,LVIR_BOUNDS); GetItemRect(nFirstVisibleItemIndex,topItemRect,LVIR_BOUNDS); if(ItemRect.bottom>ClipRect.bottom) { ItemRect.bottom=ClipRect.bottom; } // ... Exclude area that should not be filled with background pDC->ExcludeClipRect(ItemRect); } bReturn=CListCtrl::OnEraseBkgnd(pDC); if(nCount>0) { // ... Include area that was not filled with background InvalidateRect(ItemRect, FALSE); } return bReturn; } void COXGridList::SetColItemText(CDC* pDC, CString& stColText, CRect& rectText, UINT nJustify, UINT nFormat/*=DT_END_ELLIPSIS|DT_NOPREFIX*/) // --- In : pDC : the device context to draw on // sCellText : the text to be drawn // rectText : the bounding rect of the text // --- Out : // --- Returns : // --- Effect : Draws text on a DC within a rectangle { // Align the text in the whole grid CRect TmpRect(rectText); // Draw the background fast pDC->ExtTextOut(rectText.left, rectText.top, ETO_OPAQUE | ETO_CLIPPED, rectText, NULL, 0, NULL); // Draw the text TmpRect.top += 1; // Cosmetic TmpRect.InflateRect(-2, 0); // Text does not touch borders nFormat&=~(DT_LEFT|DT_RIGHT|DT_CENTER); switch(nJustify & LVCFMT_JUSTIFYMASK) { case LVCFMT_LEFT: nFormat |= DT_LEFT; break; case LVCFMT_RIGHT: nFormat |= DT_RIGHT; break; case LVCFMT_CENTER: nFormat |= DT_CENTER; break; default: ASSERT(FALSE); break; } ::DrawText(pDC->m_hDC, stColText, -1, TmpRect, nFormat); } COXGridList::EUseExtendedData COXGridList::GetExtendedDataState() // --- In : // --- Out : // --- Returns : The extended data state at this moment // --- Effect : { ASSERT(m_eFirstED <= m_eUseExtendedData); ASSERT(m_eUseExtendedData <= m_eLastED); return m_eUseExtendedData; } void COXGridList::SetExtendedDataState(EUseExtendedData eUseExtendedData) // --- In : eUseExtendedData : The new extended data state // --- Out : // --- Returns : // --- Effect : Only certain state changes are allowed // EDNo -> EDAdding -> EDYes -> EDRemoving -> ... { #ifdef _DEBUG // ... Range check requested state ASSERT(m_eFirstED <= eUseExtendedData); ASSERT(eUseExtendedData <= m_eLastED); // ... Range check current state ASSERT(m_eFirstED <= m_eUseExtendedData); ASSERT(m_eUseExtendedData <= m_eLastED); // Only certain state transistions are valid // EDNo -> EDAdding -> EDYes -> EDRemoving -> ... // ... EDNo -> EDNo, EDAdding if (m_eUseExtendedData == EDNo) ASSERT((eUseExtendedData == EDNo) || (eUseExtendedData == EDAdding)); // ... EDYes -> EDYes, EDRemoving if (m_eUseExtendedData == EDYes) ASSERT((eUseExtendedData == EDYes) || (eUseExtendedData == EDRemoving)); // ... EDAdding -> EDAdding, EDYes if (m_eUseExtendedData == EDAdding) ASSERT((eUseExtendedData == EDAdding) || (eUseExtendedData == EDYes)); // ... EDRemoving -> EDRemoving, EDNo if (m_eUseExtendedData == EDRemoving) ASSERT((eUseExtendedData == EDRemoving) || (eUseExtendedData == EDNo)); #endif // _DEBUG m_eUseExtendedData = eUseExtendedData; } BOOL COXGridList::AddExtendedData() // --- In : // --- Out : // --- Returns : Whether it was successful or not // --- Effect : Adds extended data (COXGridListData objects) to each item // if this was not already done { if (GetExtendedDataState() == EDYes) // Already using extended data, nothing to do return TRUE; // ... Extended data should not be in use, nor in an intermediate state ASSERT(GetExtendedDataState() == EDNo); // Mark that we are adding SetExtendedDataState(EDAdding); // Iterate all present rows and create COXGridListData objects // and attach them to each item int nIndex = 0; DWORD_PTR dwUserData; COXGridListData* pGridListData; for ( ; nIndex < GetItemCount(); nIndex++) { dwUserData = GetOriginalItemData(nIndex); pGridListData = new COXGridListData(dwUserData); // ... Must succeed, may not be cancelled VERIFY(SetOriginalItemData(nIndex, (DWORD_PTR)pGridListData)); } // Mark that we have finished adding SetExtendedDataState(EDYes); return TRUE; } BOOL COXGridList::RemoveExtendedData() // --- In : // --- Out : // --- Returns : Whether it was successful or not // --- Effect : Removes extended data (COXGridListData objects) from each item // if this was not already done { if (GetExtendedDataState() == EDNo) // Not using extended data, nothing to do return TRUE; // ... Extended data should be in use ASSERT(GetExtendedDataState() == EDYes); // Mark that we are removing SetExtendedDataState(EDRemoving); // Iterate all present rows and delete the COXGridListData objects int nIndex = 0; DWORD_PTR dwUserData; COXGridListData* pGridListData; for ( ; nIndex < GetItemCount(); nIndex++) { pGridListData = (COXGridListData*)GetOriginalItemData(nIndex); ASSERT(pGridListData != NULL); ASSERT(AfxIsValidAddress(pGridListData, sizeof(COXGridListData))); dwUserData = pGridListData->m_dwUserData; delete pGridListData; // ... Must succeed, may not be cancelled VERIFY(SetOriginalItemData(nIndex, dwUserData)); } // Mark that we have finished removing SetExtendedDataState(EDNo); return TRUE; } BOOL COXGridList::AdjustNotification(NM_LISTVIEW* pNMListView) // --- In : pNMListView // --- Out : // --- Returns : Whether this message should be eaten (not passed on to the parent) // --- Effect : Adjusts the pNMListView to reflect the correct lParam value { // ... Do not eat the message by default BOOL bEaten = FALSE; if((GetExtendedDataState() != EDYes) && (GetExtendedDataState() != EDNo)) { // ... Item in intermediate change, do not pass this message to the parent, eat it bEaten = TRUE; } else if (GetExtendedDataState() == EDYes) { // ... Substitute the lParam for the user data by dereferencing COXGridListData COXGridListData* pGridListData; pGridListData = (COXGridListData*)GetOriginalItemData(pNMListView->iItem); if (pGridListData != NULL) { ASSERT(AfxIsValidAddress(pGridListData, sizeof(COXGridListData))); pNMListView->lParam = pGridListData->m_dwUserData; } } return bEaten; } void COXGridList::RefreshFocusItem() // --- In : // --- Out : // --- Returns : // --- Effect : Invalidates the visible item that has focus { ASSERT_VALID(this); int nFocusItem = GetCurFocus(); if (nFocusItem != -1) RedrawItems(nFocusItem, nFocusItem); } void COXGridList::RefreshSelItems() // --- In : // --- Out : // --- Returns : // --- Effect : Invalidates the visible itess that are selected { ASSERT_VALID(this); // ... Item with an index greater than nMaxVisibleItem are surely not visible int nMaxVisibleItem = GetTopIndex() + GetCountPerPage(); // ... We start searching for a selected item from the first visible item int nSelItem = GetNextItem(GetTopIndex() - 1, LVNI_SELECTED); while ((0 <= nSelItem) && (nSelItem <= nMaxVisibleItem)) { RedrawItems(nSelItem, nSelItem); nSelItem = GetNextItem(nSelItem, LVNI_SELECTED); } } // private: // ========================================================================== int COXGridList::OnCreate(LPCREATESTRUCT lpCreateStruct) { if (CListCtrl::OnCreate(lpCreateStruct) == -1) return -1; // ... Window must be valid ASSERT(m_hWnd != NULL); ASSERT(::IsWindow(m_hWnd)); // ... List control must be in report view and owner drawn ASSERT((GetStyle() & LVS_REPORT) == LVS_REPORT); ASSERT((GetStyle() & LVS_OWNERDRAWFIXED) == LVS_OWNERDRAWFIXED); // Initialize this control InitGrid(); return 0; } afx_msg LRESULT COXGridList::OnInsertItem(WPARAM wParam, LPARAM lParam) { // ... Use original LV_ITEM by default; const LV_ITEM* pLviOriginal = (const LV_ITEM*)lParam; BOOL bUseCopy = FALSE; LV_ITEM lviCopy; ::ZeroMemory((void*)&lviCopy,sizeof(lviCopy)); // Extended should be in use or not, not in an intermediate state ASSERT((GetExtendedDataState() == EDNo) || (GetExtendedDataState() == EDYes)); if (GetExtendedDataState() == EDYes) { // We're using lParam ourselves (for sorting), so change the mask // to tell the List ctrl this member is also valid // We use copy because pItem is const if (!bUseCopy) { // Copy not yet used : initialise it first ASSERT(AfxIsValidAddress(pLviOriginal, sizeof(LV_ITEM))); memcpy(&lviCopy, pLviOriginal, sizeof(LV_ITEM)); bUseCopy = TRUE; } if ((lviCopy.mask & LVIF_PARAM) != LVIF_PARAM) { lviCopy.lParam = 0; lviCopy.mask |= LVIF_PARAM; } lviCopy.lParam = (LPARAM)new COXGridListData(lviCopy.lParam); } // If the control is checkable but no image state was specified, add it if (GetCheckable() && (((pLviOriginal->mask & LVIF_STATE) != LVIF_STATE) || !(pLviOriginal->stateMask & ALLSTATEIMAGEMASKS)) ) { // The state is not filled out, so we set it to not checked by default // We have to tell the List ctrl the state member is also valid // We use copy because pItem is const if (!bUseCopy) { // Copy not yet used : initialise it first ASSERT(AfxIsValidAddress(pLviOriginal, sizeof(LV_ITEM))); memcpy(&lviCopy, pLviOriginal, sizeof(LV_ITEM)); bUseCopy = TRUE; } // ... Remove all state image masks and add our (non checked) if ((lviCopy.mask & LVIF_STATE) == LVIF_STATE) { // ...State already used, remove all state images lviCopy.state &= ~ALLSTATEIMAGEMASKS; } else { // ... State not yet used, initialize it and use it now lviCopy.state = 0; lviCopy.stateMask = 0; lviCopy.mask |= LVIF_STATE; } lviCopy.state |= INDEXTOSTATEIMAGEMASK(0 + 1); lviCopy.stateMask |= ALLSTATEIMAGEMASKS; } // pass the message and the (changed) item struct directly to the windows // list ctrl, otherwise we get a recursive call of this function return DefWindowProc(LVM_INSERTITEM, wParam, bUseCopy ? (LPARAM)&lviCopy : lParam); } afx_msg LRESULT COXGridList::OnInsertColumn(WPARAM wParam, LPARAM lParam) { // pass the message and the changed item struct directly to the windows // list ctrl otherwise we get a recursive call of this function DWORD_PTR dwIndex = DefWindowProc(LVM_INSERTCOLUMN, wParam, lParam); if (dwIndex != -1) { // success, so increase the number of columns m_nNumOfCols++; // Adjust our array of editable columns // Disallow editing by default m_rgbEditable.InsertAt(dwIndex, (WORD)FALSE); ASSERT(GetNumCols() == m_rgbEditable.GetSize()); // Workaround for Windows bug : // Window does not repaint the list control after a column has been inserted // So the old text is still shown. We invalidate it now. Invalidate(); } return dwIndex; } afx_msg LRESULT COXGridList::OnDeleteColumn(WPARAM wParam, LPARAM lParam) { // pass the message and the changed item struct directly to the windows // list ctrl otherwise we get a recursive call of this function BOOL bSuccess = (BOOL)DefWindowProc(LVM_DELETECOLUMN, wParam, lParam); if (bSuccess) { // Success, so decrease the number of columns m_nNumOfCols--; // Adjust our array of editable columns // Disallow editing by default m_rgbEditable.RemoveAt(wParam); ASSERT(GetNumCols() == m_rgbEditable.GetSize()); // Workaround for Windows bug : // Window does not repaint the list control after a column has been deleted // So the old text is still shown. We invalidate it now. Invalidate(); } return bSuccess; } BOOL COXGridList::OnBeginlabeledit(NMHDR* pNMHDR, LRESULT* pResult) { LV_DISPINFO* pDispInfo = (LV_DISPINFO*)pNMHDR; *pResult = 0; // ... Do not eat this message by default BOOL bEaten = FALSE; int nItem = pDispInfo->item.iItem; HWND hWndEdit = (HWND)::SendMessage(m_hWnd, LVM_GETEDITCONTROL, 0, 0L); // Normally pGridEdit->GetSafeHwnd() == NULL but in some rare situations this is not true. // The normal behaviour of notification messages is this : First Windows sends the notification // message to the parent of the window/control that has to be notified. This parent first sends // this notification message to the control itself. If the control does not handle the notification // message, the parent will try to handle it. But in the occasion that this parent is a MFC // CControlBar, we noticed that, if the control does not handle the notification message, CControlBar:: // WindowProc(...) passes this message on to its OWNER = the mainframe in most cases. // See the following code fragment : // // LRESULT CControlBar::WindowProc(UINT nMsg, WPARAM wParam, LPARAM lParam) // { // ASSERT_VALID(this); // // LRESULT lResult; // switch (nMsg) // { // case WM_NOTIFY: // case WM_COMMAND: // case WM_DRAWITEM: // case WM_MEASUREITEM: // case WM_DELETEITEM: // case WM_COMPAREITEM: // case WM_VKEYTOITEM: // case WM_CHARTOITEM: // // send these messages to the owner if not handled // if (OnWndMsg(nMsg, wParam, lParam, &lResult)) // return lResult; // else // return GetOwner()->SendMessage(nMsg, wParam, lParam); // } // // // otherwise, just handle in default way // lResult = CWnd::WindowProc(nMsg, wParam, lParam); // return lResult; // } // // The result of this implementation causes the mainframe to get the notification message and // thsi mainframe wil first pass the message to the control, which then receives this message // for the second time. We'll test whether the editcontrol is allready subclassed. If so, // return immediately and in case of the mainframe which sent us this second message, the message // passing will stop there, if it's not handled. // Therefore we skip it here. COXGridEdit* pGridEdit=GetGridEditCtrl(); ASSERT(pGridEdit!=NULL); if(pGridEdit->GetSafeHwnd() == hWndEdit) return bEaten; ASSERT(pGridEdit->GetSafeHwnd() == NULL); pGridEdit->Initialize(); pGridEdit->SubclassWindow(hWndEdit); int nFoundItem; CRect subItemRect; if(!GetSubItemFromPoint(m_lastClickPos, nFoundItem, m_nEditSubItem, subItemRect)) { // The user did not click on a valid subitem, nothing to do // ... Prevent editing by returning TRUE *pResult = TRUE; // ... Handled this message, do not pass on to the parent bEaten = TRUE; return bEaten; } ASSERT(0 <= m_nEditSubItem); ASSERT(m_nEditSubItem < GetNumCols()); if (!GetEditable(m_nEditSubItem)) { // Editing for this column is not allowed // ... Prevent editing by returning TRUE *pResult = TRUE; // ... Handled this message, do not pass on to the parent bEaten = TRUE; return bEaten; } if (nFoundItem != pDispInfo->item.iItem) { // Normally (nFoundItem == pDispInfo->item.iItem) but in the rare situation that // m_lastClickPos does not point to the focused item this is not true // (e.g. state change notification aborted the setting of the focus) // Therfore we assign it here. nFoundItem = pDispInfo->item.iItem; subItemRect = GetRectFromSubItem(nFoundItem, m_nEditSubItem); } // Set the edit window left pos to the left of the corresponding column CPoint ptEdit = subItemRect.TopLeft(); if (ptEdit.x < 0) // ... Never let the edit control start left of the left border ptEdit.x = 0; pGridEdit->SetWindowPos(CPoint(ptEdit.x + m_nListEditXOffset, ptEdit.y + m_nListEditYOffset)); pGridEdit->SetWindowHeight(subItemRect.Height() + m_nListEditCYOffset); // When the label is not completely visible, we have to adjust the width of the edit control // Because this adjustment is less than optimal, we only use it when absolutely needed CRect labelRect; VERIFY(GetItemRect(nFoundItem, labelRect, LVIR_LABEL)); if(labelRect.left<0) pGridEdit->AdjustWindowWidth(m_nListEditCXOffset); // Set the new window text to the contents of the corresponding column CString sText = GetItemText(nItem, m_nEditSubItem); pGridEdit->SetDeferedWindowText(sText); // Remove the selection of all items except the focused item int nSelItem = -1; while((nSelItem = GetNextItem(nSelItem, LVNI_SELECTED)) != -1) { if (nSelItem != nItem) SetItemState(nSelItem, 0, LVNI_SELECTED); } return bEaten; } BOOL COXGridList::OnEndlabeledit(NMHDR* pNMHDR, LRESULT* pResult) { LV_DISPINFO* pDispInfo = (LV_DISPINFO*)pNMHDR; pDispInfo->item.iSubItem=m_nEditSubItem; // ... Never eat this message BOOL bEaten = FALSE; *pResult = 0; // If not cancelled, set the text in the correct column if ((pDispInfo->item.pszText != NULL) && (pDispInfo->item.iItem != -1)) { if (!ChangeItemText(pDispInfo->item.iItem, m_nEditSubItem, pDispInfo->item.pszText)) // It the setting of the new text was prevented by LVN_ITEMCHANGING, do not continue return bEaten; } COXGridEdit* pGridEdit=GetGridEditCtrl(); ASSERT(pGridEdit!=NULL); // If the editing was ended by pressing the a key // start editing the next (or previous) subitem UINT nChar; BOOL bShift; BOOL bCtrl; if (pGridEdit->GetEndKey(nChar, bShift, bCtrl)) { int nItem = pDispInfo->item.iItem; int nSubItem = m_nEditSubItem; int nItemOffset = 0; int nSubItemOffset = 0; // Switch different possible keys if ((nChar == VK_TAB) && !bShift) // ... Next subitem nSubItemOffset = 1; if ((nChar == VK_TAB) && bShift) // ... Previuos subitem nSubItemOffset = -1; if (nChar == VK_INSERT) // ... Next subitem nSubItemOffset = 1; if (nChar == VK_UP) // ... Previous item nItemOffset = -1; if (nChar == VK_DOWN) // ... Next item nItemOffset = 1; int nOldItem=nItem; int nOldSubItem=nSubItem; // ... Always edit a subitem, even when it is the same as before SearchNextEditItem(nItemOffset, nSubItemOffset, nItem, nSubItem); if(nOldItem!=nItem || nOldSubItem!=nSubItem) { // ... Make sure the item is visible before we edit it EnsureVisible(nItem, nSubItem, FALSE); if(nItemOffset==1) Update(nOldItem-1); else if(nItemOffset==-1) Update(nOldItem); PostEditLabel(nItem, nSubItem); } } return bEaten; } void COXGridList::OnLButtonDown(UINT nFlags, CPoint point) { // Store last point that was clicked m_lastClickPos = point; // Check whether the state icon was clicked int nCheckItem = GetCheckItemFromPoint(point); if (0 <= nCheckItem) { OnCheck(nCheckItem); // If we do not have focus, get it if (GetFocus() != this) SetFocus(); } else CListCtrl::OnLButtonDown(nFlags, point); } void COXGridList::OnDestroy() { // Make sure list control is not sortable anymore and thus all // COXGridListData objects are cleaned up VERIFY(SetSortable(FALSE)); // ... Re-initialize all the data members Empty(); // Call base class implmentation CListCtrl::OnDestroy(); } LRESULT COXGridList::OnDeleteAllItems(WPARAM wParam, LPARAM lParam) { // make sure that edit control is hidden if(GetGridEditCtrl()==GetFocus()) { SetFocus(); } // First destroy all COXGridListData objects (if any) EUseExtendedData eUseExtendedData = GetExtendedDataState(); if (eUseExtendedData == EDYes) VERIFY(RemoveExtendedData()); // Pass the message directly to the windows LRESULT result = DefWindowProc(LVM_DELETEALLITEMS, wParam, lParam); // Set the extended data state back to the original state if (eUseExtendedData == EDYes) VERIFY(AddExtendedData()); return result; } afx_msg LRESULT COXGridList::OnDeleteItem(WPARAM wParam, LPARAM lParam) { // make sure that edit control is hidden if(GetGridEditCtrl()==GetFocus()) { SetFocus(); } int nFirstVisibleItemIndex=GetTopIndex(); ASSERT(nFirstVisibleItemIndex!=-1); if(nFirstVisibleItemIndex!=0 && nFirstVisibleItemIndex+GetCountPerPage()>=GetItemCount()) { CRect rectItem; GetItemRect(nFirstVisibleItemIndex,rectItem,LVIR_BOUNDS); Scroll(CSize(0,-rectItem.Height())); } // First delete the COXGridListData object if one is associated with this item // ... Extended should be in use or not, not in an intermediate state COXGridListData* pGridListDataToDelete=NULL; ASSERT((GetExtendedDataState() == EDNo) || (GetExtendedDataState() == EDYes)); if (GetExtendedDataState() == EDYes) { pGridListDataToDelete = (COXGridListData*)GetOriginalItemData((int)wParam); ASSERT(pGridListDataToDelete != NULL); } // Pass the message directly to the windows LRESULT lResult=DefWindowProc(LVM_DELETEITEM, wParam, lParam); if (pGridListDataToDelete != NULL) { ASSERT(AfxIsValidAddress(pGridListDataToDelete, sizeof(COXGridListData))); delete pGridListDataToDelete; } return lResult; } afx_msg LRESULT COXGridList::OnFindItem(WPARAM wParam, LPARAM lParam) { int nStart = (int)wParam; const LV_FINDINFO* plvfi = (const LV_FINDINFO*)lParam; ASSERT(AfxIsValidAddress(plvfi, sizeof(LV_FINDINFO))); // If the control is not sortable or we are not searching for an LPARAM // then let the default implementation handle it // ... Extended should be in use or not, not in an intermediate state ASSERT((GetExtendedDataState() == EDNo) || (GetExtendedDataState() == EDYes)); if ((GetExtendedDataState() == EDNo) || ((plvfi->flags & LVFI_PARAM) != LVFI_PARAM)) { // Pass the message directly to the windows return DefWindowProc(LVM_FINDITEM, wParam, lParam); } else { // We will iterate all the items and look for the requested // LPARAM by dereferencing all the COXGridListData objects int nIndex = nStart + 1; int nLastIndex = GetItemCount() - 1; COXGridListData* pGridListData; for ( ; nIndex <= nLastIndex; nIndex++) { pGridListData = (COXGridListData*)GetOriginalItemData(nIndex); ASSERT(pGridListData != NULL); ASSERT(AfxIsValidAddress(pGridListData, sizeof(COXGridListData))); if (pGridListData->m_dwUserData == (DWORD)plvfi->lParam) return nIndex; } // Specified LPARAM not found, returning -1 return (LRESULT)-1; } } afx_msg LRESULT COXGridList::OnGetItem(WPARAM wParam, LPARAM lParam) { // Pass the message directly to the windows HRESULT result = (HRESULT)(INT_PTR)DefWindowProc(LVM_GETITEM, wParam, lParam); LV_ITEM* plvi = (LV_ITEM*)lParam; ASSERT(AfxIsValidAddress(plvi, sizeof(LV_ITEM))); // If it succeeded and the list control is sortable and // the LPARAM of and item was requested // then fill it out correctly (dereference the COXGridListData object) // ... Extended should be in use or not, not in an intermediate state ASSERT((GetExtendedDataState() == EDNo) || (GetExtendedDataState() == EDYes)); if (result && (GetExtendedDataState() == EDYes) && ((plvi->mask & LVIF_PARAM) == LVIF_PARAM)) { COXGridListData* pGridListData; pGridListData = (COXGridListData*)plvi->lParam; ASSERT(AfxIsValidAddress(pGridListData, sizeof(COXGridListData))); plvi->lParam = pGridListData->m_dwUserData; } return result; } afx_msg LRESULT COXGridList::OnSetItem(WPARAM wParam, LPARAM lParam) { const LV_ITEM* plvi = (const LV_ITEM*)lParam; ASSERT(AfxIsValidAddress(plvi, sizeof(LV_ITEM))); // ... Extended should be in use or not, not in an intermediate state ASSERT((GetExtendedDataState() == EDNo) || (GetExtendedDataState() == EDYes)); if ((GetExtendedDataState() == EDYes) && ((plvi->mask & LVIF_PARAM) == LVIF_PARAM)) { // The list is sortable and the LPARAM will be changed // First get the corresponding COXGridListData object, // change its user data value COXGridListData* pGridListData = (COXGridListData*)GetOriginalItemData(plvi->iItem); ASSERT(pGridListData != NULL); ASSERT(AfxIsValidAddress(pGridListData, sizeof(COXGridListData))); pGridListData->m_dwUserData = plvi->lParam; // Then let the default implementation handle the REST // So we remove the LVIF_PARAM request // We have to copy the plvi struct because it is const LV_ITEM lvi; memcpy(&lvi, plvi, sizeof(LV_ITEM)); lvi.mask &= (~LVIF_PARAM); return DefWindowProc(LVM_SETITEM, wParam, (LPARAM)&lvi); } // Pass the message directly to the windows return DefWindowProc(LVM_SETITEM, wParam, lParam); } afx_msg LRESULT COXGridList::OnSetColumn(WPARAM wParam, LPARAM lParam) { // Pass the message directly to the windows BOOL bResult=(BOOL)DefWindowProc(LVM_SETCOLUMN, wParam, lParam); if(bResult && (int)wParam==m_nSortCol) { COXGridHeader* pHeader=(COXGridHeader*)GetHeaderCtrl(); if(pHeader!=NULL) { pHeader->SortColumn(m_nSortCol, (m_bSortAscending ? 1 : -1)); } } return (LRESULT)bResult; } void COXGridList::OnLButtonDblClk(UINT nFlags, CPoint point) { int nCheckItem = HitTest(point); if (GetCheckable() && (0 <= nCheckItem)) { OnCheck(nCheckItem); } else CListCtrl::OnLButtonDblClk(nFlags, point); } afx_msg LRESULT COXGridList::OnEditLabel(WPARAM wParam, LPARAM lParam) { ASSERT(::IsWindow(m_hWnd)); int nItem = (int)wParam; int nSubItem = (int)lParam; // Simulate that the user clicked in the specified subitem CRect subItemRect = GetRectFromSubItem(nItem, nSubItem); if (!subItemRect.IsRectNull()) m_lastClickPos = subItemRect.TopLeft(); SetFocus(); return (HRESULT) DefWindowProc(LVM_EDITLABEL, wParam, 0); } BOOL COXGridList::OnColumnclick(NMHDR* pNMHDR, LRESULT* pResult) { NM_LISTVIEW* pNMListView = (NM_LISTVIEW*)pNMHDR; BOOL bEaten = FALSE; // The user clicked on one of the column headings - sort by this column. if (GetSortable()) { // ... Extended should be in use ASSERT(GetExtendedDataState() == EDYes); CWaitCursor wc; SortColumn(pNMListView->iSubItem, pNMListView->iSubItem==m_nSortCol ? !m_bSortAscending : TRUE); // ... Handled this message, do not pass on to the parent bEaten = TRUE; } *pResult = 0; return bEaten; } void COXGridList::PreSubclassWindow() { // ... Attached window must be valid ASSERT(m_hWnd != NULL); ASSERT(::IsWindow(m_hWnd)); // ... List control must be in report view and owner drawn ASSERT((GetStyle() & LVS_REPORT) == LVS_REPORT); ASSERT((GetStyle() & LVS_OWNERDRAWFIXED) == LVS_OWNERDRAWFIXED); // Initialize this control _AFX_THREAD_STATE* pThreadState=AfxGetThreadState(); // hook not already in progress if(pThreadState->m_pWndInit==NULL) { InitGrid(); } CListCtrl::PreSubclassWindow(); } BOOL COXGridList::OnListCtrlNotify(NMHDR* pNMHDR, LRESULT* pResult) { NM_LISTVIEW* pNMListView = (NM_LISTVIEW*)pNMHDR; *pResult = 0; // Adjust the pNMListView and eat the message if necessary return AdjustNotification(pNMListView); } void COXGridList::OnChar(UINT nChar, UINT nRepCnt, UINT nFlags) { if(m_bAutoEdit && nChar!=VK_ESCAPE && nChar!=VK_RETURN && nChar!=VK_BACK) { // Start editing the item that has focus int nFocusItem = GetCurFocus(); if (nFocusItem < 0) { // ... No item has focus return; } CEdit* pEdit; pEdit = EditLabel(nFocusItem, -1); // pEdit can be NULL if e.g. no column is editable if (pEdit != NULL) { CString sText((TCHAR)nChar); pEdit->SetWindowText(sText); pEdit->SetSel(1, 1); } else { // .. No editable subitem is found ::MessageBeep(0xFFFFFFFF); return; } // We handled the message, do not call base class implementation return; } CListCtrl::OnChar(nChar, nRepCnt, nFlags); } BOOL COXGridList::OnSetFocus(NMHDR* /* pNMHDR */, LRESULT* pResult) { // ... Never eat this message BOOL bEaten = FALSE; // Refresh the selected and focussed items RefreshFocusItem(); if (!GetShowSelAlways()) RefreshSelItems(); *pResult = 0; return bEaten; } BOOL COXGridList::OnLostFocus(NMHDR* /* pNMHDR */, LRESULT* pResult) { // ... Never eat this message BOOL bEaten = FALSE; // Refresh the selected and focussed items RefreshFocusItem(); if (!GetShowSelAlways()) RefreshSelItems(); *pResult = 0; return bEaten; } void COXGridList::OnParentNotify(UINT message, LPARAM lParam) { CListCtrl::OnParentNotify(message,lParam); if(LOWORD(message)==WM_CREATE) { HWND hWnd=GetHeaderCtrlHandle(); if(hWnd==(HWND)lParam) { if(!::IsWindow(m_gridHeader.GetSafeHwnd())) { m_gridHeader.SubclassWindow(hWnd); } else { ASSERT(m_gridHeader.GetSafeHwnd()==hWnd); } } } } void COXGridList::OnContextMenu(CWnd* pWnd, CPoint point) { // TODO: Add your message handler code here UNREFERENCED_PARAMETER(pWnd); // fill in GLCONTEXTMENU structure to use it in the notification GLCONTEXTMENU glcm; glcm.pMenu=m_pContextMenu; glcm.point=point; // fill structure for notification NMGRIDLIST nmgl; nmgl.hdr.code=GLN_CONTEXTMENU; nmgl.lParam=(LPARAM)(&glcm); // if parent didn't reject to display menu then do that if(!SendGLNotification(&nmgl) && m_pContextMenu!=NULL && m_pContextMenu->GetMenuItemCount()>0) m_pContextMenu->TrackPopupMenu(TPM_LEFTALIGN|TPM_LEFTBUTTON,point.x,point.y,this); } LRESULT COXGridList::SendGLNotification(LPNMGRIDLIST pNMGL) { ASSERT(::IsWindow(GetSafeHwnd())); // send standard grid list notification to its parent using // NMGRIDLIST structure // notify parent CWnd* pParentWnd=GetParent(); if(pParentWnd) { // fill notification structure pNMGL->hdr.hwndFrom=GetSafeHwnd(); pNMGL->hdr.idFrom=GetDlgCtrlID(); return (pParentWnd->SendMessage(WM_NOTIFY,(WPARAM)GetDlgCtrlID(), (LPARAM)pNMGL)); } else return (LRESULT)0; }