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

92

我该如何:

  • 将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效果很好,而且自动宽度当然也有效,但是当ObservativeCollection "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>

2
你是否曾经找到过自动调整大小问题的解决方案?我也遇到了同样的问题。 - Oskar
2
@Oskar - 列表的虚拟化防止了自动解决方案。列表只知道当前可见的项目并相应地设置大小。如果列表下面还有更多项目,则它不知道它们,因此无法考虑它们。ProgrammingWPF - Sells-Griffith书籍建议如果您正在使用数据绑定,则使用手动列宽。:( - Gishu
如果使用MVVM和数据绑定值正在更改,请查看@Rolf Wessels的答案。 - Jake Berger
13个回答

108
要使每个列自适应大小,您可以在GridViewColumn上设置Width ="Auto"。
要将ID列中的文本右对齐,您可以使用TextBlock创建单元格模板并设置TextAlignment。然后,使用ListViewItem.HorizontalContentAlignment(使用带有ListViewItem上的setter的样式)将单元格模板填充整个GridViewCell。
也许有更简单的解决方法,但这个方法应该有效。
注意:此解决方案需要Window.Resources中的HorizontalContentAlignment = Stretch和CellTemplate中的TextAlignment = Right。
<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>

@Kjetil - 我可以将此设置应用于特定列吗? - Gishu
15
+1 表示: <Setter Property="HorizontalContentAlignment" Value="Stretch" />。 - Helge Klein
很棒,但我有15列,有没有办法让我不必为它们重复单元格模板? - Nitin Chaudhari
8
如果你忘记从GridViewColumn中移除DisplayMemberBinding,它也不会起作用。那个模板就没有效果。 - floele
@Mohamed 为什么不是呢? - It'sNotALie.
"Width = 'Auto' 似乎没有任何效果。" - David

39

如果内容的宽度发生了变化,您需要使用以下代码来更新每个列:

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

    column.Width = double.NaN;
}

每当该列的数据更新时,您都需要触发它。


1
你会把这个附加到什么上面? - Armentage
1
在更新了网格数据后,手动在GridViewColumn上运行它。如果您有一个ViewModel,可以订阅其PropertyChanged事件,然后运行它。 - RandomEngy
+1 谢谢!这对我帮助很大!虽然与此问题无关,但是我实现了一个自定义的List/GridView,在运行时可以通过GUI动态添加/删除列。然而,当我删除并重新添加一列时,它不再出现了。起初,我以为根本没有添加(由于某种原因),但是(使用Snoop)我发现它实际上已经添加了,但其ActualWidth为0(它是自动调整大小的,并且显然在删除列时重置)。现在,我使用您的代码在将列重新添加到Columns后将其设置为正确的宽度。非常感谢! - gehho
一个简单的解决方案,解决了我的问题! - Gqqnbig
+1 完美!希望这被标记为答案。我在定义列的XAML中添加了x:Name="gvcMyColumnName",以便在代码后台中访问它。像个冠军一样运作。 - K0D4
似乎只根据可见数据(即前X行)调整大小,而不一定是每列最长的值。尽管如此,仍然很好。 - Chris

19

如果您的列表视图也需要重新调整大小,则可以使用行为模式来调整列宽以适应整个列表视图的宽度。这几乎与您使用网格列定义相同。

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

请查看以下链接以获取一些示例和源代码链接。 http://lazycowprojects.tumblr.com/post/7063214400/wpf-c-listview-column-width-auto


这个很棒。解决了问题并提供了您所寻找的所有、n、自动功能。 - Designpattern
注意:似乎存在一个 bug。当垂直地调整 ListView 的大小,使之出现垂直滚动条时,该列的宽度将不断增加,直到滚动条消失。 - Jake Berger
1
这篇文章可能会对我之前评论中描述的行为提供一些见解。 - Jake Berger
太酷了,我的意思是指代码和网站都很棒 :). 我相信当我有更严格的要求时它会非常有用。 - Gqqnbig
有人有源代码的链接吗?它曾经托管在Google Code上,但导出到Github后不再显示源代码了。 - Boumbles
嗯,我明白你的意思。我会回家检查一下,看看我是否还有备份。 - Rolf Wessels

15

我已经创建了下面这个类,并在应用程序中的需要处使用它来代替 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);
    }
}

7

因为我有一个ItemContainerStyle,所以我必须将HorizontalContentAlignment放在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" /> 
    ....

6

我喜欢用户1333423的解决方案,但它总是调整每一列的大小;我需要允许一些列具有固定的宽度。因此,在这个版本中,设置为“Auto”宽度的列将自动调整大小,而那些设置为固定值的列将不会自动调整大小。

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

3

我知道现在来说有点晚了,但这是我的解决方案:

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

主要思路是将cellTemplete元素的宽度绑定到ViewGridColumn的宽度上。在第一次调整大小之前,宽度默认为100。没有任何后台代码,一切都在xaml中。


1
这激发了我对解决方案的灵感,以填充一列的宽度: <GridViewColumn Width="{Binding RelativeSource={RelativeSource AncestorType=ListView}, Path=ActualWidth}"> - J. Andersen

1
上面@RandomEngy给出的解决方案可行,但似乎存在一个问题, 只根据可见数据(即前X行)进行调整大小,而不一定是每列中最长(最宽)的值。
为了解决上述问题,可以执行以下操作。
像下面这样附加列表视图的集合更改事件:
((INotifyCollectionChanged) MyListView.ItemsSource).CollectionChanged += CollectionChanged_Handler;

同时,声明一个私有的maxWidth属性来存储视图遇到的最长内容。
private double maxWidth = 200;//Whatever your default width is.

现在处理程序如下所示,
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;
         }       
       }   
}

此外,有可能在启动对话框时窗口已经填充了多行,而且顶部行不是最长的。上面的代码将仅在集合更改时触发,但由于数据已经加载并且在加载对话框时可见的行不是最宽的行,因此上面的代码将无法调整列的大小。为了解决这个问题,在ListView_OnPreviewMouseLeftButton事件中调用上面的处理程序,如下所示:
private void MyListView_OnPreviewMouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
CollectionChanged_Handler(sender, null);
}

上述代码会在某人滚动到视图中最宽的行内容时刷新您的列宽度。

1

通过禁用虚拟化并在 GridViewColumn 上设置 Width="Auto",可以将列自动调整为最长的文本。

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

虚拟化默认启用,意味着仅呈现可见行。这对性能很有好处。缺点是仅使用当前可见的行来计算列宽度(Width="Auto")。禁用虚拟化意味着即使某一行当前不可见,也会呈现所有行。结果是自动宽度计算会考虑所有行的值。


1

我在接受的答案上遇到了问题(因为我错过了HorizontalAlignment=Stretch部分并调整了原始答案)。

这是另一种技术。它使用具有SharedSizeGroup的网格。

注意:ListView上的Grid.IsSharedScope=true

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

你将 GridViewColumn 的宽度设置为 40,并将列定义宽度设置为 Auto?这没有意义。 - B.K.

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接