Introduction
Did you ever wonder how to make a list control automatically resize the last column to fit the entire list control without a horizontal scrollbar. Did you ever want to make it do that as you resize your list control.
Did you actually get it done but ended up with something like this?
There is hope, keep on reading.
Background
A while back, I was answering a question on a forum, the person asking the question had created a list control that would resize the last column as he resized his view. But he ran into a problem. When the first item in the list control was not the first visible item and the user resized the view to where a vertical scrollbar was no longer needed, the list control would end up with an empty entry at the top. Of course there was no item there, but it looked like a blank item. He said that a version of Windows Media player had the same problem, but I can't remember the version number. Anyway I didn't believe him until I tried it for myself, and saw the bug first hand.
Let's start writing some code for the resizing, find the problem along the way and fix it.
Using the code
The first thing we need to do is to create a dialog base application. Delete everything on the dialog box. Add a list control to it, make it report type, give it an ID, and attach a variable to it, let's call it m_cList
. Don't worry too much about its size or position; we will take care of that in the code. Don't forget to change the dialog's border style to Resizing while you are in the resource editor. Also in order to reduce flickering set the WS_CLIPCHILDREN
flag for the dialog box.
Once the above steps are done, we can start coding. Let's add some columns and items to the list control. We are going to do that in OnInitDialog
of the main dialog box.
BOOL CListControlIssueDlg::OnInitDialog()
{
CDialog::OnInitDialog();
SetIcon(m_hIcon, TRUE); SetIcon(m_hIcon, FALSE);
m_cList.InsertColumn(0, _T("Column1"),LVCFMT_LEFT,100);
m_cList.InsertColumn(1, _T("Column2"),LVCFMT_LEFT,100);
m_cList.InsertColumn(2, _T("Column3"),LVCFMT_LEFT,100);
for ( int i = 0 ; i < 10 ; i++ )
{
for ( int j = 0 ; j < 3 ; j++ )
{
CString s;
s.Format(_T("Item %d (%d)") , i, j);
if ( j == 0 )
{
m_cList.InsertItem(i, s);
}
else
{
m_cList.SetItemText(i, j, s);
}
}
}
CRect Rect;
GetClientRect(&Rect);
m_cList.MoveWindow( 5, 5, Rect.Width()-10, Rect.Height()-10);
return TRUE;
}
Now let's also add a handler for the WM_SIZE
message so that we can resize the list control to fit the dialog as the dialog is being resized. This should look something like this:
void CListControlIssueDlg::OnSize(UINT nType, int cx, int cy)
{
CDialog::OnSize(nType, cx, cy);
if ( IsWindow( m_cList.m_hWnd ) )
{
m_cList.MoveWindow( 5, 5, cx-10, cy-10);
}
}
Now when you run the program you should see a list control with 10 items, 3 columns at 100 pixels wide, which gets resized as you resize the dialog. Easy so far.
Now let's add the code to automatically change the size of the column. For that we need to create a class that inherits from CListCtrl
, let call it CMyListCtrl
. After that's done let go ahead and catch the WM_SIZE
message for the List control. Also add a private method named AutoAdjustColumns
which looks like this:
(I have to thank Chris Radke for this code.)
void CMyListCtrl::AutoSizeColumn()
{
SetColumnWidth(GetHeaderCtrl()->GetItemCount()-1, LVSCW_AUTOSIZE_USEHEADER);
}
The code above is pretty straight forward. It simply loops through all the columns (except the last one) and subtract their width from the width of the control. And sets the last column's width to the remaining value. (Note: In order to keep the article from getting too complicated this code does not handle minimum column size. However this situation should be handled).
Now in order to resize the columns as the list control is being resized, you would naturally want to call AutoSizeColumn
from the OnSize
handler of the list control. Go ahead and do that.
void CMyListCtrl::OnSize(UINT nType, int cx, int cy)
{
CListCtrl::OnSize(nType, cx, cy);
AutoSizeColumn();
}
Franc Morales suggested that the columns should autoresize after the user drags the column headers. In order to do that we need to handle the HDN_ENDTRACK
message and call AutoSizeColumn
.
void CMyListCtrl::OnHdnEndtrack(NMHDR *pNMHDR, LRESULT *pResult)
{
AutoSizeColumn();
*pResult = 1;
}
Don't forget to change your m_cList
type from CListCtrl
to CMyListCtrl
. Then run the program, scroll the list control to the bottom, and then resize the dialog to a point where a vertical scrollbar is no longer needed. You will probably see this:
Well the fix for that in my opinion is pretty interesting. If the call to AutoSizeColumn
is made directly from OnSize
then this is the result, but if you were to call it after OnSize
had returned everything works fine. So to do this we are going to change OnSize
's implementation to use PostMessage
to post a message to itself and the message handler for that message is going to call AutoSizeColumn
. So let's change things to make them look like this:
#define WM_RESIZEME WM_APP+1
BEGIN_MESSAGE_MAP(CMyListCtrl, CListCtrl)
ON_WM_SIZE()
ON_MESSAGE(WM_RESIZEME,OnResizeMe)
END_MESSAGE_MAP()
void CMyListCtrl::OnSize(UINT nType, int cx, int cy)
{
CListCtrl::OnSize(nType, cx, cy);
PostMessage(WM_RESIZEME);
}
LRESULT CMyListCtrl::OnResizeMe(WPARAM,LPARAM)
{
AutoSizeColumn();
return 1;
}
Now run the program and resize the dialog in the same way you did before. Things should be fine now.
Have Fun!