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);    
}     
About these ads

12 Responses to “C#: ListView – Dynamically Sizing Columns to Fill Whole Control”

  1. steve Says:

    Excellent. Just what I needed and well executed.

    Thanks.

  2. Mattias Says:

    Perfect! Just what I needed at the moment!

  3. David Says:

    Thanks Nick! Not 100% for sure but I’m 99.99% that you’re the ONLY person who’s actually provided the solution to the problem that was at hand. EVERY thread and/or post I’ve see so far DON’T address how to resize listview control dynamically according to winForm size! Thx again and take care!

    -Dee

  4. David Says:

    can someone give an example of implementing this code within a custom listview control?

  5. Hani Says:

    Thanks alot !! The code was what I needed.

  6. CodeMan Says:

    Thanks for the post, Nick. This works fine until the ListView is resized vertically when: 1) a vertical scrollbar is present and 2) the scrollbar is scrolled at least some of the way down. I have my custom ListView inside of a SplitContainer and when the horizontal split bar is dragged to expand the ListView you get empty rows at the top of the ListView and it gets worse: a subsequent click on an item will make all the items disappear (they will not paint). I’m not doing any other processing. Very annoying. Refresh() nor Invalidate() resolve the painting issue. Thoughts?

    • Nick Olsen Says:

      No solution off the top of my head. Do you have a sample project I could take a look at?

  7. Ahmeti Says:

    Great solution

  8. Ahmet Murati Says:

    I just added two lines of code and then the Taks on UI was done perfectly

    // Don’t allow overlapping of SizeChanged calls
    if (!Resizing)
    {
    // Set the resizing flag
    Resizing = true;

    ListView listView = sender as ListView;
    listView.BeginUpdate();

    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);
    }
    }
    listView.EndUpdate();
    }

    // Clear the resizing flag
    Resizing = false;

  9. miki Says:

    I PERFECTED THIS :)
    to the point where u dont need to use tags.. or use a constant.. it will change the ratio only when user change it and when the window size change it will change with it :)

    private void Form1_Load(object sender, EventArgs e)
    {
    int totalW = listView1.Width;
    for (int i = 0; i < listView1.Columns.Count – 1; i++)
    {
    listView1.Columns[i].Tag = (decimal)listView1.Columns[i].Width / (decimal)totalW;
    }
    }
    private void Form1_Resize(object sender, EventArgs e)
    {
    int totalW = listView1.Width;
    for (int i = 0; i < listView1.Columns.Count – 1; i++)
    {
    listView1.Columns[i].Width = Convert.ToInt32(totalW * (decimal)listView1.Columns[i].Tag);
    }
    }

    private void listView1_ColumnWidthChanging(object sender, ColumnWidthChangingEventArgs e)
    {
    int totalW = listView1.Width;
    for (int i = 0; i < listView1.Columns.Count – 1; i++)
    {
    listView1.Columns[i].Tag = (decimal)listView1.Columns[i].Width / (decimal)totalW;
    }
    }

  10. miki Says:

    ***I PERFECTED THIS
    To make it dinamic! it will change the ratio only when user change it and when the window size change it will change with it

    private void Form1_Load(object sender, EventArgs e)
    {
    int totalW = listView1.Width;
    for (int i = 0; i < listView1.Columns.Count; i++)
    {
    listView1.Columns[i].Tag = (decimal)listView1.Columns[i].Width / (decimal)totalW;
    }
    }
    private void Form1_Resize(object sender, EventArgs e)
    {
    int totalW = listView1.Width;
    for (int i = 0; i < listView1.Columns.Count ; i++)
    {
    listView1.Columns[i].Width = Convert.ToInt32(totalW * (decimal)listView1.Columns[i].Tag);
    }
    }

    private void listView1_ColumnWidthChanging(object sender, ColumnWidthChangingEventArgs e)
    {
    int totalW = listView1.Width;
    for (int i = 0; i < listView1.Columns.Count ; i++)
    {
    listView1.Columns[i].Tag = (decimal)listView1.Columns[i].Width / (decimal)totalW;
    }
    }

  11. theja Says:

    could anyone clear this doubt ………..

    i have a list view and there are multiple columns with long text values………..
    like a column with destination file path
    it has a value like c:\users\kavya\new\coding\img1000.jpg
    something very big…………
    i want to adjust the text according to size of the column
    when the users uses the scroll bar:
    with width something very big all the data c:\users\kavya\new\coding\img1000.jpg
    should be visible
    and when he scrolls the column header to very small only the c:\img1000.jpg has to be viewd ,but the memory should have the entire path
    actualy we see something like c:\users\kavya….. i dont want this way …..
    is there any solution please any help would be appreciated…
    thanks


Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Follow

Get every new post delivered to your Inbox.

Join 66 other followers

%d bloggers like this: