Static/Freeze WPF DataGrid 顶部的 DataGridRow

Static/Freeze DataGridRow at the top of DataGrid for WPF

我想创建一个顶部有 static/frozen 行的自定义 DataGrid,类似于 Excel 的冻结行功能,无论您如何滚动,静态行将始终保持在顶部.我遇到过 Freeze DataGrid Row,但发现没有完整的解决方案。静态行也需要像所有其他行一样允许可重新排序的列。

这是我创建自定义 DataGrid 的尝试。我找到了我的静态行将显示的区域。 space between column headers and scroll content presenter

我尝试使用 DataGridRow 控件,但无法获取它 displayed/working。也许我误解了这个控件的使用方式,这是错误的方法?

错误的 DataGridRow 尝试

<DataGridRow Grid.Row="1" Grid.Column="1" Grid.ColumnSpan="2" Item="{Binding StaticRow, RelativeSource={RelativeSource AncestorType={x:Type local:CustomDataGrid}}}" />

CustomDataGrid.xaml

我在 DataGridColumnHeadersPresenter 和 ScrollContentPresenter 之间有一个文本块占位符

<Style TargetType="{x:Type local:CustomDataGrid}">
    <Setter Property="Background" Value="{DynamicResource {x:Static SystemColors.ControlBrushKey}}"/>
    <Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.ControlTextBrushKey}}"/>
    <Setter Property="BorderBrush" Value="#FF688CAF"/>
    <Setter Property="BorderThickness" Value="1"/>
    <Setter Property="RowDetailsVisibilityMode" Value="VisibleWhenSelected"/>
    <Setter Property="ScrollViewer.CanContentScroll" Value="true"/>
    <Setter Property="ScrollViewer.PanningMode" Value="Both"/>
    <Setter Property="Stylus.IsFlicksEnabled" Value="False"/>
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="{x:Type local:CustomDataGrid}">
                <Border BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}" Padding="{TemplateBinding Padding}" SnapsToDevicePixels="True">
                    <ScrollViewer x:Name="DG_ScrollViewer" Focusable="false">
                        <ScrollViewer.Template>
                            <ControlTemplate TargetType="{x:Type ScrollViewer}">
                                <Grid>
                                    <Grid.ColumnDefinitions>
                                        <ColumnDefinition Width="Auto"/>
                                        <ColumnDefinition Width="*"/>
                                        <ColumnDefinition Width="Auto"/>
                                    </Grid.ColumnDefinitions>
                                    <Grid.RowDefinitions>
                                        <RowDefinition Height="Auto"/>
                                        <RowDefinition Height="Auto"/>
                                        <RowDefinition Height="*"/>
                                        <RowDefinition Height="Auto"/>
                                    </Grid.RowDefinitions>
                                    <Button Command="{x:Static DataGrid.SelectAllCommand}" Focusable="false" Style="{DynamicResource {ComponentResourceKey ResourceId=DataGridSelectAllButtonStyle, TypeInTargetAssembly={x:Type DataGrid}}}" Visibility="{Binding HeadersVisibility, ConverterParameter={x:Static DataGridHeadersVisibility.All}, Converter={x:Static DataGrid.HeadersVisibilityConverter}, RelativeSource={RelativeSource AncestorType={x:Type DataGrid}}}" Width="{Binding CellsPanelHorizontalOffset, RelativeSource={RelativeSource AncestorType={x:Type DataGrid}}}"/>
                                    <DataGridColumnHeadersPresenter x:Name="PART_ColumnHeadersPresenter" Grid.Column="1" Visibility="{Binding HeadersVisibility, ConverterParameter={x:Static DataGridHeadersVisibility.Column}, Converter={x:Static DataGrid.HeadersVisibilityConverter}, RelativeSource={RelativeSource AncestorType={x:Type DataGrid}}}"/>

                                    <TextBlock Grid.Row="1" Grid.Column="1" Grid.ColumnSpan="2">Static Row Here</TextBlock>

                                    <ScrollContentPresenter x:Name="PART_ScrollContentPresenter" CanContentScroll="{TemplateBinding CanContentScroll}" Grid.ColumnSpan="2" Grid.Row="2"/>
                                    <ScrollBar x:Name="PART_VerticalScrollBar" Grid.Column="2" Maximum="{TemplateBinding ScrollableHeight}" Orientation="Vertical" Grid.Row="2" Visibility="{TemplateBinding ComputedVerticalScrollBarVisibility}" Value="{Binding VerticalOffset, Mode=OneWay, RelativeSource={RelativeSource TemplatedParent}}" ViewportSize="{TemplateBinding ViewportHeight}"/>
                                    <Grid Grid.Column="1" Grid.Row="3">
                                        <Grid.ColumnDefinitions>
                                            <ColumnDefinition Width="{Binding NonFrozenColumnsViewportHorizontalOffset, RelativeSource={RelativeSource AncestorType={x:Type DataGrid}}}"/>
                                            <ColumnDefinition Width="*"/>
                                        </Grid.ColumnDefinitions>
                                        <ScrollBar x:Name="PART_HorizontalScrollBar" Grid.Column="1" Maximum="{TemplateBinding ScrollableWidth}" Orientation="Horizontal" Visibility="{TemplateBinding ComputedHorizontalScrollBarVisibility}" Value="{Binding HorizontalOffset, Mode=OneWay, RelativeSource={RelativeSource TemplatedParent}}" ViewportSize="{TemplateBinding ViewportWidth}"/>
                                    </Grid>
                                </Grid>
                            </ControlTemplate>
                        </ScrollViewer.Template>
                        <ItemsPresenter SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"/>
                    </ScrollViewer>
                </Border>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
    <Style.Triggers>
        <MultiTrigger>
            <MultiTrigger.Conditions>
                <Condition Property="IsGrouping" Value="true"/>
                <Condition Property="VirtualizingPanel.IsVirtualizingWhenGrouping" Value="false"/>
            </MultiTrigger.Conditions>
            <Setter Property="ScrollViewer.CanContentScroll" Value="false"/>
        </MultiTrigger>
    </Style.Triggers>
</Style>

CustomDataGrid.cs

public class CustomDataGrid : DataGrid
{
    static CustomDataGrid()
    {
        DefaultStyleKeyProperty.OverrideMetadata(typeof(CustomDataGrid), new FrameworkPropertyMetadata(typeof(CustomDataGrid)));
    }

    public static readonly DependencyProperty StaticRowProperty = DependencyProperty.Register(
        "StaticRow", typeof(object), typeof(CustomDataGrid), new PropertyMetadata(default(object)));

    public object StaticRow
    {
        get { return GetValue(StaticRowProperty); }
        set { SetValue(StaticRowProperty, value); }
    }
}

有几种方法可以解决这个问题。 一种是有两个数据网格,一个在另一个上面。隐藏第二个的第 header 列。 顶部显示您的固定行,第二个显示您要滚动的行。

这里有几个问题。 您可能必须绑定列宽。 如果您允许用户对列进行排序,那么您需要单击冻结数据网格中的 header 对第二个显示 non-frozen 行的 collection 进行排序。 您也不会在第一行看到滚动条。 这种方法的好处是您可以直接使用数据网格,而不是更改可能产生不良副作用的相当复杂的代码。

另一种选择是查看 Vincent Sibal 的方法,并可能对此做一些工作。 “注意:示例的目的仅供学习。请不要在生产代码中使用它,因为它没有经过全面测试。https://blogs.msdn.microsoft.com/vinsibal/2008/10/31/wpf-datagrid-frozen-row-sample/

如果你能说服它可靠地工作,那看起来会是一个更优雅的解决方案。