|
||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||
|
Announcements
Chapters
Services
Feature Zones
|
IntroductionMicrosoft's This article will demonstrate the following:
The demo application allows us to show / hide columns when right-clicking the column headers.
BackgroundThere are lots of advanced grid controls that extend the This article is part of a series, where the final article CGridListCtrlEx combines the details of all the articles. How To Implement a Column Picker in CListCtrlUsually, there are two approaches for implementing the ability to dynamically hide and show columns in a
The second approach is the one described in this article, and it introduces some issues we have to take care of:
Show and Hide ColumnsThe solution of hiding a column by changing the width to zero seems easy, but there is also some extra work:
BOOL CListCtrl_Column_Picker::ShowColumn(int nCol, bool bShow)
{
SetRedraw(FALSE);
ColumnState& columnState = GetColumnState(nCol);
int nColCount = GetHeaderCtrl()->GetItemCount();
int* pOrderArray = new int[nColCount];
VERIFY( GetColumnOrderArray(pOrderArray, nColCount) );
if (bShow)
{
// Restore the position of the column
int nCurIndex = -1;
for(int i = 0; i < nColCount ; ++i)
{
if (pOrderArray[i]==nCol)
nCurIndex = i;
else
if (nCurIndex!=-1)
{
// We want to move it to the original position,
// and after the last hidden column
if ( (i <= columnState.m_OrgPosition)
|| !IsColumnVisible(pOrderArray[i])
)
{
pOrderArray[nCurIndex] = pOrderArray[i];
pOrderArray[i] = nCol;
nCurIndex = i;
}
}
}
}
else
{
// Move the column to the front of the display order list
int nCurIndex(-1);
for(int i = nColCount-1; i >=0 ; --i)
{
if (pOrderArray[i]==nCol)
{
// Backup the current position of the column
columnState.m_OrgPosition = i;
nCurIndex = i;
}
else
if (nCurIndex!=-1)
{
pOrderArray[nCurIndex] = pOrderArray[i];
pOrderArray[i] = nCol;
nCurIndex = i;
}
}
}
VERIFY( SetColumnOrderArray(nColCount, pOrderArray) );
delete [] pOrderArray;
if (bShow)
{
// Restore the column width
columnState.m_Visible = true;
VERIFY( SetColumnWidth(nCol, columnState.m_OrgWidth) );
}
else
{
// Backup the column width
int orgWidth = GetColumnWidth(nCol);
VERIFY( SetColumnWidth(nCol, 0) );
columnState.m_Visible = false;
columnState.m_OrgWidth = orgWidth;
}
SetRedraw(TRUE);
Invalidate(FALSE);
return TRUE;
}
Prevent Resizing of Hidden ColumnsWe have to block the resize events for the hidden columns. This is done by intercepting the resize event ( BEGIN_MESSAGE_MAP(CListCtrl_Column_Picker, CListCtrl)
ON_MESSAGE(LVM_SETCOLUMNWIDTH, OnSetColumnWidth)
ON_NOTIFY_EX(HDN_BEGINTRACKA, 0, OnHeaderBeginResize)
ON_NOTIFY_EX(HDN_BEGINTRACKW, 0, OnHeaderBeginResize)
END_MESSAGE_MAP()
BOOL CListCtrl_Column_Picker::OnHeaderBeginResize(UINT, NMHDR* pNMHDR, LRESULT* pResult)
{
// Check that column is allowed to be resized
NMHEADER* pNMH = (NMHEADER*)pNMHDR;
int nCol = (int)pNMH->iItem;
if (!IsColumnVisible(nCol))
{
*pResult = TRUE; // Block resize
return TRUE; // Block event
}
return FALSE;
}
LRESULT CListCtrl_Column_Picker::OnSetColumnWidth(WPARAM wParam, LPARAM lParam)
{
// Check that column is allowed to be resized
int nCol = (int)wParam;
if (!IsColumnVisible(nCol))
{
return FALSE;
}
// Let the CListCtrl handle the event
return DefWindowProc(LVM_SETCOLUMNWIDTH, wParam, lParam);
}
This doesn't handle the situation where one can double-click the divider area of the column, which causes the column to resize according to its entire content. Handling the resize event ( BEGIN_MESSAGE_MAP(CListCtrl_Column_Picker, CListCtrl)
ON_NOTIFY_EX(HDN_DIVIDERDBLCLICKA, 0, OnHeaderDividerDblClick)
ON_NOTIFY_EX(HDN_DIVIDERDBLCLICKW, 0, OnHeaderDividerDblClick)
END_MESSAGE_MAP()
BOOL CListCtrl_Column_Picker::OnHeaderDividerDblClick(UINT, NMHDR* pNMHDR,
LRESULT* pResult)
{
NMHEADER* pNMH = (NMHEADER*)pNMHDR;
SetColumnWidthAuto(pNMH->iItem);
return TRUE; // Don't let parent handle the event
}
There is also a special shortcut key we can press in any BEGIN_MESSAGE_MAP(CListCtrl_Column_Picker, CListCtrl)
ON_WM_KEYDOWN() // OnKeydown
END_MESSAGE_MAP()
void CListCtrl_Column_Picker::OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags)
{
switch(nChar)
{
case VK_ADD: // CTRL + NumPlus (Auto size all columns)
{
if (GetKeyState(VK_CONTROL) < 0)
{
// Special handling to avoid showing "hidden" columns
SetColumnWidthAuto(-1);
return;
}
} break;
}
CListCtrl::OnKeyDown(nChar, nRepCnt, nFlags);
}
BOOL CListCtrl_Column_Picker::SetColumnWidthAuto(int nCol, bool includeHeader)
{
if (nCol == -1)
{
for(int i = 0; i < GetHeaderCtrl()->GetItemCount() ; ++i)
{
SetColumnWidthAuto(i, includeHeader);
}
return TRUE;
}
else
{
if (includeHeader)
return SetColumnWidth(nCol, LVSCW_AUTOSIZE_USEHEADER);
else
return SetColumnWidth(nCol, LVSCW_AUTOSIZE);
}
}
Prevent the Drag of Hidden ColumnsWe cannot drag a column if the width is zero, but we still have to ensure that when other columns are dragged, they are not dragged in between the hidden ones. This is done by intercepting the end drag event ( BEGIN_MESSAGE_MAP(CListCtrl_Column_Picker, CListCtrl)
ON_NOTIFY_EX(HDN_ENDDRAG, 0, OnHeaderEndDrag)
END_MESSAGE_MAP()
BOOL CListCtrl_Column_Picker::OnHeaderEndDrag(UINT, NMHDR* pNMHDR, LRESULT* pResult)
{
NMHEADER* pNMH = (NMHEADER*)pNMHDR;
if (pNMH->pitem->mask & HDI_ORDER)
{
// Correct iOrder so it is just after the last hidden column
int nColCount = GetHeaderCtrl()->GetItemCount();
int* pOrderArray = new int[nColCount];
VERIFY( GetColumnOrderArray(pOrderArray, nColCount) );
for(int i = 0; i < nColCount ; ++i)
{
if (IsColumnVisible(pOrderArray[i]))
{
pNMH->pitem->iOrder = max(pNMH->pitem->iOrder,i);
break;
}
}
delete [] pOrderArray;
}
return FALSE;
}
Inserting and Deleting ColumnsWe have to keep the list of column-visible-state synchronized with the actual list of displayed columns. This could be done by providing a custom method for inserting / deleting columns, which also updates the column state list. Another approach is to monitor for the events issued when a column is inserted / deleted. BEGIN_MESSAGE_MAP(CListCtrl_Column_Picker, CListCtrl)
ON_MESSAGE(LVM_DELETECOLUMN, OnDeleteColumn)
ON_MESSAGE(LVM_INSERTCOLUMN, OnInsertColumn)
END_MESSAGE_MAP()
LRESULT CListCtrl_Column_Picker::OnDeleteColumn(WPARAM wParam, LPARAM lParam)
{
// Let the CListCtrl handle the event
LRESULT lRet = DefWindowProc(LVM_DELETECOLUMN, wParam, lParam);
if (lRet == FALSE)
return FALSE;
// Book keeping of columns
DeleteColumnState((int)wParam);
return lRet;
}
LRESULT CListCtrl_Column_Picker::OnInsertColumn(WPARAM wParam, LPARAM lParam)
{
// Let the CListCtrl handle the event
LRESULT lRet = DefWindowProc(LVM_INSERTCOLUMN, wParam, lParam);
if (lRet == -1)
return -1;
int nCol = (int)lRet;
// Book keeping of columns
if (GetColumnStateCount() < GetHeaderCtrl()->GetItemCount())
InsertColumnState((int)nCol, true); // Insert as visible
return lRet;
}
Using the CodeThe source code provided includes a simple implementation of a Points of InterestThe demo application demonstrates a little oddity in the The demo application also shows a little visual quirk introduced by using this way of showing / hiding columns. If hiding a column, then move the mouse to the start of the first column-header, so the mouse icon will change as we should be able to resize the column. History
|
|||||||||||||||||||||||||||||||||||||||||||||||