AutoSize a .NET ListView Control

Published:
I was recently working on a project that uses a list view control. I had to dynamically add items based on a configuration file so I could not set the height of the list view at design time unless I wanted scroll bars. For my application, I wasn't expecting a large amount of items. Therefore, I wanted to always size the list view to just the right amount so that it would not show a vertical scroll bar. Unfortunately, there is no autosize property of the list view control. I was able to write a series of extension methods that let you auto size the list view control. This could be done by inheriting from the list view control instead. I chose the extension method model for this article so that you don't have to add a control to your tool box.

For maximum control, I separated the height and width functionality and then included an all encompassing method that calls both. For added flexibility, I allow you to set the number of items you want to display. To autosize it fully, pass in the number of items that the list view control contains.


1. Create the Extension Method Framework


using System;
                      using System.Windows.Forms;
                      namespace ListViewExtensionMethods
                      {
                         public static class ListViewExtensions
                         {
                            public static void AutoSizeControlHeight ( this ListView lv, int maxItemsToShow, bool adjustForHorizontalScrollBar)
                            {
                            }
                            public static void AutoSizeControlWidth ( this ListView lv, bool adjustForVerticalScrollBar )
                            {
                            }
                            public static void AutoSizeControl ( this ListView lv, int maxItemsToShow, bool adjustForHorizontalScrollBar, bool adjustForVerticalScrollBar )
                            {
                            }
                         }
                      }

Open in new window


As you can see, we've created 3 functions, one for each dimension and then one that brings it all together. We'll first work with the Height function

Within the height method

2. Declare our variables


int positionBefore = 0;
                      int positionAfter = 0;

Open in new window


3. Make sure that the list view has items.


if (lv.Items.Count == 0)
                          return;

Open in new window


4. Scale down the max # of items if needed


Ensure the max number of items to show is not greater than the number of items in the control
if (maxItemsToShow > lv.Items.Count)
                          maxItemsToShow = lv.Items.Count);

Open in new window


Now we are ready to start sizing the control dynamically.

5. Initialize the size of the control


Set the size of the listview to 1 so that it can grow from there
lv.Height = 1;

Open in new window


6. Grow the control in a while loop


The concept for determining the height is fairly straightforward. We scroll the first item into view and then the nth item into view over and over again. As we do this, we grow the control by one pixel. While doing that, we record the nth items position when the first item is in view and then when the nth item is in view. Once those two numbers are the same, we have grown enough.

We can wrap our growing functionality in a while loop. The while loop will run as long as the two positions are different. To get the loop to run once, we need to make sure the two numbers are different to start. This is ok because the values are immediately overwritten once in the loop. We also need to account for the maximum size property. If this has been set, we want to stop growing once that size has been reached so we don't end up in a never ending loop (the two positions might never be the same).
positionBefore = -1;
                      while (positionBefore != positionAfter && (lv.MaximumSize.Height == 0 || lv.Height < lv.MaximumSize.Height))
                      {
                      }

Open in new window


Within the while loop:

7. Grow the control


We first want to grow the control by a pixel. Even though this will occur before any real testing is done, this is ok because we have set the size so small that it won't matter.
lv.Height += 1;

Open in new window


8. Determine the before position


We then need to determine the Y coordinate of the nth ListViewItem when the first item is in view. To accomplish this, we can use the EnsureVisible method of the ListViewItem. If the list view happens to be scrolled, ensuring this item is visible will scroll the list view back to the very top. Then we need to record the position of the nth item for later comparison. This should go without saying, but since all the collections have a zero based index, we need to subtract one from the maxItemsToShow
lv.Items[0].EnsureVisible();
                      positionBefore = lv.Items[maxItemsToShow - 1].Position.Y;

Open in new window


9. Determine the after position


Now we need to determine the position of the nth item after it has been scrolled into view.
lv.Items[maxItemsToShow - 1].EnsureVisible();
                      positionAfter = lv.Items[maxItemsToShow - 1].Position.Y;

Open in new window


while (positionBefore != positionAfter && (lv.MaximumSize.Height == 0 || lv.Height < lv.MaximumSize.Height))
                         {
                            lv.Height += 1;
                            lv.Items[0].EnsureVisible();
                            positionBefore = lv.Items[maxItemsToShow - 1].Position.Y;
                            lv.Items[maxItemsToShow - 1].EnsureVisible();
                            positionAfter = lv.Items[maxItemsToShow - 1].Position.Y;
                         }

Open in new window


10. Adjust for the scrollbar


Finally, we need to adjust for the width of the horizontal scroll bar. Otherwise the last item will be cut off.
if (adjustForHorizontalScrollBar)
                          lv.Height += SystemInformation.HorizontalScrollBarHeight;

Open in new window


Within the width method
We are now ready to resize the width of the control to match the content. This is much simpler.

1. Reset the width


Reset the width of the list view to zero so we can grow it as needed.
lv.Width = 0;

Open in new window


2. Grow the control


We can now loop through each column and add each column width to the control.
for (int i = 0; i < lv.Columns.Count; i++)
                          lv.Width += lv.Columns[i].Width;

Open in new window


3. Adjust for the scroll bar


Finally, we need to adjust for the vertical scroll bar, otherwise we'll cut the last column off and the horizontal scroll bar will be visible
if (adjustForVerticalScrollBar)
                          lv.Width += SystemInformation.VerticalScrollBarWidth;

Open in new window


The last thing we need to do is call these two methods in the AutoSizeControl method so that it does both. You may see the attached code for that example.

Full Code
using System;
                      using System.Windows.Forms;
                      
                      namespace ListViewExtensionMethods
                      {
                          public static class ListViewExtensions
                          {
                              public static void AutoSizeControlHeight ( this ListView lv, int maxItemsToShow, bool adjustForHorizontalScrollBar )
                              {
                                  int positionBefore = 0;
                                  int positionAfter = 0;
                      
                                  if (lv.Items.Count == 0)
                                      return;
                      
                                  if (maxItemsToShow > lv.Items.Count)
                                      maxItemsToShow = lv.Items.Count;
                      
                                  lv.Height = 50;
                      
                                  while (positionBefore != positionAfter && (lv.MaximumSize.Height == 0 || lv.Height < lv.MaximumSize.Height))
                                  {
                                      lv.Height += 1;
                      
                                      lv.Items[0].EnsureVisible();
                                      positionBefore = lv.Items[maxItemsToShow - 1].Position.Y;
                      
                                      lv.Items[maxItemsToShow - 1].EnsureVisible();
                                      positionAfter = lv.Items[maxItemsToShow - 1].Position.Y;
                                  }
                      
                                  if (adjustForHorizontalScrollBar)
                                      lv.Height += SystemInformation.HorizontalScrollBarHeight;
                              }
                      
                              public static void AutoSizeControlWidth ( this ListView lv, bool adjustForVerticalScrollBar)
                              {
                                  lv.Width = 0;
                                  for (int i = 0; i < lv.Columns.Count; i++)
                                      lv.Width += lv.Columns[i].Width;
                      
                                  if (adjustForVerticalScrollBar)
                                      lv.Width += SystemInformation.VerticalScrollBarWidth;
                              }
                      
                              public static void AutoSizeControlColumnWidth ( this ListView lv, bool adjustForVerticalScrollBar)
                              {
                                  int resizeByContent = 0;
                                  int resizeByHeader = 0;
                      
                                  for (int i = 0; i < lv.Columns.Count; i++)
                                  {
                                      lv.Columns[i].AutoResize(ColumnHeaderAutoResizeStyle.ColumnContent);
                                      resizeByContent = lv.Columns[i].Width;
                      
                                      lv.Columns[i].AutoResize(ColumnHeaderAutoResizeStyle.HeaderSize);
                                      resizeByHeader = lv.Columns[i].Width;
                      
                                      lv.Columns[i].Width = (resizeByHeader > resizeByContent) ? resizeByHeader : resizeByContent;
                                  }
                              }
                      
                              public static void AutoSizeControl ( this ListView lv, int maxItemsToShow, bool adjustForHorizontalScrollBar, bool adjustForVerticalScrollBar )
                              {
                                  lv.AutoSizeControlColumnWidth();
                                  lv.AutoSizeControlWidth(adjustForVerticalScrollBar);
                                  lv.AutoSizeControlHeight(maxItemsToShow,adjustForHorizontalScrollBar);
                              }
                          }
                      }

Open in new window


Placing limits on size
There is the potential that if you have too many items, the control might grow too wide. The ListView provides us with a maximum size property. If you set this, then the control will only grow up to that point and not further. In the case of adjusting the height, we had to make sure we didn't get stuck in a never ending loop. The width however does not need such a check because it is only looping through the columns, which has a finite number of iterations. When attempting to grow past the maximum size, you will not see an error. Instead, the control will just set the dimension to the maximum size.

Ending Note
I also like to auto adjust the actual column widths. That was outside of the scope of this article, but in the code I've included a function that does that as well. My AutoSizeControl also calls this function automatically but this is just my preference.
1
12,534 Views

Comments (1)

CERTIFIED EXPERT
Most Valuable Expert 2012
Top Expert 2008

Commented:
For owner-drawn ListView controls this code makes sense, but for details view with fixed-height items,  you should be able to use get the first item height, and calculate this way:

var itemHeight = listView.Items.OfType<ListViewItem>().First().Bounds.Height;
listView.Height = (itemCount + 1) * itemHeight + 6;

Open in new window


The only caveat is that the column header height is slightly taller than the other items.  You might be able to determine this, but for now I just added a fixed offset of 6 pixels.

Have a question about something in this article? You can receive help directly from the article author. Sign up for a free trial to get started.