2025-11-27 16:46:48 +09:00

2761 lines
78 KiB
C++

// ==========================================================================
// 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;
}