Friday, September 10, 2010
 
   
 
Welcome to my site

First let me say thanks for stopping by my site. My name is David Hanson-Graville and I am a IT consultant working in the UK. Let me make it clear, I am passionate about technology and specifically .net and its various forms. I've programmed in a range of langages, but I can say, I am now at my happiest when coding with c#. I hope my blog is an enjoyable & educational read and please feel free to email me at David.Hanson@OnTheBlog.net if you have any questions. 

WPF: How to stretch columns in a ListView Minimize
Location: BlogsOnTheBlog    
Posted by: David Hanson Sun, 13 Apr 2008 21:21:11 GMT
The ListView control in WPF is a powerful option when trying to present tabular data to users. It supports many of the common behaviours found in grid controls as well as the full WFP templating architecture we have all come to love. Below is a simple of example of a ListView which has been bound to a collection of strings. The example uses a template GridVewColumn and a 3 standard GridViewColumn’s to display the data.
 

And here is the associated XAML.
 
<Window x:Class="WPFSamples.ListViewDemo"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:s="clr-namespace:System;assembly=mscorlib"
    xmlns:Extensions="clr-namespace:Demo.Extension.Properties"
    Title="ListViewDemo" Height="353" Width="714">
    <Grid>
        <Grid.Resources>
           
            <x:Array Type="{x:Type s:String}" x:Key="ourData">
                <s:String>Scott Gus:String>
                <s:String>Tim Sneaths:String>
                <s:String>Rockford Lhotkas:String>
                <s:String>Robert Scobles:String>
                <s:String>Ayende Rahiens:String>
            x:Array>
        Grid.Resources>
       
        <ListView Name="myListView"
                  Background="LightBlue"
                  ItemsSource="{StaticResource ourData}">
            <ListView.View>
                <GridView>
                   
                    <GridViewColumn Header="Names" >
                        <GridViewColumn.CellTemplate>
                            <DataTemplate>
                                <Border>
                                    <TextBlock Text="{Binding .}"
                                               Foreground="Black"
                                               FontWeight="Bold" />
                                Border>
                            DataTemplate>
                        GridViewColumn.CellTemplate>
                    GridViewColumn>
                   
                    <GridViewColumn Header="Length"
                                    Width="50"  DisplayMemberBinding="{Binding Length}" />
                    <GridViewColumn Header="Names"  DisplayMemberBinding="{Binding}" />
                    <GridViewColumn Header="Length"
                                    Width="50" DisplayMemberBinding="{Binding Length}" />
                GridView>
            ListView.View>
        ListView>
    Grid>
Window>
 
 
As you can see, I have highlighted the column’s in our GridView that have explicit width settings.  Now looking at what we get when we run this sample you can see that the default behaviour of the GridViewColumn is to set the width to AUTO thus fitting the content being displayed.  However, there often situations where you would rather the GridViewColum’s stretch to fit the remaining available space of its container. To achieve this you would imagine you could set the width of the GridViewColumn to “*”. Unfortunately this is not supported and you will be prompted with the following message.
 
<GridViewColumn Header="Names" Width="*">
    <GridViewColumn.CellTemplate>
        <DataTemplate>
            <Border>
                <TextBlock Text="{Binding .}"
                           Foreground="Black"
                           FontWeight="Bold" />
            Border>
        DataTemplate>
    GridViewColumn.CellTemplate>
GridViewColumn>
 
Cannot convert string '*' in attribute 'Width' to object of type 'System.Double'. '*'
 
I find this inconsistent use of the * in WPF frustrating, I am sure the WPF team have a valid reason for why this is not supported but it just feels like it should be. As the * is not supported on a GridViewColumn’s width I tried many other way to get our GridViewColumn to stretch, all to no avail.
 
<ListView HorizontalAlignment="Stretch" ... Makes no difference.
<ListView HorizontalContentAlignment="Stretch" ... Makes no difference.
<GridViewColumn HorizontalAlignment="Stretch" ... Not supported.
<GridViewColumn HorizontalContentAlignment="Stretch" ... Not supported.
 
Having played around for a while and running a few google searches it became apparent that a number of people have come across this issue, particularly when trying to stretch the last column to the remaining space. As a result, I decided to solve the problem using the attached property approach. This provides a simple way if implementing the stretch column behaviour on ListView’s. As I will outline, you change the default behaviour of the GridView setting the width to Auto so that be default all columns will stretch. Below is the XAML that will allow us to do just that.
 
<ListView Name="myListView"
          Background="LightBlue"
          ItemsSource="{StaticResource ourData}"
          Extensions:ListViewColumns.Stretch="true">
 
 
For us to achieve this level of simplicity in our XAML we have to place the complexity elsewhere.....in our attached property infact. The first stage when creating a new WPF attached property is to put the appropriate infrastructure in place. You will find may examples of how to create attached properties in WPF so I won’t bore you with the details here. Below is base code for our attached property.
 
using System;
using System.Collections.Generic;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
 
namespace Demo.Extension.Properties
{
    ///
    /// ListViewColumnStretch
    ///
    public class ListViewColumns : DependencyObject
    {
 
        ///
        /// IsStretched Dependancy property which can be attached to gridview columns.
        ///
        public static readonly DependencyProperty StretchProperty =
            DependencyProperty.RegisterAttached("Stretch",
            typeof(bool),
            typeof(ListViewColumns),
            new UIPropertyMetadata(true,null,OnCoerceStretch));
 
 
        ///
        /// Gets the stretch.
        ///
        /// The obj.
        ///
        public static bool GetStretch(DependencyObject obj)
        {
            return (bool)obj.GetValue(StretchProperty);
        }
 
        ///
        /// Sets the stretch.
        ///
        /// The obj.
        /// if set to true [value].
        public static void SetStretch(DependencyObject obj, bool value)
        {
            obj.SetValue(StretchProperty, value);
        }
 
        ///
        /// Called when [coerce stretch].
        ///
        ///If this callback seems unfamilar then please read
        /// the great blog post by Paul jackson found here.
        /// http://compilewith.net/2007/08/wpf-dependency-properties.html
        /// The source.
        /// The value.
        ///
        public static object OnCoerceStretch(DependencyObject source, object value)
        {
            ListView lv = (source as ListView);
 
            //Ensure we dont have an invalid dependancy object of type ListView.
            if (lv == null)
                throw new ArgumentException("This property may only be used on ListViews");
 
            //Setup our event handlers for this list view.
            lv.Loaded += new RoutedEventHandler(lv_Loaded);
            lv.SizeChanged += new SizeChangedEventHandler(lv_SizeChanged);
            return value;
        }
 
    }
}
 
 
Firstly, notice that dependency property inherits from DependencyObject, just about most elements in WPF derive from this type and attached properties are no exception. The remainder of the code above is fairly self explanatory, we have WPF setters and getters and we have registered the property name (DependencyProperty.RegisterAttached("Stretch") as part of this work. The only part that may not be recognised is the static method OnCoerceStretch. This method is actually just a callback that will be called prior to our property being set. It allows us to intercept the setter in order to change the the state of the value if so require.
 
public static object OnCoerceStretch(DependencyObject source, object value)
{
    ListView lv = (source as ListView);
 
    //Ensure we dont have an invalid dependancy object of type ListView.
    if (lv == null)
        throw new ArgumentException("This property may only be used on ListViews");
 
    //Setup our event handlers for this list view.
    lv.Loaded += new RoutedEventHandler(lv_Loaded);
    lv.SizeChanged += new SizeChangedEventHandler(lv_SizeChanged);
    return value;
}
 
In our coerce method above you can see we receive the dependency object the property has been attached to (in our case a listview) and then the value being set. The only this we really need to do in our method is to attach a couple of event handlers which will notify us later on about state changes in our listview.
 
    //Setup our event handlers for this list view.
    lv.Loaded += new RoutedEventHandler(lv_Loaded);
    lv.SizeChanged += new SizeChangedEventHandler(lv_SizeChanged);
 
The loaded handler is setup as we can only gain access to our listview’s internal GridView and columns once the listview has fully loaded. The SizeChanged event handler will be used to monitor when the ListView’s sizes changes, when this happens we want to be able to perform a calculation so that columns with no width having been set can stretch to fill the available space. Implementing the dependency property above allows us to use the following XAML syntax. Extensions:ListViewColumns.Stretch="true". Don’t forget to import the namespace into your XAML like this xmlns:Extensions="clr-namespace:Demo.Extension.Properties".
 
Back to our dependency property, the event handlers are once again fairly self explanatory, each event handler calls into a method called SetColumnWidths. As the SizeChanged event is called prior to the loaded event a simple check is made to ensure SetColumnWidths is not called at this point.
 
///
/// Handles the SizeChanged event of the lv control.
///
/// The source of the event.
/// The instance containing the event data.
private static void lv_SizeChanged(object sender, SizeChangedEventArgs e)
{
    ListView lv = (sender as ListView);
    if (lv.IsLoaded)
    {
        //Set our initial widths.
        SetColumnWidths(lv);
    }
}
 
///
/// Handles the Loaded event of the lv control.
///
/// The source of the event.
/// The instance containing the event data.
private static void lv_Loaded(object sender, RoutedEventArgs e)
{
    ListView lv = (sender as ListView);
    //Set our initial widths.
    SetColumnWidths(lv);
}
 
 
The final part of the puzzle is too look at our method SetColumnsWidth. This method does the majority of the work in our dependency property, it identifies columns in our GridView which do not have a width setting and then based on the available space remaining divides to those columns. The only interesting part is that the columns identified as having no width are stored in the ListView’s TAG property for reuse later.
 
///
/// Sets the column widths.
///
private static void SetColumnWidths(ListView listView)
{
    //Pull the stretch columns fromt the tag property.
    List<GridViewColumn> columns = (listView.Tag as List<GridViewColumn>);
    double specifiedWidth = 0;
    GridView gridView = listView.View as GridView;
    if (gridView != null)
    {
        if (columns == null)
        {
            //Instance if its our first run.
            columns = new List<GridViewColumn>();
            // Get all columns with no width having been set.
            foreach (GridViewColumn column in gridView.Columns)
            {
                if (!(column.Width >= 0))
                    columns.Add(column);
                else specifiedWidth += column.ActualWidth;
            }
        }
        else
        {
            // Get all columns with no width having been set.
            foreach (GridViewColumn column in gridView.Columns)
                if (!columns.Contains(column))
                    specifiedWidth += column.ActualWidth;
        }
 
        // Allocate remaining space equally.
        foreach (GridViewColumn column in columns)
        {
            double newWidth = (listView.ActualWidth - specifiedWidth) / columns.Count;
            if (newWidth >= 0) column.Width = newWidth - 10;
        }
 
        //Store the columns in the TAG property for later use.
        listView.Tag = columns;
    }
}
 
And that’s pretty much it..... were done! If we run the sample now it will look like this. You can see the two [Name] columns are stretching to fill the available space. 
 
 
 
 
 
 Sourcecode for this blog can be downloaded here.
 
 
 
 
 
 
 
Permalink |  Trackback

Comments (11)   Add Comment
Re: WPF: How to stretch columns in a ListView    By Paul on Tue, 15 Apr 2008 18:54:28 GMT
This is a very timely for me. I only change ListView to GridViewHeaderRowPresenter because I use GridViewHeaderRowPresenter object without ListView.<br>Thank you very match.

Re: WPF: How to stretch columns in a ListView    By host on Tue, 15 Apr 2008 18:45:52 GMT
Glad it helped. :-)

Re: WPF: How to stretch columns in a ListView    By Simeon on Fri, 02 May 2008 10:57:05 GMT
Thank you! It works fine!

Re: WPF: How to stretch columns in a ListView    By host on Fri, 02 May 2008 10:58:05 GMT
Welcome.

Re: WPF: How to stretch columns in a ListView    By Phil on Mon, 14 Jul 2008 20:32:11 GMT
This is a little old, but i have a problem with the code where if you turn the stretch on, it no longer allows users to resize columns. If they try, it'll just snap right back to a stretch. Is there a way around this?

Re: WPF: How to stretch columns in a ListView    By John Melville on Sun, 30 Nov 2008 22:33:04 GMT
Thanks a lot. This post was really helpful. Didn't end up using you code (I'm actually using a TreeListView, out of the samples) but your clear code made extracting the principles and recoding them trivial.<br>

Re: WPF: How to stretch columns in a ListView    By Tim Orr on Tue, 26 Jan 2010 10:12:57 GMT
Great tutorial. FYI -- the line of code below will throw an exception the result is < 0:<br> <br>if (newWidth >= 0) column.Width = newWidth - 10;<br><br>Instead, it probably makes sense to do:<br><br><br>double newWidth = (listView.ActualWidth - specifiedWidth) / columns.Count - 10;<br><br>Thanks again! Great tutorial!

Re: WPF: How to stretch columns in a ListView    By stean on Tue, 26 Jan 2010 10:12:48 GMT
The problem ist that you are setting the ListView.Tag property. If anyone overwrites it it won't work

Re: WPF: How to stretch columns in a ListView    By John on Tue, 26 Jan 2010 10:13:00 GMT
Thank YOU!!!!!!!

Re: WPF: How to stretch columns in a ListView    By Kurt on Thu, 04 Feb 2010 11:01:05 GMT
Works unless vertical scroll bar is enabled. i think you need to check for the presence of the vertical scroll bar in order to factor in the width.

Re: WPF: How to stretch columns in a ListView    By Berci on Fri, 26 Feb 2010 09:54:40 GMT
Dealing with vertical scroll bar can be implemented like this:<br>look for the code calculating column widths (method SetColumnWidths()), and change calculating newWidth as follows (3 new line and one modified):<br><br>Decorator border = VisualTreeHelper.GetChild(listView, 0) as Decorator;<br>ScrollViewer scrollViewer = border.Child as ScrollViewer;<br>double scrollBarWidth = scrollViewer.ScrollableHeight > 0 ? SystemParameters.VerticalScrollBarWidth : 0;<br>double newWidth = (listView.ActualWidth - scrollBarWidth - specifiedWidth) / columns.Count;<br><br>Hope this helps.


Your name:
Title:
Comment:
Security Code
Enter the code shown above in the box below
Add Comment   Cancel 
Tweets Minimize
Twitter / LordHanson
  1. LordHanson: Experienced .net dev in sydney for next 5 months if anyone needs me. CV on request just tweet me.

    Published Sun, 29 Aug 2010 05:41:05 +0000 by
  2. LordHanson: Flash on iPad....nice. http://www.tipb.com/2010/07/04/frash-android-flash-ported-ipad/

    Published Sun, 04 Jul 2010 22:07:55 +0000 by
  3. LordHanson: Anyone noticed that when typing on your iPhone it sounds like your holding a gieger counter?

    Published Sun, 04 Jul 2010 22:05:28 +0000 by
  4. LordHanson: Missing wacko's music... What's happened to the album he was working on before he died?

    Published Fri, 25 Jun 2010 23:01:45 +0000 by
  5. LordHanson: New version of Connectify cannot recognise my active Internet connection! Had to roll back to previous version! #fail

    Published Fri, 25 Jun 2010 22:54:54 +0000 by
  6. LordHanson: vuvuzela blowing spoils the world cup! Fact!

    Published Mon, 14 Jun 2010 05:08:43 +0000 by
  7. LordHanson: About http://www.theaustralian.com.au/business/news/us-competition-regulators-to-investigate-apple/story-e6frg90x-1225878779986

    Published Mon, 14 Jun 2010 00:04:45 +0000 by
  8. LordHanson: In the camper van and a storm is coming.....How exciting.

    Published Sun, 25 Apr 2010 04:39:48 +0000 by
  9. LordHanson: My vaio p is doing well while travelling. 3g Internet, HD movies, digital tv, photo editing, wifi router for iPods and much more. Love it

    Published Wed, 10 Mar 2010 10:29:19 +0000 by
  10. LordHanson: Ok so I need to stay techie while away from a computer for a year. Anyone got any ideas.

    Published Mon, 22 Feb 2010 12:31:09 +0000 by
  11. LordHanson: Sitting in YHA Glebe Sydney waiting for the movie night to start

    Published Thu, 18 Feb 2010 08:13:10 +0000 by
  12. LordHanson: Madness today. We only booked our return tickets to bangkok on the wrong day! Luckily we managed to change them!

    Published Wed, 10 Feb 2010 15:54:37 +0000 by
  13. LordHanson: HTML5 the future? http://bit.ly/6yf9Bu

    Published Tue, 09 Feb 2010 13:43:48 +0000 by
  14. LordHanson: Last night in Bangkok! Good fun!

    Published Thu, 04 Feb 2010 18:09:38 +0000 by
  15. LordHanson: @trampussandal Dad? lol

    Published Thu, 04 Feb 2010 05:26:39 +0000 by
  16. LordHanson: Im sitting in a coffee shop in my home town of epsom thinking... Man the day has finally arrived. I can feel the stress lifting.

    Published Mon, 01 Feb 2010 09:05:13 +0000 by
  17. LordHanson: So what excuse will apple use to not allow flash or silverlight to run on the ipad this time I wonder.

    Published Fri, 29 Jan 2010 19:07:46 +0000 by
  18. LordHanson: Yay just manage to upgrade from vista ultimate to windows 7 enterprise by using the registry hack trick. No reinstalls.

    Published Wed, 27 Jan 2010 07:18:07 +0000 by
  19. LordHanson: @swhelband Sure am...http://bit.ly/aZ6Xvd

    Published Tue, 26 Jan 2010 19:05:18 +0000 by
  20. LordHanson: I finished work today in prep for travelling. I must admit as i left the office i felt a little emotional. Sign of a good job with great ...

    Published Tue, 26 Jan 2010 17:48:49 +0000 by
Print  
Archive Minimize
Print  
Contact me Minimize
Print  
Microsoft Certs Minimize







Print  
Silverlight News Minimize
Silverlight - Google News
Print