如何在 WPF 中自动调整和右对齐 GridViewColumn 数据?

我怎么做:

  • 右对齐 ID 列中的文本
  • 使每个列自动大小根据文本长度的单元格与最长的可见数据?

密码如下:

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

部分回答:

谢谢 Kjetil,GridViewColumn。CellTemplate 工作得很好,Auto Width 工作得很好,但是当使用长于列宽的数据更新 Observer ativeCollection“ Collection”时,列的大小不会自动更新,所以这只是初始数据显示的一个解决方案:

<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>
162785 次浏览

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>

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

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" />
....

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);
}
}

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;
}
}

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);
}
}

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.

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>

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.

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.