2760 lines
160 KiB
C++
2760 lines
160 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;
|
||
}
|
||
|