I want a WPF datagrid which can select the rows same as we can do in Win explorer. For the i have implemented some code and need some help in that.
Here the autoscroll for up direction is working fine but for downside autoscroll its not working. Can anyone help me in this?
What I have tried:
<pre>namespace DataGridDragSelectExample
{
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.TextFormatting;
using System.Windows.Threading;
public class DragSelectDataGrid : DataGrid
{
private readonly SelectionAdorner selectionAdorner;
private AdornerLayer adornerLayer;
private double actualRowHeight;
private ScrollViewer scrollViewer;
private bool canPerfromDragSelect;
private readonly HashSet<object> dragSelectedItemsTable;
private readonly Stack<object> dragSelectedItemsHistory;
private Point selectionStartPoint;
private Point oldMousePosition;
private DataGridColumnHeadersPresenter PART_ColumnHeadersPresenter;
private ScrollBar PART_HorizontalScrollBar;
private ScrollBar PART_VerticalScrollBar;
private bool isVisualDragSelectActive;
private bool isCustomSelectionActive;
private ScrollDirection autoScrollDirection;
private const int AutoScrollStep = 1;
private readonly DispatcherTimer autoScrollTimer;
internal enum ScrollDirection
{
Undefined = 0,
Up,
Down
}
public DragSelectDataGrid()
{
this.selectionAdorner = new SelectionAdorner(this);
this.SelectionMode = DataGridSelectionMode.Extended;
this.Loaded += OnLoaded;
this.canPerfromDragSelect = true;
this.dragSelectedItemsTable = new HashSet<object>();
this.dragSelectedItemsHistory = new Stack<object>();
this.autoScrollTimer = new DispatcherTimer(DispatcherPriority.Background, this.Dispatcher)
{
Interval = TimeSpan.FromMilliseconds(100)
};
this.Unloaded += OnUnloaded;
}
private void OnUnloaded(object sender, RoutedEventArgs e)
{
this.autoScrollTimer.Stop();
this.autoScrollTimer.Tick -= AutoScrollTimer_Tick;
}
private T FindVisualChild<T>(DependencyObject parent) where T : DependencyObject
{
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(parent); i++)
{
var child = VisualTreeHelper.GetChild(parent, i);
if (child is T typedChild)
{
return typedChild;
}
else
{
T foundChild = FindVisualChild<T>(child);
if (foundChild != null)
{
return foundChild;
}
}
}
return null;
}
private void OnLoaded(object sender, RoutedEventArgs e)
{
var scrollViewer = FindVisualChild<ScrollViewer>(this);
if (scrollViewer != null)
{
var scrollContentPresenter = FindVisualChild<ScrollContentPresenter>(scrollViewer);
if (scrollContentPresenter != null)
{
scrollContentPresenter.MouseLeave += OnScrollContentPresenterMouseLeave;
scrollContentPresenter.MouseEnter += OnScrollContentPresenterMouseEnter;
}
}
Window parentWindow = Window.GetWindow(this);
parentWindow.AddHandler(PreviewMouseLeftButtonUpEvent, new MouseButtonEventHandler(OnWindowPreviewMouseLeftButtonUp));
this.adornerLayer = AdornerLayer.GetAdornerLayer(this);
if (this.adornerLayer == null)
{
throw new InvalidOperationException("No AdornerDecorator found in the parent tree.");
}
EnsureScrollViewer();
}
private void EnsureScrollViewer()
{
if (this.scrollViewer is null)
{
StopAutoScroll();
throw new InvalidOperationException("No ScrollViewer found");
}
if (!this.scrollViewer.CanContentScroll)
{
StopAutoScroll();
throw new NotSupportedException("ScrollViewer.CanContentScroll must be set to TRUE.");
}
}
private void OnScrollContentPresenterMouseLeave(object sender, MouseEventArgs e)
{
var scrollContentPresenter = (ScrollContentPresenter)sender;
Rect scrollContentPresenterBounds = LayoutInformation.GetLayoutSlot(scrollContentPresenter);
var currentMousePosition = e.GetPosition(scrollContentPresenter);
if (currentMousePosition.Y >= scrollContentPresenterBounds.Bottom && e.LeftButton==MouseButtonState.Pressed)
{
StartAutoScroll(ScrollDirection.Down);
}
else
{
if (e.LeftButton == MouseButtonState.Pressed)
StartAutoScroll(ScrollDirection.Up);
}
}
private void OnScrollContentPresenterMouseEnter(object sender, MouseEventArgs e)
=> StopAutoScroll();
private void StartAutoScroll(ScrollDirection scrollDirection)
{
EnsureScrollViewer();
if (!CanAutoScroll())
{
return;
}
this.autoScrollDirection = scrollDirection;
this.autoScrollTimer.Tick += AutoScrollTimer_Tick;
this.autoScrollTimer.Start();
}
private void StopAutoScroll()
{
this.autoScrollTimer.Stop();
this.autoScrollTimer.Tick -= AutoScrollTimer_Tick;
}
private void AutoScrollTimer_Tick(object sender, EventArgs e)
{
if (!CanAutoScroll())
{
StopAutoScroll();
return;
}
double newVerticalOffset = 0;
switch (this.autoScrollDirection)
{
case ScrollDirection.Up:
newVerticalOffset = this.scrollViewer.VerticalOffset - DragSelectDataGrid.AutoScrollStep;
this.scrollViewer.ScrollToVerticalOffset(newVerticalOffset);
break;
case ScrollDirection.Down:
newVerticalOffset = this.scrollViewer.VerticalOffset + DragSelectDataGrid.AutoScrollStep;
this.scrollViewer.ScrollToVerticalOffset(newVerticalOffset);
break;
default:
throw new NotImplementedException();
}
this.scrollViewer.ScrollToVerticalOffset(newVerticalOffset);
var currentRowContainer = (DataGridRow)this.ItemContainerGenerator.ContainerFromIndex((int)newVerticalOffset);
currentRowContainer.IsSelected = true;
}
private bool CanAutoScroll()
{
EnsureScrollViewer();
if ((this.autoScrollDirection is ScrollDirection.Up && this.scrollViewer.VerticalOffset == 0)
|| (this.autoScrollDirection is ScrollDirection.Down && this.scrollViewer.VerticalOffset >= this.scrollViewer.ScrollableHeight)) {
return false;
}
return true;
}
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
if (!this.TryFindVisualChild(out this.scrollViewer))
{
throw new InvalidOperationException($"No {typeof(ScrollViewer).FullName} found!");
}
this.scrollViewer.ScrollChanged += OnScrollChanged;
if (this.scrollViewer.IsLoaded)
{
EnsureDataGridColumnHeadersPresenter();
}
else
{
this.scrollViewer.Loaded += OnScrollViewerLoaded;
}
}
private void OnScrollViewerLoaded(object sender, RoutedEventArgs e)
{
_ = this.scrollViewer.TryFindVisualChildElementByName(nameof(this.PART_HorizontalScrollBar), out this.PART_HorizontalScrollBar);
_ = this.scrollViewer.TryFindVisualChildElementByName(nameof(this.PART_HorizontalScrollBar), out this.PART_VerticalScrollBar);
EnsureDataGridColumnHeadersPresenter();
}
private void EnsureDataGridColumnHeadersPresenter()
{
if (!this.scrollViewer.TryFindVisualChild(out this.PART_ColumnHeadersPresenter))
{
throw new InvalidOperationException($"No {typeof(DataGridColumnHeadersPresenter).FullName} found!");
}
}
protected override void OnPreviewMouseDown(MouseButtonEventArgs e)
{
base.OnPreviewMouseDown(e);
this.isCustomSelectionActive = this.canPerfromDragSelect;
}
protected override void OnPreviewMouseMove(MouseEventArgs e)
{
base.OnPreviewMouseMove(e);
if (e.LeftButton != MouseButtonState.Pressed || this.isCustomSelectionActive)
{
return;
}
Point mousePosition = e.GetPosition(this);
Rect selectionBounds = GetSelectionBounds();
if (!selectionBounds.Contains(mousePosition))
{
return;
}
if (this.isVisualDragSelectActive)
{
Point selectionEndPoint = mousePosition;
UpdateVisualDragSelect(selectionEndPoint);
}
else
{
this.selectionStartPoint = mousePosition;
StartVisualDragSelect(this.selectionStartPoint);
}
}
protected override void OnBeginningEdit(DataGridBeginningEditEventArgs e)
{
base.OnBeginningEdit(e);
this.canPerfromDragSelect = false;
}
protected override void OnCellEditEnding(DataGridCellEditEndingEventArgs e)
{
base.OnCellEditEnding(e);
this.canPerfromDragSelect = true;
}
protected override void PrepareContainerForItemOverride(DependencyObject element, object item)
{
base.PrepareContainerForItemOverride(element, item);
if (element is UIElement uIElement)
{
uIElement.MouseEnter += OnDataGridRowMouseEnter;
uIElement.PreviewMouseLeftButtonDown += OnDataGridRowPreviewMouseLeftButtonDown;
}
}
protected override void ClearContainerForItemOverride(DependencyObject element, object item)
{
base.ClearContainerForItemOverride(element, item);
if (element is UIElement uIElement)
{
uIElement.MouseEnter -= OnDataGridRowMouseEnter;
uIElement.PreviewMouseLeftButtonDown -= OnDataGridRowPreviewMouseLeftButtonDown;
}
}
protected override void OnSelectionChanged(SelectionChangedEventArgs e)
{
base.OnSelectionChanged(e);
if (!this.isCustomSelectionActive)
{
this.dragSelectedItemsTable.Clear();
this.dragSelectedItemsHistory.Clear();
foreach (object item in this.SelectedItems)
{
this.dragSelectedItemsHistory.Push(item);
this.dragSelectedItemsTable.Add(item);
}
}
}
protected override void OnLoadingRow(DataGridRowEventArgs e)
{
base.OnLoadingRow(e);
if (e.Row.ActualHeight == 0 || this.actualRowHeight == 0)
{
return;
}
this.actualRowHeight = e.Row.ActualHeight;
}
private void OnDataGridRowMouseEnter(object sender, MouseEventArgs e)
{
if (e.LeftButton != MouseButtonState.Pressed || !this.isCustomSelectionActive)
{
return;
}
var rowItemContainer = (DataGridRow)sender;
HandleItemSelect(rowItemContainer);
}
private void HandleItemSelect(DependencyObject rowItemContainer)
{
object rowItem = this.ItemContainerGenerator.ItemFromContainer(rowItemContainer);
bool isItemContainerAlreadyVisited = this.dragSelectedItemsTable.Contains(rowItem);
if (isItemContainerAlreadyVisited)
{
if (this.dragSelectedItemsTable.Count <= 1)
{
return;
}
UnselectItem();
}
else
{
SelectItem(rowItem);
}
}
private void UnselectItem()
{
object unselectedRowItem = this.dragSelectedItemsHistory.Pop();
_ = this.dragSelectedItemsTable.Remove(unselectedRowItem);
this.SelectedItems.Remove(unselectedRowItem);
}
private void SelectItem(object rowItem)
{
this.dragSelectedItemsHistory.Push(rowItem);
_ = this.dragSelectedItemsTable.Add(rowItem);
this.SelectedItems.Add(rowItem);
}
private void OnDataGridRowPreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
this.isCustomSelectionActive = false;
Point selectionStartPoint = e.GetPosition(this);
StartVisualDragSelect(selectionStartPoint);
}
private void UpdateVisualDragSelect(Point selectionEndPoint)
{
this.selectionAdorner.SelectionBounds = GetSelectionBounds();
this.selectionAdorner.SetEndPoint(selectionEndPoint);
}
private void OnScrollChanged(object sender, ScrollChangedEventArgs e)
{
if (this.actualRowHeight == 0)
{
if (!double.IsNaN(this.RowHeight))
{
this.actualRowHeight = this.RowHeight;
}
else
{
var rowItemContainer = (FrameworkElement)this.ItemContainerGenerator.ContainerFromIndex((int)this.scrollViewer.VerticalOffset);
this.actualRowHeight = rowItemContainer?.ActualHeight ?? 0;
}
}
double verticalOffset = this.scrollViewer.CanContentScroll
? this.actualRowHeight * e.VerticalChange
: e.VerticalChange;
double horizontalOffset = e.HorizontalChange;
this.selectionAdorner?.Move(-horizontalOffset, -verticalOffset);
}
private void OnWindowPreviewMouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
StopVisualDragSelect();
this.isCustomSelectionActive = false;
}
private void ClearSelection()
{
this.dragSelectedItemsHistory.Clear();
this.dragSelectedItemsTable.Clear();
this.SelectedItems.Clear();
}
private void StopVisualDragSelect()
{
this.adornerLayer.Remove(this.selectionAdorner);
this.isVisualDragSelectActive = false;
}
private void StartVisualDragSelect(Point selectionStartPoint)
{
if (!this.canPerfromDragSelect)
{
return;
}
this.selectionAdorner.SelectionBounds = GetSelectionBounds();
this.selectionAdorner.SetStartPoint(selectionStartPoint);
this.selectionAdorner.SetEndPoint(selectionStartPoint);
this.adornerLayer.Add(this.selectionAdorner);
this.isVisualDragSelectActive = true;
}
private Rect GetSelectionBounds()
{
Rect bounds = LayoutInformation.GetLayoutSlot(this);
double columnHeaderHeight = double.IsNaN(this.ColumnHeaderHeight)
? this.PART_ColumnHeadersPresenter.ActualHeight
: this.ColumnHeaderHeight;
bounds.Location = new Point(0, 0);
bounds.Offset(0, columnHeaderHeight);
bounds.Height = this.ActualHeight - columnHeaderHeight - this.PART_HorizontalScrollBar.ActualHeight;
bounds.Width = this.ActualWidth - this.PART_VerticalScrollBar.ActualHeight;
return bounds;
}
}
}