How to autosize and right-align GridViewColumn data in WPF?

Viewed 161746

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>
13 Answers

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>

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.

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>

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.

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>

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>

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.

Related