Introduction
Microsoft's CListCtrl
has support for displaying data in a grid using the report style, but certain changes are required for it to remember its column state (column order, width, etc.).
This implementation supports the following features:
- Can backup and restore column width and column order.
- Can handle that columns are added / removed from the initial configuration.
- Can handle column configurations for multiple views.
- Can reset the column configuration to the initial configuration.
- Can switch between multiple column state profiles. Useful if you want an extra special column configuration (e.g., when printing).
- Easy to change to another persistence layer.
Background
There are lots of advanced grid controls that extend the CListCtrl
so it can persist the column state. But, because these grid controls can be very complex, it can be difficult to see how they do it.
At the same time, there is seldom separation between loading and saving the column state, and the chosen persistence layer (Registry, INI-, XML-file).
This article is part of a series, where the final article CGridListCtrlEx combines the details of all the articles.
Choosing the persistence layer
There are usually three different layers to choose from:
- Registry database - Supports deep hierarchy and binary data-types.
- INI file - Supports one level hierarchy and string data-types.
- XML file - Supports deep hierarchy and string data-types.
Microsoft has made it easy to store settings in the Registry database or an INI-file, through the CWinApp
object and its methods GetProfileString()
and WriteProfileString()
.
Many people have made great efforts in creating the best implementation for saving / storing settings:
Using the code
Unique column identifier
Each column in the CListCtrl
must have a unique identifier, or else the backup and restore of column states will not function properly. Also, when removing an obsolete column, the unique identifiers of the other columns should remain unchanged.
We can specify the column identifier (col_id
) when calling CListCtrl::InsertColumn()
.
m_ListCtrl.InsertColumn(col_pos, _T("Column Title"), LVCFMT_LEFT, 100, col_id);
It is important when adding / removing columns that the column identifiers of the other columns remains the same.
Setting the persistence layer
PersistViewConfigWinApp
implements the interface PersistViewConfig
, and makes it possible to store settings in INI-files or the Registry through CWinApp
.
void CListCtrl_Column_Persist::LoadConfiguration(PersistViewConfigProfiles* pViewConfig)
{
m_pViewConfig = pViewConfig;
if (!m_pViewConfig->HasDefaultConfig())
{
PullConfiguration(m_pViewConfig->GetDefaultConfig());
}
PushConfiguration(*m_pViewConfig);
}
Saving the column state
When using the LVS_EX_HEADERDRAGDROP
style, we must retrieve the column order from the CHeaderCtrl
. We store the unique column identifiers according to the column order (along with the column width).
void CListCtrl_Column_Persist::PullConfiguration(PersistViewConfig& config)
{
config.RemoveCurrentConfig(); int nColCount = GetHeaderCtrl()->GetItemCount();
int* pOrderArray = new int[nColCount];
GetColumnOrderArray(pOrderArray, nColCount);
int nVisibleCols = 0;
for(int i = 0 ; i < nColCount; ++i)
{
int nCol = pOrderArray[i];
int nColData = GetColumnData(nCol);
if (IsColumnVisible(nCol))
{
CString colSetting;
colSetting.Format(_T("ColumnData_%d"), nVisibleCols);
config.SetIntSetting(colSetting, nColData);
colSetting.Format(_T("ColumnWidth_%d"), nVisibleCols);
config.SetIntSetting(colSetting, GetColumnWidth(nCol));
nVisibleCols++;
}
}
config.SetIntSetting(_T("ColumnCount"), nVisibleCols);
delete [] pOrderArray;
}
Applying the column state
The reverse of saving the column state, but with the extra twist that we must handle the situations where columns no longer exist or new columns have arrived.
void CListCtrl_Column_Persist::PushConfiguration(const PersistViewConfig& config)
{
int nVisibleCols = config.GetIntSetting(_T("ColumnCount"));
int nColCount = GetHeaderCtrl()->GetItemCount();
int* pOrderArray = new int[nColCount];
GetColumnOrderArray(pOrderArray, nColCount);
SetRedraw(FALSE);
int nColOrder = nColCount;
for(int i = nVisibleCols-1; i >= 0; --i)
{
CString colSetting;
colSetting.Format(_T("ColumnData_%d"), i);
int nColData = config.GetIntSetting(colSetting);
for(int nCol = 0; nCol < nColCount; ++nCol)
{
if (nColData==GetColumnData(nCol))
{
colSetting.Format(_T("ColumnWidth_%d"), i);
int width = config.GetIntSetting(colSetting);
SetColumnWidth(nCol, width);
pOrderArray[--nColOrder] = nCol;
break;
}
}
}
if (nColOrder < nColCount)
{
for(int nCol = nColCount-1; nCol >= 0; --nCol)
{
bool visible = false;
for(int i = nColOrder; i < nColCount; ++i)
{
if (pOrderArray[i]==nCol)
{
visible = true;
break;
}
}
if (!visible)
{
pOrderArray[--nColOrder] = nCol;
SetColumnWidth(nCol, 0);
}
}
ASSERT(nColOrder==0); SetColumnOrderArray(nColCount, pOrderArray);
}
delete [] pOrderArray;
SetRedraw(TRUE);
Invalidate();
UpdateWindow();
}
Resetting the column configuration
Reset the current configuration to the initial state, and then load the current configuration.
void CListCtrl_Column_Persist::ResetConfiguration()
{
if (m_pViewConfig->HasDefaultConfig())
{
m_pViewConfig->ResetConfigDefault();
PushConfiguration(*m_pViewConfig);
}
}
Changing the configuration profile
Before changing to the new configuration profile, we save the current column state.
void CListCtrl_Column_Persist::ChangeProfile(const CString& profile)
{
PullConfiguration(*m_pViewConfig);
m_pViewConfig->SetActiveProfile(profile);
PushConfiguration(*m_pViewConfig);
}
Points of interest
None so far.
History
- 2008-03-30 - Initial post.