91

How can I:

  • right-align the text in the ID column
  • make each of the columns auto size according to the text length of the cell with the longest visible data?

Here is the code:

<ListView Name="lstCustomers" ItemsSource="{Binding Path=Collection}">
    <ListView.View>
        <GridView>
            <GridViewColumn Header="ID" DisplayMemberBinding="{Binding Id}" Width="40"/>
            <GridViewColumn Header="First Name" DisplayMemberBinding="{Binding FirstName}" Width="100" />
            <GridViewColumn Header="Last Name" DisplayMemberBinding="{Binding LastName}"/>
        </GridView>
    </ListView.View>
</ListView>

partial answer:

Thanks Kjetil, the GridViewColumn.CellTemplate works well and the Auto Width works of course but when the ObservativeCollection "Collection" is updated with longer-than-column-width data, the column sizes do not update themselves so that is only a solution for the initial display of data:

<ListView Name="lstCustomers" ItemsSource="{Binding Path=Collection}">
    <ListView.View>
        <GridView>
            <GridViewColumn Header="ID" Width="Auto">
                <GridViewColumn.CellTemplate>
                    <DataTemplate>
                        <TextBlock Text="{Binding Id}" TextAlignment="Right" Width="40"/>
                    </DataTemplate>
                </GridViewColumn.CellTemplate>
            </GridViewColumn>
            <GridViewColumn Header="First Name" DisplayMemberBinding="{Binding FirstName}" Width="Auto" />
            <GridViewColumn Header="Last Name" DisplayMemberBinding="{Binding LastName}" Width="Auto"/>
        </GridView>
    </ListView.View>
</ListView>
akjoshi
  • 15,374
  • 13
  • 103
  • 121
Edward Tanguay
  • 189,012
  • 314
  • 712
  • 1,047
  • 2
    Did you ever find a solution to your auto-size problem? I am experiencing the same thing. – Oskar Jun 10 '09 at 11:12
  • 2
    @Oskar - virtualization of the list prevents an auto-solution. The list only knows of items currently visible and sets the size accordingly. If there are more items further down the list, it does not know of them and hence can't account for them. The ProgrammingWPF - Sells-Griffith book recommends manual column widths if you are using data binding. :( – Gishu Feb 12 '10 at 05:58
  • If using MVVM and Binding values are changing, please see @Rolf Wessels answer. – Jake Berger Oct 05 '11 at 14:27

13 Answers13

108

To make each of the columns autosize you can set Width="Auto" on the GridViewColumn.

To right-align the text in the ID column you can create a cell template using a TextBlock and set the TextAlignment. Then set the ListViewItem.HorizontalContentAlignment (using a style with a setter on the ListViewItem) to make the cell template fill the entire GridViewCell.

Maybe there is a simpler solution, but this should work.

Note: the solution requires both HorizontalContentAlignment=Stretch in Window.Resources and TextAlignment=Right in the CellTemplate.

<Window x:Class="WpfApplication6.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window1" Height="300" Width="300">
<Window.Resources>
    <Style TargetType="ListViewItem">
        <Setter Property="HorizontalContentAlignment" Value="Stretch" />
    </Style>
</Window.Resources>
<Grid>
    <ListView Name="lstCustomers" ItemsSource="{Binding Path=Collection}">
        <ListView.View>
            <GridView>
                <GridViewColumn Header="ID" Width="40">
                    <GridViewColumn.CellTemplate>
                        <DataTemplate>
                            <TextBlock Text="{Binding Id}" TextAlignment="Right" />
                        </DataTemplate>
                    </GridViewColumn.CellTemplate>
                </GridViewColumn>
                <GridViewColumn Header="First Name" DisplayMemberBinding="{Binding FirstName}" Width="Auto" />
                <GridViewColumn Header="Last Name" DisplayMemberBinding="{Binding LastName}" Width="Auto"/>
            </GridView>
        </ListView.View>
    </ListView>
</Grid>
</Window>
Adam Tegen
  • 25,378
  • 33
  • 125
  • 153
Kjetil Watnedal
  • 6,097
  • 3
  • 29
  • 23
39

If the width of the contents changes, you'll have to use this bit of code to update each column:

private void ResizeGridViewColumn(GridViewColumn column)
{
    if (double.IsNaN(column.Width))
    {
        column.Width = column.ActualWidth;
    }

    column.Width = double.NaN;
}

You'd have to fire it each time the data for that column updates.

RandomEngy
  • 14,931
  • 5
  • 70
  • 113
  • 1
    What would you attach this to? – Armentage Oct 28 '10 at 03:04
  • 1
    Run it manually on the GridViewColumn after you've updated the grid data. If you've got a ViewModel you could subscribe to the PropertyChanged event on it and run it then. – RandomEngy Oct 28 '10 at 23:13
  • +1 Thanks for that! This helped me a lot! Not related to this question, but anyway: I implemented a customized List/GridView where you can dynamically add/remove columns at runtime via the GUI. However, when I removed and re-added a column, it no longer appeared. First, I thought it was not added at all (for some reason), but then (using Snoop) I found out that it is actually added, but has an ActualWidth of 0 (it was auto-sized and obviously reset when the column was removed). Now, I use your code to set the column to the correct width after I re-added it to the Columns. Many thanks! – gehho Nov 24 '10 at 15:13
  • A simple solution to my problem! – Gqqnbig Jul 10 '12 at 00:48
  • +1 Perfect! Wish this was marked as the answer. I added x:Name="gvcMyColumnName" to the XAML where the column was defined so I could access it in the code behind. Works like a champ. – K0D4 Apr 11 '13 at 18:01
  • Seems to only resize based on visible data (so, the first X rows), not necessarily the longest value for each column. Still nice though. – Chris Mar 08 '16 at 14:55
19

If your listview is also re-sizing then you can use a behavior pattern to re-size the columns to fit the full ListView width. Almost the same as you using grid.column definitions

<ListView HorizontalAlignment="Stretch"
          Behaviours:GridViewColumnResize.Enabled="True">
        <ListViewItem></ListViewItem>
        <ListView.View>
            <GridView>
                <GridViewColumn  Header="Column *"
                                   Behaviours:GridViewColumnResize.Width="*" >
                    <GridViewColumn.CellTemplate>
                        <DataTemplate>
                            <TextBox HorizontalAlignment="Stretch" Text="Example1" />
                        </DataTemplate>
                    </GridViewColumn.CellTemplate>

See the following link for some examples and link to source code http://lazycowprojects.tumblr.com/post/7063214400/wpf-c-listview-column-width-auto

Rolf Wessels
  • 797
  • 9
  • 12
  • This one is cool. Solves the problem and gives you all the *, n*, Auto functionality you are looking for. – Designpattern Aug 23 '11 at 12:49
  • Note: there seems to be a bug. When the ListView is resized vertically, to the point that causes a vertical scrollbar to appear, the column will continuously increase in width until the scrollbar disappears. – Jake Berger Oct 05 '11 at 18:54
  • 1
    [This post](http://www.codeproject.com/Tips/84529/Simulate-WPF-ListView-Column-Width-Set-to) may provide insight on the behavior described in my previous comment. – Jake Berger Oct 05 '11 at 19:39
  • It's cool, I mean both the code and the site:). I believe it will be useful when I have stricter requirements. – Gqqnbig Jul 10 '12 at 00:48
  • Does anybody have a link to the source code? It was hosted on Google code and exporting it to Github doesn't show any more source. – Boumbles Feb 17 '16 at 18:31
  • Hmm, I see what you mean. I will go and check at home to see if I still have a backup somewhere. – Rolf Wessels Feb 18 '16 at 06:48
15

I have created the following class and used across the application wherever required in place of GridView:

/// <summary>
/// Represents a view mode that displays data items in columns for a System.Windows.Controls.ListView control with auto sized columns based on the column content     
/// </summary>
public class AutoSizedGridView : GridView
{        
    protected override void PrepareItem(ListViewItem item)
    {
        foreach (GridViewColumn column in Columns)
        {
            // Setting NaN for the column width automatically determines the required
            // width enough to hold the content completely.

            // If the width is NaN, first set it to ActualWidth temporarily.
            if (double.IsNaN(column.Width))
              column.Width = column.ActualWidth;

            // Finally, set the column with to NaN. This raises the property change
            // event and re computes the width.
            column.Width = double.NaN;              
        }            
        base.PrepareItem(item);
    }
}
Drew Noakes
  • 300,895
  • 165
  • 679
  • 742
user1333423
  • 211
  • 3
  • 3
7

Since I had an ItemContainerStyle I had to put the HorizontalContentAlignment in the ItemContainerStyle

    <ListView.ItemContainerStyle>
            <Style TargetType="ListViewItem">
                <Style.Triggers>
                    <DataTrigger Binding="{Binding Path=FieldDef.DispDetail, Mode=OneWay}" Value="False">
                         <Setter Property="Visibility" Value="Collapsed"/>
                    </DataTrigger>
                </Style.Triggers>
                <Setter Property="HorizontalContentAlignment" Value="Stretch" /> 
    ....
paparazzo
  • 44,497
  • 23
  • 105
  • 176
6

I liked user1333423's solution except that it always re-sized every column; i needed to allow some columns to be fixed width. So in this version columns with a width set to "Auto" will be auto-sized and those set to a fixed amount will not be auto-sized.

public class AutoSizedGridView : GridView
{
    HashSet<int> _autoWidthColumns;

    protected override void PrepareItem(ListViewItem item)
    {
        if (_autoWidthColumns == null)
        {
            _autoWidthColumns = new HashSet<int>();

            foreach (var column in Columns)
            {
                if(double.IsNaN(column.Width))
                    _autoWidthColumns.Add(column.GetHashCode());
            }                
        }

        foreach (GridViewColumn column in Columns)
        {
            if (_autoWidthColumns.Contains(column.GetHashCode()))
            {
                if (double.IsNaN(column.Width))
                    column.Width = column.ActualWidth;

                column.Width = double.NaN;                    
            }          
        }

        base.PrepareItem(item);
    }        
}
jv_
  • 1,784
  • 1
  • 16
  • 12
3

I know that this is too late but here is my approach:

<GridViewColumn x:Name="GridHeaderLocalSize"  Width="100">      
<GridViewColumn.Header>
    <GridViewColumnHeader HorizontalContentAlignment="Right">
        <Grid Width="Auto" HorizontalAlignment="Right">
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="100"/>
            </Grid.ColumnDefinitions>
            <TextBlock Grid.Column="0" Text="Local size" TextAlignment="Right" Padding="0,0,5,0"/>
        </Grid>
    </GridViewColumnHeader>
</GridViewColumn.Header>
<GridViewColumn.CellTemplate>
    <DataTemplate>
        <TextBlock Width="{Binding ElementName=GridHeaderLocalSize, Path=Width, FallbackValue=100}"  HorizontalAlignment="Right" TextAlignment="Right" Padding="0,0,5,0" Text="Text" >
        </TextBlock>
    </DataTemplate>
</GridViewColumn.CellTemplate>

The main idea is to bind the width of the cellTemplete element to the width of the ViewGridColumn. Width=100 is default width used until first resize. There isn't any code behind. Everything is in xaml.

Azzy Elvul
  • 1,403
  • 1
  • 12
  • 22
1

I created a function for updating GridView column headers for a list and call it whenever the window is re-sized or the listview updates it's layout.

public void correctColumnWidths()
{
    double remainingSpace = myList.ActualWidth;

    if (remainingSpace > 0)
    {
         for (int i = 0; i < (myList.View as GridView).Columns.Count; i++)
              if (i != 2)
                   remainingSpace -= (myList.View as GridView).Columns[i].ActualWidth;

          //Leave 15 px free for scrollbar
          remainingSpace -= 15;

          (myList.View as GridView).Columns[2].Width = remainingSpace;
    }
}
bytecode77
  • 14,163
  • 30
  • 110
  • 141
1

I had trouble with the accepted answer (because I missed the HorizontalAlignment=Stretch portion and have adjusted the original answer).

This is another technique. It uses a Grid with a SharedSizeGroup.

Note: the Grid.IsSharedScope=true on the ListView.

<Window x:Class="WpfApplication6.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window1" Height="300" Width="300">
<Grid>
    <ListView Name="lstCustomers" ItemsSource="{Binding Path=Collection}" Grid.IsSharedSizeScope="True">
        <ListView.View>
            <GridView>
                <GridViewColumn Header="ID" Width="40">
                    <GridViewColumn.CellTemplate>
                        <DataTemplate>
                             <Grid>
                                  <Grid.ColumnDefinitions>
                                       <ColumnDefinition Width="Auto" SharedSizeGroup="IdColumn"/>
                                  </Grid.ColumnDefinitions>
                                  <TextBlock HorizontalAlignment="Right" Text={Binding Path=Id}"/>
                             </Grid>
                        </DataTemplate>
                    </GridViewColumn.CellTemplate>
                </GridViewColumn>
                <GridViewColumn Header="First Name" DisplayMemberBinding="{Binding FirstName}" Width="Auto" />
                <GridViewColumn Header="Last Name" DisplayMemberBinding="{Binding LastName}" Width="Auto"/>
            </GridView>
        </ListView.View>
    </ListView>
</Grid>
</Window>
Adam Tegen
  • 25,378
  • 33
  • 125
  • 153
  • You have width of `GridViewColumn` as `40` and you set column definition width to `Auto`? That doesn't make sense. – B.K. Mar 15 '14 at 15:11
1

The solution given above by @RandomEngy works but seems it has an issue of, to only resize based on visible data (so, the first X rows), not necessarily the longest(widest row) value for each column.

To fix the above issue following can be done.

Attach the collection changed even of your list view like below,

((INotifyCollectionChanged) MyListView.ItemsSource).CollectionChanged += CollectionChanged_Handler;

also declare a private maxWidth property to store the longest content your view has encountered.

private double maxWidth = 200;//Whatever your default width is.

Now the handler is as below,

private void CollectionChanged_Handler(object sender, NotifyCollectionChangedEventArgs args)
{
   var gridView = (GridView)MyListView.View;
   if(gridView != null)
   {
      foreach( var column in gridView.Columns)
      {
         if(column.ActualWidth > maxWidth)
         {
            if (double.IsNaN(column.Width))
            {
                   maxWidth = column.ActualWidth;
                   column.Width = maxWidth ;
            }
            column.Width = double.NaN;
         }       
       }   
}

Also there can be chances that once you launch the dialog already the widow is populated with multiple rows and the top rows are not the longest. The above code will be triggered only when the collection changes but since the data is already loaded and the visible rows on loading the dialog were not the widest ones the above code will not be able to resize the column. In order to fix this issue call the above handler on the ListView_OnPreviewMouseLeftButton event. like below,

private void MyListView_OnPreviewMouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
CollectionChanged_Handler(sender, null);
}

The above code will refresh your column width once someone scrolls to the widest row content of the view.

Gumbie
  • 11
  • 1
1

Auto sizing a column to the longest text can be archived by disabling virtualisation and setting Width="Auto" on the GridViewColumn.

<ListView ItemsSource="{Binding ...}" VirtualizingStackPanel.IsVirtualizing="False">
    <ListView.View>
        <GridView>
            <GridViewColumn Width="Auto"
                            DisplayMemberBinding="{Binding Name}"
                            Header="Name"/>
        </GridView>
    </ListView.View>
</ListView>

Virtualization is enabled by default and means that only visible rows are rendered. This is good for performance. The downside is that only the currently visible rows are used to calculate the column width (Width="Auto"). Disabling Virtualization means that all rows are rendered even if a row is not visible at the moment. The result is that Auto width calculation considers the values of all rows.

Joel
  • 4,862
  • 7
  • 46
  • 71
0

This is your code

<ListView Name="lstCustomers" ItemsSource="{Binding Path=Collection}">
    <ListView.View>
        <GridView>
            <GridViewColumn Header="ID" DisplayMemberBinding="{Binding Id}" Width="40"/>
            <GridViewColumn Header="First Name" DisplayMemberBinding="{Binding FirstName}" Width="100" />
            <GridViewColumn Header="Last Name" DisplayMemberBinding="{Binding LastName}"/>
        </GridView>
    </ListView.View>
</ListView>

Try this

<ListView Name="lstCustomers" ItemsSource="{Binding Path=Collection}">
    <ListView.View>
        <GridView>
            <GridViewColumn DisplayMemberBinding="{Binding Id}" Width="Auto">
               <GridViewColumnHeader Content="ID" Width="Auto" />
            </GridViewColumn>
            <GridViewColumn DisplayMemberBinding="{Binding FirstName}" Width="Auto">
              <GridViewColumnHeader Content="First Name" Width="Auto" />
            </GridViewColumn>
            <GridViewColumn DisplayMemberBinding="{Binding LastName}" Width="Auto">
              <GridViewColumnHeader Content="Last Name" Width="Auto" />
            </GridViewColumn
        </GridView>
    </ListView.View>
</ListView>
StriderWaR
  • 23
  • 6
0

Well, I just came to this problem and I solved it with a IValueConverter.

public class GridViewColumHeaderWidthConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        return (double)value / 8;
    }

    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    {
        throw new NotImplementedException();
    }
}

Notice: That 8 is the number of GridViewColumns

Now in the Xaml:

<ListView x:Name="TheListView" ItemsSource="{Binding Customers}">
    <ListView.View>
        <GridView>
            <!--  Id  -->
            <GridViewColumn Width="{Binding ElementName=TheListView, Path=ActualWidth, Converter={StaticResource GridViewColumHeaderWidthConverter}}">
                <GridViewColumnHeader Content="Id" />
                <GridViewColumn.CellTemplate>
                    <DataTemplate>
                        <TextBlock Text="{Binding CustomerId}" />
                    </DataTemplate>
                </GridViewColumn.CellTemplate>
            </GridViewColumn>
            <!--  Code  -->
            <GridViewColumn Width="{Binding ElementName=TheListView, Path=ActualWidth, Converter={StaticResource GridViewColumHeaderWidthConverter}}">
                <GridViewColumnHeader Content="Code" />
                <GridViewColumn.CellTemplate>
                    <DataTemplate>
                        <TextBlock Text="{Binding CustomerCode}" />
                    </DataTemplate>
                </GridViewColumn.CellTemplate>
            </GridViewColumn>

Don't forget to include the converter file. In this case, I have defined the converter in App.xaml

<Application.Resources>
    <converters:GridViewColumHeaderWidthConverter x:Key="GridViewColumHeaderWidthConverter" />
</Application.Resources>
Samir Dahal
  • 19
  • 1
  • 5