List Control: Header Drag, Column Resize, and Remember User Settings

AID: 1645
  • Status: Published

2360 points

  • By
  • TypeTutorial
  • Posted on2009-09-26 at 19:58:47

Here's how to let your user rearrange and resize the columns in your ListView Control, and have them be that same way the next time he runs your program.

The Common Controls ListView Control in "Report View" style is an excellent way to display a grid of data.  It has many capabilities, including the ability to let the user rearrange the columns and resize them.  That sort of customization is not always needed, but in a case where the user will spend a lot of time with the grid, you really don't want to lock him in to whatever you, the developer, think would be the best layout.

Fig1-1.JPG
  • 47 KB
  • Drag headers to rearrange column order
Drag headers to rearrange column order


By default, the ListView control lets your user make columns wider and narrower, and it's not hard to turn on the feature that allows drag-and-drop column headers to rearrange the columns,  In the dialog editor, you will also need to set the View property to Report, and make sure that "No Column Header" is FALSE, then add this code:

    m_ctlList.SetExtendedStyle( LVS_EX_HEADERDRAGDROP );
                                  
1:

Select allOpen in new window

...to the dialog initialization section.    

But after the user rearranges the columns, your program won't remember the column order settings the next time it runs unless you take action to save and restore them.  The place to save such settings is in the registry.  All you really need is the column order and the width of each column.

Fig1-2.JPG
  • 23 KB
  • Save arrangement in the registry
Save arrangement in the registry


To set up such a scheme, your program creates the list in the normal way, setting  LV_COLUMN.SubItemId to sequential values.  Then use the header control's SetItem function to specify HDITEM.iOrder and HDITEM.cxy with different sequences and widths, as saved in a previous session.  

This is all handled in the CListCtrlEx class object defined in the following source code:

CListCtrlEx header file

#pragma once
// CListCtrlEx.h

//--------------- these two structs are used to define the default column layout
typedef enum {
	SORT_None = 0,
	SORT_AZ   = 1,
	SORT_ZA   = -1,
} SortOrder;

typedef struct {
	int       nId;
	int       nClmNum;
	int       nWidth;       // pixels
	SortOrder eSortOrder;
	LPSTR     pszName;
} ListClmInfo;

#define CNUM_MaxListClms 20
//------------------------------------------- the class object extends CListCtrl
class CListCtrlEx : public CListCtrl
{
	DECLARE_DYNAMIC(CListCtrlEx)

public:
	CListCtrlEx();
	virtual ~CListCtrlEx();

	void SetupClms( ListClmInfo* parLCI, BOOL fSetFromSaved=FALSE, LPCSTR szKey=0 );

	void SaveClmInfo(     LPCSTR sKey ); 
	void UseSavedClmInfo( LPCSTR sKey ); 

	void EraseList();
	BOOL PutItem(int nItem,int nSubItem, int nVal,      BOOL fAddRow=FALSE );
	BOOL PutItem(int nItem,int nSubItem, LPCTSTR sItem, BOOL fAddRow=FALSE );

private:
	void SaveClmHdrInfo( LPCSTR sKey, int nClms, LPINT paiIndexes, LPINT paiWidths );
	BOOL ReadClmHdrInfo( LPCSTR sKey, int nClms, LPINT paiIndexes, LPINT paiWidths );

protected:
	DECLARE_MESSAGE_MAP()
};
                                  
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
37:
38:
39:
40:
41:
42:
43:
44:

Select allOpen in new window



CListCtrlEx source file

// ListCtrlEx.cpp : implementation file
//
#include "stdafx.h"
#include "ListClms.h"
#include "ListCtrlEx.h"

IMPLEMENT_DYNAMIC(CListCtrlEx, CListCtrl)

//----------------------------------- nothing special for the ctor or dtor
CListCtrlEx::CListCtrlEx(){}
CListCtrlEx::~CListCtrlEx(){}

BEGIN_MESSAGE_MAP(CListCtrlEx, CListCtrl)
END_MESSAGE_MAP()

/////////////////////////////////////////////////////////////////////////////
// Save and restore user prefs on column positions and widths
// to the registry key for this program
//
void CListCtrlEx::SaveClmInfo( LPCSTR szKey ) 
{
	HDITEM rHdr;

	int nClmCnt= GetHeaderCtrl()->GetItemCount();

	int anClmIdxs[   CNUM_MaxListClms ];
	int anClmWidths[ CNUM_MaxListClms ];

	for (int j=0; j< nClmCnt; j++ ) {
		rHdr.mask= HDI_WIDTH | HDI_ORDER;
		GetHeaderCtrl()->GetItem(j, &rHdr);	
		anClmIdxs[   j ] = rHdr.iOrder;
		anClmWidths[ j ] = rHdr.cxy;
	}
	SaveClmHdrInfo( szKey, nClmCnt, anClmIdxs, anClmWidths );
}

void CListCtrlEx::UseSavedClmInfo( LPCSTR szKey ) 
{
	HDITEM rHdr;

	int nClmCnt= GetHeaderCtrl()->GetItemCount();

	int anClmIdxs[   CNUM_MaxListClms ];
	int anClmWidths[ CNUM_MaxListClms ];

	if ( ReadClmHdrInfo( szKey, nClmCnt, anClmIdxs, anClmWidths ) ) {
		for (int j=0; j< nClmCnt; j++ ) {
			if ( anClmWidths[j] == 0 ) anClmWidths[j]= 5;

			rHdr.mask= HDI_WIDTH | HDI_ORDER;
			rHdr.iOrder= anClmIdxs[   j ];
			rHdr.cxy   = anClmWidths[ j ];
			GetHeaderCtrl()->SetItem(j, &rHdr);	
		}
	}
}
//-------------------------------------------
// private functions used by the above to get data from registry
//
void CListCtrlEx::SaveClmHdrInfo( LPCSTR szKey, int nClms, LPINT paiIndexes, LPINT paiWidths )
{
	CString sAll="";
	CString sOne;
	if (szKey==0) szKey= "ListClms";
	
	for (int j=0; j< nClms; j++ ) {
		sOne.Format("%d,", *paiIndexes++ );
		sAll += sOne;
		sOne.Format("%d, ", *paiWidths++ );
		sAll += sOne;
	}
	AfxGetApp()->WriteProfileString("Prefs", sKey, sAll);
}
BOOL CListCtrlEx::ReadClmHdrInfo( LPCSTR szKey, int nClms, LPINT paiIndexes, LPINT paiWidths )
{
	if (szKey==0) szKey= "ListClms";

	CString sAll= AfxGetApp()->GetProfileString("Prefs", szKey );
	if (sAll.IsEmpty()) {
		return FALSE;
	}
	sAll += "                                ";
	int nCurPos=0;
	for (int j=0; j<nClms; j++ ) {

		*paiIndexes++ = atoi( sAll.Mid( nCurPos ) );
		nCurPos= sAll.Find( ',', nCurPos ) +1; // get past the comma
		if ( nCurPos <= 0) return( FALSE );

		*paiWidths++  = atoi( sAll.Mid( nCurPos ) );
		nCurPos= sAll.Find( ',', nCurPos ) +1; // get past the comma
		if ( nCurPos <= 0) return( FALSE );
	}
	return TRUE;
}

/////////////////////////////////////////////////////////////////////////////
// Generic utilities simplify use of ListView control

//------------------------------------------------------------------	
// Set up the columns -- given title, order and widths from a setup array
//
void CListCtrlEx::SetupClms( ListClmInfo* parLCI, BOOL fSetFromSaved /*=FALSE*/, LPCSTR szKey /*=0*/ )
{
	LV_COLUMN  rLVC;

	EraseList();  // delete all columns

	while (parLCI->nId != -1 ) {
		rLVC.mask=     LVCF_FMT | LVCF_WIDTH | LVCF_TEXT | LVCF_SUBITEM;
		rLVC.cx=       parLCI->nWidth;     // in pixels
		rLVC.iSubItem= parLCI->nId;        // 
		rLVC.pszText=  parLCI->pszName;
		rLVC.fmt=      LVCFMT_LEFT;

		int nRet= InsertColumn( parLCI->nClmNum, &rLVC );
		parLCI++;
	}
	if ( fSetFromSaved ) {
		UseSavedClmInfo( szKey );
	}
}

void CListCtrlEx::EraseList()
{
	DeleteAllItems();
	while( DeleteColumn(0) )
		;
	UpdateWindow();
}
//--------------------------------------
// Used in populating the list -- overloads given for an int or a string value
// Use fAddRow=TRUE when populating the first column in the new row
//
BOOL CListCtrlEx::PutItem(int nItem,int nSubItem, int nVal, BOOL fAddRow /*=FALSE*/ )
{
	CString str;  str.Format("%d",nVal);
	return( PutItem(nItem, nSubItem, str) );
}

BOOL CListCtrlEx::PutItem(int nItem,int nSubItem, LPCTSTR sItem, BOOL fAddRow /*=FALSE*/)
{
	LV_ITEM         lvItm;
	lvItm.mask=     LVIF_TEXT;
	lvItm.iItem=    nItem;
	lvItm.iSubItem= nSubItem;
	lvItm.pszText=  (LPSTR)sItem;
	if( nSubItem == 0 && fAddRow ) {
		return InsertItem( &lvItm );
	}
	return SetItem( &lvItm );
}
                                  
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
37:
38:
39:
40:
41:
42:
43:
44:
45:
46:
47:
48:
49:
50:
51:
52:
53:
54:
55:
56:
57:
58:
59:
60:
61:
62:
63:
64:
65:
66:
67:
68:
69:
70:
71:
72:
73:
74:
75:
76:
77:
78:
79:
80:
81:
82:
83:
84:
85:
86:
87:
88:
89:
90:
91:
92:
93:
94:
95:
96:
97:
98:
99:
100:
101:
102:
103:
104:
105:
106:
107:
108:
109:
110:
111:
112:
113:
114:
115:
116:
117:
118:
119:
120:
121:
122:
123:
124:
125:
126:
127:
128:
129:
130:
131:
132:
133:
134:
135:
136:
137:
138:
139:
140:
141:
142:
143:
144:
145:
146:
147:
148:
149:
150:
151:
152:
153:

Select allOpen in new window



The CListCtrl object will save the column layout to the application-defined registry key, in a subkey named prefs.  You can save layouts for multiple lists -- a common need in a data-centric application program -- by passing a subkey name into the SaveClmInfo and UseSavedColmInfo member functions.

The object provides a clean and easy way to define the initial column layout.  You don't need to use it, but I think you'll find it handy.  Just create an array of ListClmInfo structures and pass it to CListCtrlEx.SetupClms().  For instance:

ListClmInfo arClmsList[]= {
  {  0,  0, 130, SORT_None, "Name"     },
  {  1,  1,  90, SORT_None, "Created"  },
  {  2,  2,  60, SORT_None, "Your Ref" },
  {  3,  3,  80, SORT_None, "Type"     },
  {  4,  4,  40, SORT_None, "Status"   },
  {  5,  5,  80, SORT_None, "Comment"  },
  { -1,  0,   0, SORT_None, 0          }
};
...
m_ctlList.SetExtendedStyle( LVS_EX_HEADERDRAGDROP );

m_ctlList.SetupClms( arClmsList, FALSE );
                                  
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:

Select allOpen in new window


To use this, just use the dialog editor to create a ListView control object.  Set its properties to Report View.  Right-click it, choose Add Variable..., and create a control-type variable (e.g., m_ctlList).  Then in the dialog's header, change the line:

	//CListCtrl m_ctlList;  // put here by the class wizard
	CListCtrlEx m_ctlList;  // <<<==== Change to this
                                  
1:
2:

Select allOpen in new window


You can create and populate your lists in whatever way you normally do, and the save/restore functions will work as described.

A commonly-asked question here at EE is "How can I let the user change the sort order by clicking on a header?"  I've be covered that in another EE Article.  See:

      List Control: Sorting Columns on a Header Click

=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
If you liked this article and want to see more from this author,  please click the Yes button near the:
      Was this article helpful?
label that is just below and to the right of this text.   Thanks!
=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=

    Asked On
    2009-09-26 at 19:58:47ID1645
    Tags

    CListCtrl

    ,

    LVS_EX_HEADERDRAGDROP

    ,

    customize columns

    ,

    change column width

    ,

    change column order

    ,

    Dan Rollins

    Topic

    Windows Programming

    Views
    1789

    Comments

    Add your Comment

    Please Sign up or Log in to comment on this article.

    Loading Advertisement...

    Top Win OS Dev Experts

    1. DanRollins

      3,685

      80 points yesterday

      Profile
      Rank: Genius
    2. jkr

      3,000

      0 points yesterday

      Profile
      Rank: Savant
    3. ShareefHuddle

      2,000

      0 points yesterday

      Profile
      Rank: Master
    4. sarabande

      2,000

      0 points yesterday

      Profile
      Rank: Sage
    5. TommySzalapski

      1,600

      0 points yesterday

      Profile
      Rank: Genius
    6. Zoppo

      1,500

      0 points yesterday

      Profile
      Rank: Genius
    7. sstairs

      400

      0 points yesterday

      Profile
    8. pgnatyuk

      400

      20 points yesterday

      Profile
      Rank: Genius

    Hall Of Fame