C#: ListView – Dynamically Sizing Columns to Fill Whole Control


The DataGridView provides many options when it comes to defining the width of each column. My personal favorite is to set the AutoSizeMode property to Fill and then define a FillWeight for each column. Doing such will automatically resize each column according to its FillWeight property no matter how the user resizes the form. I was looking for such a property when using the ListView control in detail mode but apparently there is no such feature.

According to the MSDN documentation here, setting the column header Width property to -1 will automatically adjust the column width to the longest item in the column and setting the column header Width property to -2 will automatically adjust the width of the column to the size of the column heading. But, there is no Fill setting that will automatically adjust each column proportionality like is done in the DataGridView.

The simplest solution here is to just use a DataGridView and drop the ListView entirely, but in my situation, the ListView rendered the data much more to my liking that I could get the DataGridView to do. Given that no such solution was available, I decided to write one up myself using the ListView.SizeChanged event. The ListView.SizeChanged event is fired every time the size of the ListView changes, obviously, and thus with a bit of logic in this event we can programmatically emulate the auto sizing of the columns as done in the DataGridView.

Disclaimer: The best way to implement this is to create a custom control derived from the ListView class that contains the following logic. For simplicity, I have just used the stock ListView control and the SizeChanged event.

At design time when you define the ListView columns, enter the desired FillWeight of the column as an integer in the Tag property. Originally I tried just using the Width property but ran into problems recovering the correct value when the calculated width went to zero. If we set the FillWeight in the Tag property, it will stay constant for the life of the control (if you can ensure it isn’t changed at runtime). Again, ideally if you created a custom control, you could create a custom FillWeight property instead of using the Tag property,

Then, place the code that follows in the SizeChanged event. This code calculates the percentage of space each column should occupy and then sets the width of the column appropriately depending on the visible space the ListView control occupies. When resizing the ListView, this code is called multiple times. In an effort to reduce the number of calculations performed, we use the Resizing flag.


private bool Resizing = false;

private void ListView_SizeChanged(object sender, EventArgs e)
{
    // Don't allow overlapping of SizeChanged calls
    if (!Resizing)
    {
        // Set the resizing flag
        Resizing = true;

        ListView listView = sender as ListView;
        if (listView != null)
        {                               
            float totalColumnWidth = 0;

            // Get the sum of all column tags
            for (int i = 0; i < listView.Columns.Count; i++)
                totalColumnWidth += Convert.ToInt32(listView.Columns[i].Tag);

            // Calculate the percentage of space each column should 
            // occupy in reference to the other columns and then set the 
            // width of the column to that percentage of the visible space.
            for (int i = 0; i < listView.Columns.Count; i++)
            {
                float colPercentage = (Convert.ToInt32(listView.Columns[i].Tag) / totalColumnWidth);
                listView.Columns[i].Width = (int)(colPercentage * listView.ClientRectangle.Width);             
            }
        }
    }

    // Clear the resizing flag
    Resizing = false;
}

In the example below, there are three columns with FillWeights of 1, 2, and 1 respectively. Thus, the first and third column should occupy 25% of the ListView’s visible space and the second should occupy 50%. The results of the automatic sizing are shown for various form widths below.

Note that if you have multiple ListView controls on a form for which you want to use this logic, you can just hook up each SizeChanged event with the same code as shown below. For this reason, I casted the sender object to a ListView instead of just referencing it directly.

public Form1()
{
    InitializeComponent();
    this.listView1.SizeChanged += new EventHandler(ListView_SizeChanged);
    this.listView2.SizeChanged += new EventHandler(ListView_SizeChanged);
    this.listView3.SizeChanged += new EventHandler(ListView_SizeChanged);    
}     
Advertisements