Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

CListCtrl that can save and restore column layout

0.00/5 (No votes)
1 Apr 2009 1  
An example of how to implement persistence of column widths and positions.

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.

clistctrl_persist_state/screenshot.png

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;
    // Save the current configuration as default configuration
    if (!m_pViewConfig->HasDefaultConfig())
    {
        PullConfiguration(m_pViewConfig->GetDefaultConfig());
    }
    // Load the saved configuration
    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(); // Reset the existing config
    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);

        // In this example columns are visible when their width is larger than zero
        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);
    // All invisible columns must be place in the begining of the order-array
    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))
           {
               // Column still exists
               colSetting.Format(_T("ColumnWidth_%d"), i);
               int width = config.GetIntSetting(colSetting);
               SetColumnWidth(nCol, width);
               pOrderArray[--nColOrder] = nCol;
               break;
           }
        }
    }
    // Did we find any visible columns in the saved configuration ?
    if (nColOrder < nColCount)
    {
        // All remaining columns are added to the beginning with size zero
        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);
            }
        }

        // Only update the column configuration if there are visible columns
        ASSERT(nColOrder==0); // All entries in the order-array must be set
        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.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here