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

13

92

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>
Caucasus answered 18/2, 2009 at 10:52 Comment(3)
Did you ever find a solution to your auto-size problem? I am experiencing the same thing.Sapajou
@Sapajou - 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. :(Accouplement
If using MVVM and Binding values are changing, please see @Rolf Wessels answer.Jecoa
R
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>
Rimester answered 18/2, 2009 at 11:23 Comment(6)
@Kjetil - Can I apply this setting to a specific column ?Accouplement
+1 for: <Setter Property="HorizontalContentAlignment" Value="Stretch" />Highball
awesome, but I have 15 columns, is there any way I dont have to repeat the celltemplate for all of them?Externality
It also doesn't work if you forget to remove the DisplayMemberBinding from the GridViewColumn. The template won't have any effect then.Varistor
@Mohamed Why isn't it?Primula
Width="Auto" seems to have no effectColtoncoltsfoot
B
40

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.

Bobbiebobbin answered 19/12, 2009 at 0:14 Comment(6)
What would you attach this to?Iveson
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.Bobbiebobbin
+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!Uella
A simple solution to my problem!Brennan
+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.Pickwickian
Seems to only resize based on visible data (so, the first X rows), not necessarily the longest value for each column. Still nice though.Spearmint
G
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

Giotto answered 30/6, 2011 at 2:36 Comment(6)
This one is cool. Solves the problem and gives you all the , n, Auto functionality you are looking for.Alius
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.Jecoa
This post may provide insight on the behavior described in my previous comment.Jecoa
It's cool, I mean both the code and the site:). I believe it will be useful when I have stricter requirements.Brennan
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.Sturgill
Hmm, I see what you mean. I will go and check at home to see if I still have a backup somewhere.Giotto
C
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);
    }
}
Certitude answered 1/4, 2013 at 14:13 Comment(0)
H
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" /> 
    ....
Hack answered 1/8, 2011 at 16:3 Comment(0)
T
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);
    }        
}
Typhoid answered 15/5, 2015 at 7:32 Comment(0)
M
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.

Margy answered 25/5, 2015 at 7:39 Comment(1)
This inspired me to this solution to fill the width of one column: <GridViewColumn Width="{Binding RelativeSource={RelativeSource AncestorType=ListView}, Path=ActualWidth}" >Paronym
V
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>
Vouge answered 24/12, 2010 at 17:13 Comment(1)
You have width of GridViewColumn as 40 and you set column definition width to Auto? That doesn't make sense.Flori
R
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;
    }
}
Reachmedown answered 31/7, 2014 at 13:1 Comment(0)
T
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.

Trangtranquada answered 3/6, 2021 at 18:4 Comment(0)
R
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.

Rennin answered 6/2, 2022 at 23:7 Comment(0)
L
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>
Limb answered 19/2, 2019 at 17:2 Comment(0)
P
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>
Palawan answered 5/5, 2021 at 13:52 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.