我们知道,wpf冻结左侧列是很简单的,微软提供给我们了FrozenColumnCount属性,它的值代表你要从左往右冻结几列,如下图所示:
但是 这往往不是我们想要的,我们习惯于冻结右边的列,来实现固定操作列,删除,修改等等,遗憾的是,官方并没有提供支持,于是我们只能通过修改样式来实现.
先上效果:
我们实现了和网页一样丝滑的右侧固定列,并且使用起来及为方便,接下来我们看看代码是如何实现的.
.xaml
<controls:DataGrid RightFrozenCount="2" FrozenColumnCount="1" x:Name="listView" Margin="0 10 0 0" LoadingRow="listView_LoadingRow" Style="{StaticResource DesignDataGrid2}">
<DataGrid.Columns>
<DataGridTextColumn Header="很长很长的标题" Binding="{Binding Index1}"/>
<DataGridTextColumn Header="第3" Binding="{Binding Index2}"/>
<DataGridTextColumn Header="第四" Binding="{Binding Index3}"/>
<DataGridTextColumn Header="第五" Binding="{Binding Index4}"/>
<DataGridTextColumn Header="第六" Binding="{Binding Index2}"/>
<DataGridTextColumn Header="第七" Binding="{Binding Index3}"/>
<DataGridTextColumn Header="第八" Binding="{Binding Index6}"/>
<DataGridTextColumn Header="第九" Binding="{Binding Index6}"/>
<DataGridTextColumn Header="第十" Binding="{Binding Index1}"/>
<DataGridTextColumn Header="第十一" Binding="{Binding Index6}"/>
<DataGridTextColumn Header="第十二" Binding="{Binding Index1}"/>
<DataGridTemplateColumn Header="操作">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal" Margin="6 0" VerticalAlignment="Center" HorizontalAlignment="Center">
<Button Content="修改" Margin="0" FontSize="15" Padding="0" BorderThickness="0" Background="Transparent"/>
<Button Content="删除" Padding="0" FontSize="15" Margin="20 0 0 0" BorderThickness="0" Background="Transparent"/>
</StackPanel>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</controls:DataGrid>
我们通过自定义的依赖属性RightFrozenCount来冻结右侧的列
资源样式
<Style x:Key="DesignDataGridRow2" TargetType="{x:Type DataGridRow}">
<Setter Property="Background" Value="Transparent" />
<Setter Property="SnapsToDevicePixels" Value="true" />
<Setter Property="Foreground" Value="#332E2E"/>
<Setter Property="Validation.ErrorTemplate" Value="{x:Null}" />
<Setter Property="ValidationErrorTemplate">
<Setter.Value>
<ControlTemplate>
<TextBlock
Margin="2,0,0,0"
VerticalAlignment="Center"
Foreground="#d50000"
Text="!" />
</ControlTemplate>
</Setter.Value>
</Setter>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type DataGridRow}">
<Border
x:Name="DGR_Border"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
SnapsToDevicePixels="True" >
<SelectiveScrollingGrid>
<SelectiveScrollingGrid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</SelectiveScrollingGrid.ColumnDefinitions>
<SelectiveScrollingGrid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</SelectiveScrollingGrid.RowDefinitions>
<DataGridCellsPresenter
Grid.Column="1"
ItemsPanel="{TemplateBinding ItemsPanel}"
SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" />
<DataGridDetailsPresenter
Grid.Row="1"
Grid.Column="1"
SelectiveScrollingGrid.SelectiveScrollingOrientation="{Binding AreRowDetailsFrozen, ConverterParameter={x:Static SelectiveScrollingOrientation.Vertical}, Converter={x:Static DataGrid.RowDetailsScrollingConverter}, RelativeSource={RelativeSource AncestorType={x:Type DataGrid}}}"
Visibility="{TemplateBinding DetailsVisibility}" />
<DataGridRowHeader
Grid.Row="0"
Grid.RowSpan="2"
Grid.Column="0"
SelectiveScrollingGrid.SelectiveScrollingOrientation="Vertical"
Visibility="{Binding HeadersVisibility, ConverterParameter={x:Static DataGridHeadersVisibility.Row}, Converter={x:Static DataGrid.HeadersVisibilityConverter}, RelativeSource={RelativeSource AncestorType={x:Type DataGrid}}}" />
</SelectiveScrollingGrid>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Background" Value="#b8dcff" />
<Setter Property="assist2:DataGridAssist.MouseOverItem" Value="{Binding RelativeSource={RelativeSource Mode=FindAncestor,AncestorType={x:Type controls:DataGrid}}}"/>
</Trigger>
<Trigger Property="IsSelected" Value="True">
<Setter Property="Background" Value="#b8dcff" />
</Trigger>
<!--<Trigger Property="IsNewItem" Value="True">
<Setter Property="Margin" Value="{Binding NewItemMargin, RelativeSource={RelativeSource AncestorType={x:Type DataGrid}}}" />
</Trigger>-->
</Style.Triggers>
</Style>
<Style x:Key="DesignDataGrid2" TargetType="{x:Type controls:DataGrid}">
<Setter Property="IsReadOnly" Value="True"/>
<Setter Property="Background" Value="White" />
<Setter Property="Foreground" Value="#DD000000" />
<Setter Property="BorderBrush" Value="#1F000000" />
<Setter Property="BorderThickness" Value="1" />
<Setter Property="AutoGenerateColumns" Value="False" />
<Setter Property="CanUserAddRows" Value="False" />
<Setter Property="FontSize" Value="18" />
<Setter Property="GridLinesVisibility" Value="Horizontal" />
<Setter Property="HorizontalGridLinesBrush">
<Setter.Value>
<MultiBinding Converter="{StaticResource RemoveAlphaBrushConverter}">
<Binding Path="BorderBrush" RelativeSource="{RelativeSource Self}" />
<Binding Path="Background" RelativeSource="{RelativeSource Self}" />
</MultiBinding>
</Setter.Value>
</Setter>
<Setter Property="VerticalGridLinesBrush" Value="{Binding HorizontalGridLinesBrush, RelativeSource={RelativeSource Self}}" />
<Setter Property="RowDetailsVisibilityMode" Value="VisibleWhenSelected" />
<Setter Property="HeadersVisibility" Value="Column" />
<Setter Property="ScrollViewer.CanContentScroll" Value="true" />
<Setter Property="ScrollViewer.PanningMode" Value="Both" />
<Setter Property="Stylus.IsFlicksEnabled" Value="False" />
<Setter Property="RowStyle" Value="{StaticResource DesignDataGridRow2}"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type controls:DataGrid}">
<Border
Padding="{TemplateBinding Padding}"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
CornerRadius="{TemplateBinding assist:DataGridAssist.CornerRadius}"
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" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Border BorderThickness="0 0 0 0" Grid.Column="2" Grid.RowSpan="2" BorderBrush="#1F000000" >
<ContentControl Content="{Binding RightFrozenDataGrid,RelativeSource={RelativeSource AncestorType={x:Type controls:DataGrid}}}"/>
</Border>
<Border
Grid.Row="0"
Grid.Column="1"
Visibility="{Binding HeadersVisibility, ConverterParameter={x:Static DataGridHeadersVisibility.Column}, Converter={x:Static DataGrid.HeadersVisibilityConverter}, RelativeSource={RelativeSource AncestorType={x:Type DataGrid}}}">
<DataGridColumnHeadersPresenter x:Name="PART_ColumnHeadersPresenter" />
</Border>
<ScrollContentPresenter
x:Name="PART_ScrollContentPresenter"
Grid.Row="1"
Grid.Column="0"
Grid.ColumnSpan="2"
CanContentScroll="{TemplateBinding CanContentScroll}" />
<ScrollBar
x:Name="PART_VerticalScrollBar"
Grid.Row="1"
Style="{StaticResource ScrollBarStyle}"
Grid.Column="3"
Maximum="{TemplateBinding ScrollableHeight}"
Orientation="Vertical"
ViewportSize="{TemplateBinding ViewportHeight}"
Visibility="{TemplateBinding ComputedVerticalScrollBarVisibility}"
Value="{Binding VerticalOffset, Mode=OneWay, RelativeSource={RelativeSource TemplatedParent}}" />
<Grid Grid.Row="3" Grid.Column="1">
<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"
Style="{StaticResource ScrollBarStyle}"
ViewportSize="{TemplateBinding ViewportWidth}"
Visibility="{TemplateBinding ComputedHorizontalScrollBarVisibility}"
Value="{Binding HorizontalOffset, Mode=OneWay, RelativeSource={RelativeSource TemplatedParent}}" />
</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" />
</MultiTrigger.Conditions>
<Setter Property="ScrollViewer.CanContentScroll" Value="false" />
</MultiTrigger>
</Style.Triggers>
</Style>
DataGrid.cs
public class DataGrid: System.Windows.Controls.DataGrid
{
private ScrollViewer mianScrollViewer;
private ItemsControl itemsControl;
private DataGrid rightDataGrid;
public object RightFrozenDataGrid
{
get { return (object)GetValue(RightFrozenDataGridProperty); }
set { SetValue(RightFrozenDataGridProperty, value); }
}
// Using a DependencyProperty as the backing store for RightFrozenDataGrid. This enables animation, styling, binding, etc...
public static readonly DependencyProperty RightFrozenDataGridProperty =
DependencyProperty.Register("RightFrozenDataGrid", typeof(object), typeof(DataGrid), new PropertyMetadata(null));
public object MouseOverItem
{
get { return (object)GetValue(MouseOverItemProperty); }
set { SetValue(MouseOverItemProperty, value); }
}
// Using a DependencyProperty as the backing store for MouseOverItem. This enables animation, styling, binding, etc...
public static readonly DependencyProperty MouseOverItemProperty =
DependencyProperty.Register("MouseOverItem", typeof(object), typeof(DataGrid), new PropertyMetadata(null, OnMouseOverItemChanged));
private static void OnMouseOverItemChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (d != null && d is DataGrid grid)
{
// 获取当前可见行的可视元素
if(grid.rightDataGrid != null)
{
foreach (var item in grid.Items)
{
var mainDataGridRow = grid.ItemContainerGenerator.ContainerFromItem(item) as DataGridRow;
if (mainDataGridRow != null) {
var row = grid.rightDataGrid?.ItemContainerGenerator.ContainerFromItem(item) as DataGridRow;
if (row != null) {
row.Background = mainDataGridRow.Background;
}
}
}
}
}
}
public int RightFrozenCount
{
get { return (int)GetValue(RightFrozenCountProperty); }
set { SetValue(RightFrozenCountProperty, value); }
}
// Using a DependencyProperty as the backing store for RightFrozenCount. This enables animation, styling, binding, etc...
public static readonly DependencyProperty RightFrozenCountProperty =
DependencyProperty.Register("RightFrozenCount", typeof(int), typeof(DataGrid), new PropertyMetadata(0, OnRightFrozenCountChanged));
private static void OnRightFrozenCountChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
}
public DataGrid()
{
Loaded += DataGrid_Loaded;
SelectionChanged += (e, s)=> BackgroundChanged();
MouseLeave += (e, s) => BackgroundChanged();
MouseEnter += (e, s) => BackgroundChanged();
}
private void DataGrid_Loaded(object sender, System.Windows.RoutedEventArgs e)
{
if (Items != null&& Columns.Count>0&& RightFrozenCount>0) {
rightDataGrid = new DataGrid();
rightDataGrid.Background = Brushes.Transparent;
rightDataGrid.BorderThickness = new Thickness(0);
rightDataGrid.Style = Style;
rightDataGrid.Columns.Clear();
rightDataGrid.AutoGenerateColumns = false;
for (int i = 0; i < RightFrozenCount; i++)
{
var last = Columns[Columns.Count - 1];
Columns.Remove(last);
rightDataGrid.Columns.Insert(0,last);
}
rightDataGrid.ItemsSource = ItemsSource;
rightDataGrid.HeadersVisibility = DataGridHeadersVisibility.Column;
rightDataGrid.IsReadOnly = true;
rightDataGrid.CanUserAddRows = false;
rightDataGrid.SelectionChanged += (e, s) => {
SelectedItem = rightDataGrid.SelectedItem;
};
RightFrozenDataGrid = rightDataGrid;
}
if (mianScrollViewer != null && rightDataGrid != null)
{
mianScrollViewer.ScrollChanged += (e, s) =>
{
if (rightDataGrid.GetTemplateChild("DG_ScrollViewer") is ScrollViewer scroll)
{
scroll.VerticalScrollBarVisibility = ScrollBarVisibility.Disabled;
scroll.ScrollToVerticalOffset(mianScrollViewer.VerticalOffset);
}
};
}
}
private void BackgroundChanged() {
if (rightDataGrid != null)
{
foreach (var item in Items)
{
var mainDataGridRow = ItemContainerGenerator.ContainerFromItem(item) as DataGridRow;
if (mainDataGridRow != null)
{
var row = rightDataGrid?.ItemContainerGenerator.ContainerFromItem(item) as DataGridRow;
if (row != null)
{
row.Background = mainDataGridRow.Background;
}
}
}
}
}
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
mianScrollViewer = GetTemplateChild("DG_ScrollViewer") as ScrollViewer;
if (mianScrollViewer != null) {
}
}
}
附加属性DataGridAssist.cs
public class DataGridAssist
{
public static readonly DependencyProperty MouseOverItemProperty
= DependencyProperty.RegisterAttached("MouseOverItem", typeof(object), typeof(DataGridAssist),
new PropertyMetadata(null, OnMouseOverItemChanged));
private static void OnMouseOverItemChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if ( e.NewValue is Cloud.Test.Wpf.Controls.DataGrid dataGrid) {
dataGrid.MouseOverItem = d;
}
}
public static object GetMouseOverItem(DataGridRow element)
=> (object)element.GetValue(MouseOverItemProperty);
public static void SetMouseOverItem(DataGridRow element, object value)
=> element.SetValue(MouseOverItemProperty, value);
}
源码
整合中,和其他UI界面编辑完成一起发出来