WPF数据网格控件能否像WinForms DataGridControl一样调整列布局行为?

7
基本上,我想让WPF DataGrid控件以与WinForms DataGridView相同的方式布置其列。
更具体地说,这里是我要寻找的行为:
- 网格控件应占用其给定的空间(即其父控件中可用的任何空间)。 在这里,我仅指控件,而不是列。 - 创建的列(无论是自动还是手动)可能会占用所有这些空间,也可能不会占用所有这些空间。 - 如果在创建列之后有额外的空间剩余,则最后一列不应扩展以填充此空间 - 如果在创建列之后有额外的空间剩余,则不应创建一个空的列来填充这个额外的空间
据我所知,在WPF中,最后两个要点似乎是互相排斥的,您必须选择其中之一。 有人发现了同时做到这两点的方法吗? 我已经搜索了很多,但没有找到完全符合我的要求,但我发现的所有帖子都倾向于几年前,因此我希望现在有人已经解决了这个问题。
编辑:sa_ddam213,这是我编写的一个快速xaml项目,用于测试您的建议。
<Window x:Class="DataGridFix.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:DataGridFix"
        Title="MainWindow" Height="350" Width="525">

    <Window.Resources>
        <ObjectDataProvider x:Key="data"
                    ObjectType="{x:Type local:TestObject}"
                    MethodName="GetTestData" />
    </Window.Resources>
    <StackPanel>
        <DataGrid HorizontalAlignment="Left"  ColumnWidth="Auto" Height="150" VerticalAlignment="Top" ItemsSource="{Binding Source={StaticResource data}}" />
    </StackPanel>
</Window>

以下是代码:

namespace DataGridFix { public class TestObject { // 定义属性 public int Id { get; set; } public string Name { get; set; }

    public static List<TestObject> GetTestData()
    {
        var items = new List<TestObject>();

        items.Add(new TestObject() { Id = 1, Name = "Joe" });
        items.Add(new TestObject() { Id = 2, Name = "Matt" });
        items.Add(new TestObject() { Id = 3, Name = "Hal" });

        return items;
    }
}

我在你的建议中唯一值得注意的是将HorizontalAlignment设置为Left。我这样做了,并尝试将ColumnWidth设置为各种设置,但每个设置都有相同的问题(当然除了*…技术上我也可以弄糟它,但我不会深入讨论)。如果您使用鼠标扩展任何列,然后减小列大小,则空填充列将出现。我从您的帖子中注意到的另一个区别是,您将DataGrids放在StackPanel中,因为您有多个DataGrids。我只是为了好玩而尝试了一下,但结果相同。如果您发现我正在做的事情与您建议的事情之间存在任何其他区别,请告诉我。


'behavior',也就是说。我不是英国人(无意冒犯英国人)。 - Brandon Moore
所以你想让网格填充父元素,但不使列自适应并且不留下任何空白空间?? “自动”宽度的整个原因是为了删除任何空白空间,我不确定你如何同时实现两者。 - sa_ddam213
@sa_ddam213 你有使用过WinForms DataGridView控件吗?我最初猜测你对这个概念感到陌生是因为你没有使用过。如果是这样的话,那么你可能不会在你的经验范围内找到我所描述的行为,但这并不意味着它没有意义 :) 我只是想知道是否有一种方法可以让WPF DataGrid表现出与DataGridView相同的方式。 - Brandon Moore
2个回答

7
为了达到您想要的效果,您可能需要修改DataGrid的控件模板。
请看代码。我认为我已经非常接近WinForms DataGridView的外观了。
要删除额外的列,您必须从DataGridColumnHeadersPresenter中删除填充列。我只是将其注释掉了。其余部分是默认模板。
另一个修改是DataGrid模板。
通过在ScrollContentPresenter上设置HorizontalAlignment="Left",行不再占据控件的所有宽度。
这些是我对默认模板所做的唯一两个更改。 enter image description here
<Style TargetType="{x:Type DataGridColumnHeadersPresenter}">
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="{x:Type DataGridColumnHeadersPresenter}">
                <Grid>
                    <!-- Remove this filler column -->
                    <!--<DataGridColumnHeader x:Name="PART_FillerColumnHeader" IsHitTestVisible="False" />-->
                    <ItemsPresenter />
                </Grid>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

<Style TargetType="{x:Type DataGrid}">
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="{x:Type DataGrid}">
                <Border Background="{TemplateBinding Background}"
                                BorderBrush="{TemplateBinding BorderBrush}"
                                BorderThickness="{TemplateBinding BorderThickness}"
                                SnapsToDevicePixels="True"
                                Padding="{TemplateBinding Padding}">
                    <ScrollViewer Focusable="false" Name="DG_ScrollViewer">
                        <ScrollViewer.Template>
                            <ControlTemplate TargetType="{x:Type ScrollViewer}">
                                <Grid>
                                    <Grid.RowDefinitions>
                                        <RowDefinition Height="Auto"/>
                                        <RowDefinition Height="*"/>
                                        <RowDefinition Height="Auto"/>
                                    </Grid.RowDefinitions>

                                    <Grid.ColumnDefinitions>
                                        <ColumnDefinition Width="Auto"/>
                                        <ColumnDefinition Width="*"/>
                                        <ColumnDefinition Width="Auto"/>
                                    </Grid.ColumnDefinitions>

                                    <Button Command="{x:Static DataGrid.SelectAllCommand}"
                                                    Width="{Binding RelativeSource={RelativeSource AncestorType={x:Type DataGrid}}, Path=CellsPanelHorizontalOffset}"
                                                    Style="{DynamicResource {ComponentResourceKey TypeInTargetAssembly={x:Type DataGrid}, ResourceId=DataGridSelectAllButtonStyle}}"
                                                    Focusable="false"
                                                    Visibility="{Binding RelativeSource={RelativeSource AncestorType={x:Type DataGrid}}, Path=HeadersVisibility, Converter={x:Static DataGrid.HeadersVisibilityConverter}, ConverterParameter={x:Static DataGridHeadersVisibility.All}}" />

                                    <DataGridColumnHeadersPresenter Grid.Column="1" Name="PART_ColumnHeadersPresenter"
                                                    Visibility="{Binding RelativeSource={RelativeSource AncestorType={x:Type DataGrid}}, Path=HeadersVisibility, Converter={x:Static DataGrid.HeadersVisibilityConverter}, ConverterParameter={x:Static DataGridHeadersVisibility.Column}}"/>

                                    <!-- Set HorizontalAlignment="Left" to have the rows only take up the width they need and not fill the entire width of the DataGrid -->
                                    <ScrollContentPresenter HorizontalAlignment="Left" x:Name="PART_ScrollContentPresenter" Grid.Row="1" Grid.ColumnSpan="2" CanContentScroll="{TemplateBinding CanContentScroll}" />

                                    <ScrollBar Grid.Row="1" Grid.Column="2" Name="PART_VerticalScrollBar"
                                                        Orientation="Vertical"
                                                        Maximum="{TemplateBinding ScrollableHeight}"
                                                        ViewportSize="{TemplateBinding ViewportHeight}"
                                                        Value="{Binding Path=VerticalOffset, RelativeSource={RelativeSource TemplatedParent}, Mode=OneWay}"
                                                        Visibility="{TemplateBinding ComputedVerticalScrollBarVisibility}"/>

                                    <Grid Grid.Row="2" Grid.Column="1">
                                        <Grid.ColumnDefinitions>
                                            <ColumnDefinition Width="{Binding RelativeSource={RelativeSource AncestorType={x:Type DataGrid}}, Path=NonFrozenColumnsViewportHorizontalOffset}"/>
                                            <ColumnDefinition Width="*"/>
                                        </Grid.ColumnDefinitions>
                                        <ScrollBar Grid.Column="1"
                                                    Name="PART_HorizontalScrollBar"
                                                    Orientation="Horizontal"
                                                    Maximum="{TemplateBinding ScrollableWidth}"
                                                    ViewportSize="{TemplateBinding ViewportWidth}"
                                                    Value="{Binding Path=HorizontalOffset, RelativeSource={RelativeSource TemplatedParent}, Mode=OneWay}"
                                                    Visibility="{TemplateBinding ComputedHorizontalScrollBarVisibility}"/>
                                    </Grid>
                                </Grid>
                            </ControlTemplate>
                        </ScrollViewer.Template>
                        <ItemsPresenter SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" />
                    </ScrollViewer>
                </Border>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

更新
看起来.NET 4和.NET 4.5确实有所不同。
我是在Windows 8机器上使用Visual Studio 2012进行开发的,因此我尝试了针对.NET 4的测试,以查看是否可以复制错误行为。但它仍然正常工作。

为了确定,我尝试在一个只安装了.NET 4的不同机器上运行应用程序,在这里,当使列变小然后再次变大时,空行出现了。

问题似乎是DataGridRows没有正确地工作。 在只安装.NET 4的机器上运行时,当使列变小时,它们保持当前大小。 在.NET 4.5上,它们按预期调整大小。

获得所需行为的新解决方案实际上比以前的解决方案要简单得多。

只需将DataGridRows上的HorizontalAlignment设置为left,并删除填充列,即可在.NET 4和.NET 4.5上都正常工作。 不再需要替换DataGrid的整个模板。

<Style TargetType="DataGridRow">
    <Setter Property="HorizontalAlignment" Value="Left" />
</Style>
<Style TargetType="DataGridColumnHeadersPresenter">
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="{x:Type DataGridColumnHeadersPresenter}">
                <Grid>
                    <ItemsPresenter />
                </Grid>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

1
无论最终解决方案采取何种形式,它很可能采用重新调整控件模板的形式。我强烈建议使用像Blend或XamlPad这样的工具来进行任何控件模板调整。 - JerKimball
谢谢Peter,我很快会尝试这个并告诉你结果。 - Brandon Moore
好的,这真的很接近了。我注意到的唯一不太正确的事情是,如果我扩展某一列然后再缩小它,那么空列会再次出现(但没有列标题)。有任何解决方法吗? - Brandon Moore
嗨 - 我似乎无法在我的示例中获得那种行为。我的DataGrid只绑定到一组简单对象的数组,而且无论AutoGenerateColumns是true还是false,我都会得到相同的行为。也许您已经设置了DataGrid上的其他属性导致了这种行为? - Peter Hansen
或者说,我应该说的是 .NET 4.0 和 4.5 之间的区别。 - Brandon Moore
显示剩余2条评论

5
在WPF中,列有很多布局选项,只需要选择你想要显示的内容即可。
  • Pixel(像素)
  • SizeToCells(根据单元格大小调整)
  • SizeToHeader(根据标题大小调整)
  • Auto(自动调整)
  • Proportional(按比例调整)
如果将HorizontalAlignment设置为Left,则DataGrid将根据您选择的ColumnWidth调整大小以适应其内容。
以下是一些可用列设置的示例。
   <StackPanel>
        <DataGrid HorizontalAlignment="Left"  ColumnWidth="100" Height="64" VerticalAlignment="Top" ItemsSource="{Binding ElementName=UI, Path=GridItems}" />
        <DataGrid HorizontalAlignment="Left"  ColumnWidth="SizeToCells" Height="64" VerticalAlignment="Top" ItemsSource="{Binding ElementName=UI, Path=GridItems}" />
        <DataGrid HorizontalAlignment="Left"  ColumnWidth="SizeToHeader" Height="64" VerticalAlignment="Top" ItemsSource="{Binding ElementName=UI, Path=GridItems}" />
        <DataGrid HorizontalAlignment="Left"  ColumnWidth="Auto" Height="64" VerticalAlignment="Top" ItemsSource="{Binding ElementName=UI, Path=GridItems}" />
        <DataGrid HorizontalAlignment="Left"  ColumnWidth="*" Height="64" VerticalAlignment="Top" ItemsSource="{Binding ElementName=UI, Path=GridItems}" />
    </StackPanel>

enter image description here


如果有帮助的话,可以想象一个具有红色背景并包含单个文本框的自定义用户控件。当用户控件被展开时,红色背景应该随之扩展,但文本框的大小应保持不变。这大致是我想要的行为。 - Brandon Moore
更新了答案,并设置了 HorizontalAlignment,我想这是你想要的,以匹配 Winform 行为。 - sa_ddam213
与您可能认为的相反,我并没有要求帮助理解为什么我的问题很愚蠢,以及我应该接受开箱即用的WPF行为优于我喜欢的任何东西。我不想在我的DataGrid末尾附加一个愚蠢的空列,这是我曾经见过的第一个控件,所以你建议这些控件在过去20年中的行为方式存在“混乱”有点傻。 - Brandon Moore
你能发一个小的Xaml例子展示这个行为吗?因为我无法使填充列在调整列大小时出现。听起来像是容器控件而不是网格控件本身导致了这种行为。 - sa_ddam213
看起来我们遇到的行为差异可能是由于使用不同版本的 .NET Framework(我使用的是 4.0)引起的。Peter 能够提出一个对我有效的解决方案,但非常感谢你的帮助。+1 - Brandon Moore
显示剩余6条评论

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