Click here to Skip to main content
15,922,166 members
Please Sign up or sign in to vote.
1.00/5 (1 vote)
See more:
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:

C#
<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)
            {
                // Find the ScrollContentPresenter in the ScrollViewer's visual tree
                var scrollContentPresenter = FindVisualChild<ScrollContentPresenter>(scrollViewer);
                if (scrollContentPresenter != null)
                {

                    // Subscribe to the MouseLeave event
                    scrollContentPresenter.MouseLeave += OnScrollContentPresenterMouseLeave;
                    scrollContentPresenter.MouseEnter += OnScrollContentPresenterMouseEnter;
                }
            }
            Window parentWindow = Window.GetWindow(this);

      // Track global mouse up to ensure the unloading of the adorner
      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;

            // TODO::Store  currentRowContainer in Stack and HashSet
        }

        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();

      // Don't draw the adorner on the column headers
      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;
      }

      // Get the lates row height. Row height is relevant when CanContentScroll is TRUE
      // in order to convert the actual scroll offset from item to pixel.
      this.actualRowHeight = e.Row.ActualHeight;
    }

    private void OnDataGridRowMouseEnter(object sender, MouseEventArgs e)
    {
      // Don't execute selection if the select operation was not triggered outside the DataGrid
      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);

      // Unselected the DataGridRow that we left before entering the current DataGridRow.
      // The fact that this DataGridRow was already visited before means the user has changed mouse direction.
      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)
    {
      /* Update adorner position when the ScrollViewer was scrolled */

      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)
    {
      // For example, do not perform a select while any row is in edit mode
      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 position must be relative to 'this' and not screen
      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;
    }
  }
}
Posted

1 solution

I did a quick Google Search wpf datagrid multiselect dragging with scroll[^] and found this working solution: DataGrid auto scrolling when doing selection - solved[^]
 
Share this answer
 

This content, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)



CodeProject, 20 Bay Street, 11th Floor Toronto, Ontario, Canada M5J 2N8 +1 (416) 849-8900