更新时间:2023-02-25 21:20:03
这个简单的解决方案"仅适用于可冻结的页脚,冻结的页眉解决方案会有所不同(实际上更容易 - 只需使用 HeaderTeamplate -放置一个堆叠面板,其中可以堆叠任意数量的项目).
This "Simple solution" is only for a freezable footer, the frozen header solution would be a bit different (and actually much easier - just play with the HeaderTeamplate - put a stack panel with as many items stacked up as you want).
所以我需要一个可冻结的页脚行,几个月来我找不到任何东西,所以最后我决定停止偷懒并进行调查.
So I needed a footer row that is freezable, I couldn't find anything for months, so finally I decided to stop being lazy and investigate.
因此,如果您需要页脚,要点是在行和水平滚动查看器之间的 DataGrid 模板中找到一个位置,您可以在其中使用带有单元格的 ItemsControl 挤压额外的 Grid.Row.
So if you need a footer, the gist is find a place in DataGrid's Template between the rows and the horizontal scrollviewer where you can squeeze extra Grid.Row with an ItemsControl with Cells.
攻击计划:
首先,提取DataGrid 模板(我使用Blend).熟悉模板后,按顺序记下各个部分:
First, extract the DataGrid template (I used Blend). When getting familiarized with the template, note the parts in order:
PART_ColumnHeadersPresenter
PART_ScrollContentPresenter
PART_VerticalScrollBar
在 PART_VerticalScrollBar 的正下方,有一个网格(为了清楚起见,我会在这里发布)
right under PART_VerticalScrollBar, there is a grid (I'll post it here for clarity)
<Grid Grid.Column="1" Grid.Row="2">
<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>
这是我修改为包含可冻结/页脚行"的网格.为了简单起见,我将只对颜色进行硬编码,并用希望有用的假装"属性替换 Binding(我将它们标记为MyViewModel.SomeProperty,以便于查看):
That's the grid I modified to include a "freezable/footer row". I am going to just hard-code colors, and replace Binding with hoperfully helpful "pretend" properties for simplicity (I'll mark them "MyViewModel.SomeProperty so they are easy to see):
<Grid Grid.Column="1" Grid.Row="2" x:Name="PART_DataGridColumnsVisualSpace">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="{Binding RelativeSource={RelativeSource AncestorType={x:Type DataGrid}}, Path=NonFrozenColumnsViewportHorizontalOffset}"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<ScrollBar Grid.Column="2" Grid.Row="3" Name="PART_HorizontalScrollBar" Orientation="Horizontal"
Maximum="{TemplateBinding ScrollableWidth}" ViewportSize="{TemplateBinding ViewportWidth}"
Value="{Binding Path=HorizontalOffset, RelativeSource={RelativeSource TemplatedParent}, Mode=OneWay}"
Visibility="{TemplateBinding ComputedHorizontalScrollBarVisibility}"/>
<Border x:Name="PART_FooterRowHeader" Grid.Row="1" Height="30" Background="Gray" BorderBrush="Black" BorderThickness="0.5">
<TextBlock Margin="4,0,0,0" VerticalAlignment="Center">MY FOOTER</TextBlock>
</Border>
<ItemsControl x:Name="PART_Footer" ItemsSource="{Binding MyViewModel.FooterRow}"
Grid.Row="1" Grid.Column="1" Height="30">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Border Background="Gray" BorderThickness="0,0,0.5,0.5" BorderBrush="Black">
<!-- sticking a textblock as example, i have a much more complex control here-->
<TextBlock Text="{Binding FooterItemValue}"/>
</Border>
</DataTemplate>
</ItemsControl.ItemTemplate>
<ItemsControl.Template>
<ControlTemplate>
<ScrollViewer x:Name="PART_Footer_ScrollViewer" HorizontalScrollBarVisibility="Hidden" VerticalScrollBarVisibility="Hidden"
CanContentScroll="True" Focusable="false">
<StackPanel IsItemsHost="True" Orientation="Horizontal"/>
</ScrollViewer>
</ControlTemplate>
</ItemsControl.Template>
</ItemsControl>
</Grid>
还添加到 DataGrid 响应滚动和标题调整大小
Also add to DataGrid respond to scroll and header resize
<DataGrid ... ScrollViewer.ScrollChanged="OnDatagridScrollChanged"
<Style TargetType="DataGridColumnHeader">
<EventSetter Event="SizeChanged" Handler="OnDataColumnSizeChanged"/>
</Style>
现在,回到 .xaml.cs
Now, back in .xaml.cs
基本上需要两个主要的东西:
Basically two main things are needed:
(1) 同步列调整大小(以便相应的页脚单元调整大小)(2) 同步 DataGrid 滚动与页脚滚动
(1) sync column resize (so that corresponding footer cell resizes) (2) sync DataGrid scroll with footer scroll
//syncs the footer with column header resize
private void OnDatagridScrollChanged(object sender, ScrollChangedEventArgs e)
{
if (e.HorizontalChange == 0.0) return;
FooterScrollViewer.ScrollToHorizontalOffset(e.HorizontalOffset);
}
//syncs scroll
private void OnDataColumnSizeChanged(object sender, SizeChangedEventArgs e)
{
//I don't know how many of these checks you need, skip if need to the gist
if (!_isMouseDown) return;
if (!_dataGridLoaded) return;
if (!IsVisible) return;
var header = (DataGridColumnHeader)sender;
var index = header.DisplayIndex - ViewModel.NumberOfHeaderColumns;
if (index < 0 || index >= FooterCells.Count) return;
FooterCells[index].Width = e.NewSize.Width;
}
//below referencing supporting properties:
private ScrollViewer _footerScroll;
private ScrollViewer FooterScrollViewer
{
get {
return _footerScroll ??
(_footerScroll = myDataGrid.FindVisualChildByName<ScrollViewer>("PART_Footer_ScrollViewer"));
}
}
//added this so I don't have to hunt them down from XAML every time
private List<Border> _footerCells;
private List<Border> FooterCells
{
get
{
if (_footerCells == null)
{
var ic = myDataGrid.FindVisualChildByName<ItemsControl>("PART_Footer");
_footerCells = new List<Border>();
for (var i = 0; i < ic.Items.Count; i++)
{
var container = ic.ItemContainerGenerator.ContainerFromIndex(i);
var border = ((Visual)container).FindVisualChild<Border>();
_footerCells.Add(border);
}
}
return _footerCells;
}
}
就是这样!我认为最重要的部分是 XAML 以查看您可以将可冻结行"放在哪里,其他所有内容,例如操作/同步事物都非常容易 - 几乎是一个衬垫)
that's it! I think the most important part is the XAML to see where you can put your "freezable row", everything else, like manipulating/sync'ing things is pretty easy - almost one liners)