Open Siyumii opened 4 years ago
This is probably not possible. The doc says "Frozen columns are always the leftmost columns in display order". There are no hooks into DataGrid's layout that support any other policy.
You can easily change the display order of columns to put the special column at the left, where you can freeze it.
I don't know what your requirements are, but you might be able to fake it by using FlowDirection="RightToLeft"
(and fixing the rest with CellStyle
etc.). No need to say you break the built-in LTR/RTL support though.
EDIT: To clarify, it will still need to be the first column that is frozen, but it'll be frozen to the right side.
Thanks @miloush and @SamBent
I tried this alternative solution you have mentioned @miloush . Last column can be freezed.But then so many other issues are rising compared to normal WPF data-grid behavior. Such as when columns are resizing,It resizes to left side but normal grid columns are resizing to right side etc.Anyway thanks for the feedback.
@Siyumii have you thought about having two DataGrids next to each other, with the right one having just the column you want to be fixed?
@miloush I hope his approach will not be applicable for my scenario since No of columns displayed in the grid, is not static. In the application currently I'm developing, we can add and remove no of columns to be displayed. So this approach will not be better solution for this scenario.Anyway thanks again for the help !!
@Siyumii You can still do it dynamically, just not in XAML. Quick prototype:
<Window x:Class="FrozenGrid.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="450" Width="800">
<DockPanel Margin="50">
<Slider DockPanel.Dock="Bottom" Value="5" Minimum="0" Maximum="6"
TickFrequency="1" IsSnapToTickEnabled="True" ValueChanged="OnSliderSlided" />
<DataGrid DockPanel.Dock="Right" Name="_frozen" BorderThickness="0,1,1,1" AutoGenerateColumns="False" ScrollViewer.ScrollChanged="OnFrozenScrolled" HorizontalScrollBarVisibility="Visible">
<DataGrid.Columns>
<DataGridTextColumn Binding="{Binding [5]}" Header="F" />
</DataGrid.Columns>
</DataGrid>
<DataGrid Name="_grid" AutoGenerateColumns="False" VerticalScrollBarVisibility="Hidden" ScrollViewer.ScrollChanged="OnGridScrolled">
<DataGrid.Columns>
<DataGridTextColumn Binding="{Binding [0]}" Header="A" />
<DataGridTextColumn Binding="{Binding [1]}" Header="B" />
<DataGridTextColumn Binding="{Binding [2]}" Header="C" />
<DataGridTextColumn Binding="{Binding [3]}" Header="D" />
<DataGridTextColumn Binding="{Binding [4]}" Header="E" />
</DataGrid.Columns>
</DataGrid>
</DockPanel>
</Window>
using System;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
namespace FrozenGrid
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
double[][] data = new double[50][];
Random random = new Random();
for (int i = 0; i < 50; i++)
data[i] = Enumerable.Range(0, 10).Select(r => random.NextDouble()).ToArray();
_grid.ItemsSource = data;
_frozen.ItemsSource = data;
}
private ScrollViewer _gridScroll;
private ScrollViewer _frozenScroll;
private void OnGridScrolled(object sender, ScrollChangedEventArgs e) => OnScrolled(_frozen, ref _frozenScroll, e);
private void OnFrozenScrolled(object sender, ScrollChangedEventArgs e) => OnScrolled(_grid, ref _gridScroll, e);
private void OnScrolled(DataGrid targetGrid, ref ScrollViewer targetScroll, ScrollChangedEventArgs e)
{
if (targetScroll == null)
targetScroll = (ScrollViewer)VisualTreeHelper.GetChild(VisualTreeHelper.GetChild(targetGrid, 0), 0); // Grid > DG_ScrollViewer
targetScroll.ScrollToVerticalOffset(e.VerticalOffset);
}
private void OnSliderSlided(object sender, RoutedPropertyChangedEventArgs<double> e)
{
if (!IsInitialized)
return;
if (e.NewValue > e.OldValue)
{
int freeColumns = (int)(e.NewValue - e.OldValue);
for (int i = 0; i < freeColumns; i++)
{
DataGridColumn c = _frozen.Columns.First();
_frozen.Columns.Remove(c);
_grid.Columns.Add(c);
}
}
else
{
int fixColumns = (int)(e.OldValue - e.NewValue);
for (int i = 0; i < fixColumns; i++)
{
DataGridColumn c = _grid.Columns.Last();
_grid.Columns.Remove(c);
_frozen.Columns.Insert(0, c);
}
}
_grid.Visibility = _grid.Columns.Count > 0 ? Visibility.Visible : Visibility.Collapsed;
_frozen.Visibility = _frozen.Columns.Count > 0 ? Visibility.Visible : Visibility.Collapsed;
_grid.VerticalScrollBarVisibility = _frozen.Columns.Count < 1 ? ScrollBarVisibility.Visible : ScrollBarVisibility.Hidden;
}
}
}
Move the slider to adjust the number of frozen columns. You could turn this into a user control for simple reuse.
@miloush tried your solution - interesting! - but am getting weird behavior. After I scroll (using the mouse wheel) just a bit it jumps to offset 0. Any ideas?
PS - I believe it has to do with row virtualization.
@Siyumii Hi! you can define a class that derived from DataGridCellsPanel
and override the ArrangeOverride
method to calculate the arrangement of the last few columns to frozen them. The code is like this.
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
namespace Framework.Controls
{
public class RightFrozenSupportedDataGridCellsPanel : DataGridCellsPanel
{
private bool _isVisualUpdated;
private DataGrid _parentDataGrid;
private ScrollViewer _scrollViewer;
private Style _frozenColumnCellsStyle;
private int _rightFrozenColumnCount;
private double _rowHeaderWidth;
private double _verticalScrollBarWidth;
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
ObtainVisualElement();
}
private bool ObtainVisualElement()
{
if (_parentDataGrid == null)
{
if ((_parentDataGrid = this.FindVisualParent<DataGrid>()) == null)
{
return false;
}
}
if (_scrollViewer == null)
{
if ((_scrollViewer = this.FindVisualParent<ScrollViewer>()) == null)
{
return false;
}
}
_frozenColumnCellsStyle = DataGridExt.GetFrozenColumnCellsStyle(_parentDataGrid);
return true;
}
private void UpdateFrozenColumnsVisual()
{
var visualNeedUpdate = DataGridExt.GetIsRightFrozenColumnCountChanged(_parentDataGrid);
if (visualNeedUpdate == false && _isVisualUpdated)
{
return;
}
_rightFrozenColumnCount = DataGridExt.GetRightFrozenColumnCount(_parentDataGrid);
_rowHeaderWidth = (double)TryFindResource(DataGridExt.DataGridRowHeaderWidthKey);
_verticalScrollBarWidth = SystemParameters.VerticalScrollBarWidth + 2;
for (var i = 1; i <= _rightFrozenColumnCount; i++)
{
var index = InternalChildren.Count - i;
var column = _parentDataGrid.Columns[index];
var child = InternalChildren[index];
column.CanUserResize = false;
column.CanUserReorder = false;
if (child is DataGridCell cell && _frozenColumnCellsStyle != null)
{
cell.Style = _frozenColumnCellsStyle;
}
if (i == _rightFrozenColumnCount)
{
var control = (Control)child;
control.Margin = new Thickness(-1, 0, 0, 0);
if (control is DataGridCell)
{
control.BorderThickness = new Thickness(1, 0, 0, 0);
}
else if (control is DataGridColumnHeader)
{
control.BorderThickness = new Thickness(1, 0, 1, 1);
}
}
}
_isVisualUpdated = true;
}
protected override Size ArrangeOverride(Size arrangeSize)
{
var baseSize = base.ArrangeOverride(arrangeSize);
TryFrozenRightColumns(arrangeSize);
return baseSize;
}
private void TryFrozenRightColumns(Size arrangeSize)
{
if (ObtainVisualElement() == false)
{
return;
}
UpdateFrozenColumnsVisual();
if (_rightFrozenColumnCount == 0)
{
return;
}
if (_parentDataGrid.HeadersVisibility == DataGridHeadersVisibility.Column || _parentDataGrid.HeadersVisibility == DataGridHeadersVisibility.None)
{
_rowHeaderWidth = 0;
}
if (_scrollViewer.ComputedVerticalScrollBarVisibility == Visibility.Collapsed)
{
_verticalScrollBarWidth = 2;
}
var horizontalScrollOffset = (double)_parentDataGrid.GetProperty("HorizontalScrollOffset");
var rowVisualWidth = _parentDataGrid.ActualWidth - _rowHeaderWidth - _verticalScrollBarWidth;
var offset = rowVisualWidth + horizontalScrollOffset;
for (var i = 1; i <= _rightFrozenColumnCount; i++)
{
var index = InternalChildren.Count - i;
var column = _parentDataGrid.Columns[index];
var columnWidth = column.Width;
var child = InternalChildren[index];
var point1 = new Point(offset - columnWidth.DisplayValue, 0);
var point2 = new Point(offset, arrangeSize.Height);
if (i == _rightFrozenColumnCount)
{
// add 1px offset to the first column to frozen
point1 = new Point(offset - columnWidth.DisplayValue - 1, 0);
}
child.Arrange(new Rect(point1, point2));
offset -= columnWidth.DisplayValue;
}
}
}
}
The class DataGridExt
is like this.
using System.Windows;
using System.Windows.Controls;
namespace Framework.Controls.Extensions
{
public static class DataGridExt
{
public static readonly DependencyProperty RightFrozenColumnCountProperty = DependencyProperty.RegisterAttached("RightFrozenColumnCount", typeof(int), typeof(DataGridExt), new PropertyMetadata(0, RightFrozenColumnCountChangedCallback));
private static void RightFrozenColumnCountChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
SetIsRightFrozenColumnCountChanged(d, true);
}
[AttachedPropertyBrowsableForType(typeof(DataGrid))]
public static int GetRightFrozenColumnCount(DependencyObject obj)
{
return (int)obj.GetValue(RightFrozenColumnCountProperty);
}
public static void SetRightFrozenColumnCount(DependencyObject obj, int value)
{
obj.SetValue(RightFrozenColumnCountProperty, value);
}
internal static readonly DependencyProperty IsRightFrozenColumnCountChangedProperty = DependencyProperty.RegisterAttached("IsRightFrozenColumnCountChanged", typeof(bool), typeof(DataGridExt), new PropertyMetadata(default));
internal static bool GetIsRightFrozenColumnCountChanged(DependencyObject obj)
{
return (bool)obj.GetValue(IsRightFrozenColumnCountChangedProperty);
}
private static void SetIsRightFrozenColumnCountChanged(DependencyObject obj, bool value)
{
obj.SetValue(IsRightFrozenColumnCountChangedProperty, value);
}
public static readonly DependencyProperty FrozenColumnCellsStyleProperty = DependencyProperty.RegisterAttached("FrozenColumnCellsStyle", typeof(Style), typeof(DataGridExt), new PropertyMetadata(default));
[AttachedPropertyBrowsableForType(typeof(DataGrid))]
public static Style GetFrozenColumnCellsStyle(DependencyObject obj)
{
return (Style)obj.GetValue(FrozenColumnCellsStyleProperty);
}
public static void SetFrozenColumnCellsStyle(DependencyObject obj, Style value)
{
obj.SetValue(FrozenColumnCellsStyleProperty, value);
}
public static ResourceKey DataGridRowHeaderWidthKey { get; } = new ComponentResourceKey(typeof(DataGridExt), "DataGridRowHeaderWidth");
}
}
And then, you should re-define the template of DataGrid
and change the ItemsPanel of DataGridRow
and DataGridColumnHeaderPresenter
to the RightFrozenSupportedDataGridCellsPanel
.
LOOK HERE.
Love it! The option to subclass WPF controls, and especially their component parts, is too often overlooked. A shame so much of UWP/WinUI is sealed
.
DataGridColumnHeaderPresenter
Hi, can you upload a demo with source code? thank u : )
"Hi, can you upload a demo with source code? thank u : )"
Sorry @hwybao. I can only offer the code of xaml. You should focus on the usings of class RightFrozenSupportedDataGridCellsPanel. I hope it could help.
<Style x:Key="DataGridRowStyle" TargetType="{x:Type DataGridRow}">
<Setter Property="Height" Value="{DynamicResource DataGrid.Row.Height}"/>
<Setter Property="VerticalContentAlignment" Value="Center"/>
<Setter Property="FocusVisualStyle" Value="{x:Null}"/>
<Setter Property="Foreground" Value="{DynamicResource DataGrid.Row.Foreground}" />
<Setter Property="Background" Value="{DynamicResource DataGrid.Row.Background}" />
<Setter Property="BorderBrush" Value="{DynamicResource DataGrid.BorderBrush}"/>
<Setter Property="BorderThickness" Value="0,0,1,0" />
<Setter Property="ItemsPanel">
<Setter.Value>
<ItemsPanelTemplate>
<controls:RightFrozenSupportedDataGridCellsPanel></controls:RightFrozenSupportedDataGridCellsPanel>
</ItemsPanelTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Foreground" Value="{DynamicResource DataGrid.Row.Foreground.Hover}"/>
<Setter Property="Background" Value="{DynamicResource DataGrid.Row.Background.Hover}"/>
</Trigger>
<Trigger Property="IsSelected" Value="True">
<Setter Property="Foreground" Value="{DynamicResource DataGrid.Row.Foreground.Selected}"/>
<Setter Property="Background" Value="{DynamicResource DataGrid.Row.Background.Selected}"/>
</Trigger>
</Style.Triggers>
</Style>
<ControlTemplate x:Key="DataGridScrollViewerTemplate" 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"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<!-- 全选按钮:0,0 -->
<controls:DataGridSelectAllButton Style="{StaticResource {x:Type CheckBox}}"
Width="{DynamicResource {x:Static extensions:DataGridExt.DataGridRowHeaderWidthKey}}"
Background="{DynamicResource DataGrid.Header.Background}"
Foreground="{DynamicResource DataGrid.Header.Foreground}"
BorderBrush="{DynamicResource DataGrid.BorderBrush}"
BorderThickness="0,0,1,1"
IsThreeState="True"
HorizontalContentAlignment="Center"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Focusable="false"
Visibility="{Binding RelativeSource={RelativeSource AncestorType={x:Type DataGrid}}, Path=HeadersVisibility, Converter={x:Static DataGrid.HeadersVisibilityConverter}, ConverterParameter={x:Static DataGridHeadersVisibility.All}}" />
<!-- 列标题栏:0,1 -->
<DataGridColumnHeadersPresenter Name="PART_ColumnHeadersPresenter"
Grid.Row="0" Grid.Column="1"
Visibility="{Binding RelativeSource={RelativeSource AncestorType={x:Type DataGrid}}, Path=HeadersVisibility, Converter={x:Static DataGrid.HeadersVisibilityConverter}, ConverterParameter={x:Static DataGridHeadersVisibility.Column}}">
<DataGridColumnHeadersPresenter.ItemsPanel>
<ItemsPanelTemplate>
<controls:RightFrozenSupportedDataGridCellsPanel></controls:RightFrozenSupportedDataGridCellsPanel>
</ItemsPanelTemplate>
</DataGridColumnHeadersPresenter.ItemsPanel>
</DataGridColumnHeadersPresenter>
<!-- 列标题与垂直滚动条夹角的空白区域:0,3 -->
<Border Grid.Row="0" Grid.Column="3"
Background="{DynamicResource DataGrid.Header.Background}"
BorderBrush="{DynamicResource DataGrid.BorderBrush}"
BorderThickness="1,0,0,1"
Margin="-1,0,0,0"/>
<!-- 滚动内容区域:1,0(2) -->
<ScrollContentPresenter x:Name="PART_ScrollContentPresenter"
Grid.Row="1" Grid.Column="0" Grid.ColumnSpan="2"
CanContentScroll="{TemplateBinding CanContentScroll}" />
<!-- 垂直滚动条:1,3 -->
<ScrollBar Grid.Row="1" Grid.Column="3" Name="PART_VerticalScrollBar"
Orientation="Vertical"
Maximum="{TemplateBinding ScrollableHeight}"
ViewportSize="{TemplateBinding ViewportHeight}"
Value="{Binding Path=VerticalOffset, RelativeSource={RelativeSource TemplatedParent}, Mode=OneWay}"
Visibility="{TemplateBinding ComputedVerticalScrollBarVisibility}"/>
<!-- 水平滚动条:2,1 -->
<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>
"Hi, can you upload a demo with source code? thank u : )"
Sorry @hwybao. I can only offer the code of xaml. You should focus on the usings of class RightFrozenSupportedDataGridCellsPanel. I hope it could help.
<Style x:Key="DataGridRowStyle" TargetType="{x:Type DataGridRow}"> <Setter Property="Height" Value="{DynamicResource DataGrid.Row.Height}"/> <Setter Property="VerticalContentAlignment" Value="Center"/> <Setter Property="FocusVisualStyle" Value="{x:Null}"/> <Setter Property="Foreground" Value="{DynamicResource DataGrid.Row.Foreground}" /> <Setter Property="Background" Value="{DynamicResource DataGrid.Row.Background}" /> <Setter Property="BorderBrush" Value="{DynamicResource DataGrid.BorderBrush}"/> <Setter Property="BorderThickness" Value="0,0,1,0" /> <Setter Property="ItemsPanel"> <Setter.Value> <ItemsPanelTemplate> <controls:RightFrozenSupportedDataGridCellsPanel></controls:RightFrozenSupportedDataGridCellsPanel> </ItemsPanelTemplate> </Setter.Value> </Setter> <Style.Triggers> <Trigger Property="IsMouseOver" Value="True"> <Setter Property="Foreground" Value="{DynamicResource DataGrid.Row.Foreground.Hover}"/> <Setter Property="Background" Value="{DynamicResource DataGrid.Row.Background.Hover}"/> </Trigger> <Trigger Property="IsSelected" Value="True"> <Setter Property="Foreground" Value="{DynamicResource DataGrid.Row.Foreground.Selected}"/> <Setter Property="Background" Value="{DynamicResource DataGrid.Row.Background.Selected}"/> </Trigger> </Style.Triggers> </Style> <ControlTemplate x:Key="DataGridScrollViewerTemplate" 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"/> <ColumnDefinition Width="Auto"/> </Grid.ColumnDefinitions> <!-- 全选按钮:0,0 --> <controls:DataGridSelectAllButton Style="{StaticResource {x:Type CheckBox}}" Width="{DynamicResource {x:Static extensions:DataGridExt.DataGridRowHeaderWidthKey}}" Background="{DynamicResource DataGrid.Header.Background}" Foreground="{DynamicResource DataGrid.Header.Foreground}" BorderBrush="{DynamicResource DataGrid.BorderBrush}" BorderThickness="0,0,1,1" IsThreeState="True" HorizontalContentAlignment="Center" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Focusable="false" Visibility="{Binding RelativeSource={RelativeSource AncestorType={x:Type DataGrid}}, Path=HeadersVisibility, Converter={x:Static DataGrid.HeadersVisibilityConverter}, ConverterParameter={x:Static DataGridHeadersVisibility.All}}" /> <!-- 列标题栏:0,1 --> <DataGridColumnHeadersPresenter Name="PART_ColumnHeadersPresenter" Grid.Row="0" Grid.Column="1" Visibility="{Binding RelativeSource={RelativeSource AncestorType={x:Type DataGrid}}, Path=HeadersVisibility, Converter={x:Static DataGrid.HeadersVisibilityConverter}, ConverterParameter={x:Static DataGridHeadersVisibility.Column}}"> <DataGridColumnHeadersPresenter.ItemsPanel> <ItemsPanelTemplate> <controls:RightFrozenSupportedDataGridCellsPanel></controls:RightFrozenSupportedDataGridCellsPanel> </ItemsPanelTemplate> </DataGridColumnHeadersPresenter.ItemsPanel> </DataGridColumnHeadersPresenter> <!-- 列标题与垂直滚动条夹角的空白区域:0,3 --> <Border Grid.Row="0" Grid.Column="3" Background="{DynamicResource DataGrid.Header.Background}" BorderBrush="{DynamicResource DataGrid.BorderBrush}" BorderThickness="1,0,0,1" Margin="-1,0,0,0"/> <!-- 滚动内容区域:1,0(2) --> <ScrollContentPresenter x:Name="PART_ScrollContentPresenter" Grid.Row="1" Grid.Column="0" Grid.ColumnSpan="2" CanContentScroll="{TemplateBinding CanContentScroll}" /> <!-- 垂直滚动条:1,3 --> <ScrollBar Grid.Row="1" Grid.Column="3" Name="PART_VerticalScrollBar" Orientation="Vertical" Maximum="{TemplateBinding ScrollableHeight}" ViewportSize="{TemplateBinding ViewportHeight}" Value="{Binding Path=VerticalOffset, RelativeSource={RelativeSource TemplatedParent}, Mode=OneWay}" Visibility="{TemplateBinding ComputedVerticalScrollBarVisibility}"/> <!-- 水平滚动条:2,1 --> <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>
Thank you so much!!! @liwuqingxin I implemented this function by referring to your code.
LOOK HERE.
Can you give me the project please? thank you
Can you give me the project please? thank you
@LeminhDong, sorry for that I don't have a clean demo project for it. All related code about it has been shown above. If you have any problems about it I am glad to discuss them with you 😄.
While there has been some good workarounds posted in this thread, they are not trivial and I think it might be worth considering some in-box support, such as allowing negative FreezeColumnCount values or some kind of FreezeDirection property.
@liwuqingxin, i copied your code and try to build, but build is failing due to following things, kindly help if possible.
kindly prepare one simple DataGrid Project and share here.
Can you give me the project please? thank you
@LeMinhDong, sorry for that I don't have a clean demo project for it. All related code about it has been shown above. If you have any problems about it I am glad to discuss them with you 😄.
@vikasjain6 @LeMinhDong DataGrid.RightFrozen.Demo by NLNet 2022.10.13.zip is a simple dmeo of that.
@liwuqingxin thanks a lot for great sample application
@vikasjain6 @LeMinhDong DataGrid.RightFrozen.Demo by NLNet 2022.10.13.zip is a simple dmeo of that.
While everyone is still waiting for an out of the box solution, I found the solution from miloush to be pretty clever. I wrote a small Behavior
for anyone who would like to use this quick workaround.
ReactiveExtensions.cs
/// <summary>
/// Subscribes to events using reflection. You're welcome :)
/// </summary>
public static class ReactiveExtensions
{
private static EventHandler<TEvent> CreateGenericHandler<TEvent>(object target, MethodInfo method)
{
return (EventHandler<TEvent>)Delegate.CreateDelegate(typeof(EventHandler<TEvent>),
target, method);
}
private static EventHandler CreateHandler(object target, MethodInfo method)
{
return (EventHandler)Delegate.CreateDelegate(typeof(EventHandler),
target, method);
}
private static RoutedEventHandler CreateRoutedHandler(object target, MethodInfo method)
{
return (RoutedEventHandler)Delegate.CreateDelegate(typeof(RoutedEventHandler),
target, method);
}
public static Delegate BindEventToAction(this EventInfo eventInfo, object target, Delegate action)
{
MethodInfo method;
if (eventInfo.EventHandlerType.IsGenericType)
{
method = typeof(ReactiveExtensions)
.GetMethod(nameof(CreateGenericHandler))
.MakeGenericMethod(
eventInfo.EventHandlerType.GetGenericArguments());
}
else if (eventInfo.EventHandlerType == typeof(RoutedEventHandler))
{
method = typeof(ReactiveExtensions).GetMethod(nameof(CreateRoutedHandler));
}
else
{
method = typeof(ReactiveExtensions).GetMethod(nameof(CreateHandler));
}
Delegate @delegate = (Delegate)method.Invoke(null, new object[] { action.Target, action.Method });
eventInfo.AddEventHandler(target, @delegate);
return @delegate;
}
public static void SubscribeOrSkip<T>(this T entity, string eventName, Predicate<T> skipCondition, Action handler = null)
{
if (skipCondition(entity))
{
handler?.Invoke();
return;
}
Delegate @delegate = null;
EventInfo eventInfo = typeof(T).GetEvent(eventName);
if (eventInfo == null)
throw new InvalidOperationException($"Failed to subscribe to {eventName} event on type {typeof(T).Name}");
Action<object, object> action = (s, e) =>
{
eventInfo.RemoveEventHandler(entity, @delegate);
handler?.Invoke();
};
@delegate = BindEventToAction(eventInfo, entity, action);
}
}
MirrorScrollBehavior.cs
public class MirrorScrollBehavior : Behavior<ItemsControl>
{
private ScrollViewer _targetScroll;
private ScrollViewer _associatedScroll;
public ItemsControl Target
{
get { return (ItemsControl)GetValue(TargetProperty); }
set { SetValue(TargetProperty, value); }
}
public static readonly DependencyProperty TargetProperty =
DependencyProperty.Register("Target", typeof(ItemsControl), typeof(MirrorScrollBehavior), new PropertyMetadata(null, HandleTargetChanged));
private static void HandleTargetChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (d is not MirrorScrollBehavior beh) return;
beh.HandleTargetChangedInternal(e);
}
private void HandleTargetChangedInternal(DependencyPropertyChangedEventArgs e)
{
if (_targetScroll != null)
{
_targetScroll.ScrollChanged -= HandleTargetScroll;
}
if (e.NewValue is not ItemsControl target) return;
// Ensures that the ItemsControl has been loaded
target.SubscribeOrSkip(nameof(ItemsControl.Loaded), (x) => x.IsLoaded, () =>
{
_targetScroll = UIHelper.FindVisualChildren<ScrollViewer>(target).FirstOrDefault();
if (_targetScroll != null)
{
_targetScroll.ScrollChanged += HandleTargetScroll;
if (_associatedScroll != null)
_targetScroll.ScrollToVerticalOffset(_associatedScroll.VerticalOffset);
}
});
}
protected override void OnAttached()
{
ItemsControl associated = AssociatedObject;
// Ensures that the ItemsControl has been loaded
associated.SubscribeOrSkip(nameof(ItemsControl.Loaded), (x) => x.IsLoaded, () =>
{
_associatedScroll = UIHelper.FindVisualChildren<ScrollViewer>(associated).FirstOrDefault();
if (_associatedScroll != null)
{
_associatedScroll.ScrollChanged += HandleAssociatedScroll;
}
});
base.OnAttached();
}
protected override void OnDetaching()
{
if (_associatedScroll != null)
{
_associatedScroll.ScrollChanged -= HandleAssociatedScroll;
}
if (_targetScroll != null)
{
_targetScroll.ScrollChanged -= HandleAssociatedScroll;
}
_associatedScroll = _targetScroll = null;
base.OnDetaching();
}
private void HandleAssociatedScroll(object sender, ScrollChangedEventArgs e)
{
if (_targetScroll == null) return;
if (_targetScroll.VerticalOffset == e.VerticalOffset) return;
_targetScroll.ScrollToVerticalOffset(e.VerticalOffset);
}
private void HandleTargetScroll(object sender, ScrollChangedEventArgs e)
{
if (_associatedScroll == null) return;
if (_associatedScroll.VerticalOffset == e.VerticalOffset) return;
_associatedScroll.ScrollToVerticalOffset(e.VerticalOffset);
}
}
Now your ListBox
or DataGrid
will always scroll together
<UserControl
xmlns:i="http://schemas.microsoft.com/xaml/behaviors"
>
<DockPanel>
<DataGrid Name="frozenGrid" DockPanel.Dock="Right">
</DataGrid>
<DataGrid SelectedItem="{Binding ElementName=frozenGrid, Path=SelectedItem, Mode=TwoWay}"
SelectionMode="Extended" SelectionUnit="FullRow">
<i:Interaction.Behaviors>
<ui:MirrorScrollBehavior Target="{Binding ElementName=frozenGrid}" />
</i:Interaction.Behaviors>
</DataGrid>
</DockPanel>
</UserControl>
Note: RowHeight will have to be fixed for both controls
I need to freeze last column of the WPF datagrid. When I set the FrozenColumnCount as 1, then it will freeze only leftmost column but I need to freeze right most column. (that means I need to prevent column from moving). How to achieve this? Seems like need to write custom extension. But I don't have any idea of how to do that? Any guidance or advice will be much appreciated!