2

I think it is a pretty common situation, but I can't find a solution that I need.

My case is, I have a DataGrid inside ScrollViewer. DataGrid has such params

MinHeight="350"
MaxHeight="350"

So, it is means that I have a dataGrid with constant height inside ScrollViewer. And DataGrid can contain (example) 300 items, so it has a scroll and ScrollViewer also has a scroll.

What I need is when user put a mouse on DataGrid and scroll down, so content inside DataGrid scrolling up to the last item and if came to last item it means that now we should get scroll event to ScrollViewer and continuous scroll whole page...

I found a few solutions on SO like this two

  1. Mouse scroll not working in a scroll viewer with a wpf datagrid and additional UI elements
  2. ScrollViewer mouse wheel not working

I checked an answers, but them doesn't fit to my problem. Because what I get is - when user put mouse on DataGrid and try scroll, so DataGrid did not get scroll event ever, just ScrollViewer get scroll event...

How to fix it?

James Cronen
  • 5,715
  • 2
  • 32
  • 52
Sirop4ik
  • 4,543
  • 2
  • 54
  • 121

1 Answers1

1

XAML

<Window x:Class="so_wpf_test_1.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:so_wpf_test_1"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
    <Border Padding="10" Background="Yellow">
        <ScrollViewer x:Name="sv">
            <StackPanel Orientation="Vertical">
                <DataGrid x:Name="dg" MinHeight="350" MaxHeight="350" ScrollViewer.ScrollChanged="dg_ScrollChanged">
                </DataGrid>

                <Button>A button that doesn't do anything</Button>

                <DataGrid x:Name="dg2" MinHeight="350" MaxHeight="350"  ScrollViewer.ScrollChanged="dg_ScrollChanged">
                </DataGrid>
            </StackPanel>
        </ScrollViewer>
    </Border>
</Window>

Code-behind

using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;

namespace so_wpf_test_1
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();

            var items = new ObservableCollection<Item>();
            for (int i = 1; i <= 50; ++i)
            {
                items.Add(new Item($"test {i}", $"abcd {i}", $"dcba {i}"));
            }
            dg.ItemsSource = items;
            dg2.ItemsSource = items;

            dg.PreviewMouseWheel += Dg_PreviewMouseWheel;
            dg2.PreviewMouseWheel += Dg_PreviewMouseWheel;
        }

        private void Dg_PreviewMouseWheel(object sender, MouseWheelEventArgs e)
        {
            var m_dg = sender as DataGrid;
            var scroll = GetScrollViewer(m_dg);

            // if scrolling to bottom and beyond...
            if (e.Delta < 0 && scroll.VerticalOffset == scroll.ScrollableHeight)
            {
                sv.ScrollToVerticalOffset(sv.VerticalOffset - e.Delta);
            }
            // if scrolling to top and beyond...
            else if (e.Delta > 0 && scroll.VerticalOffset == 0)
            {
                sv.ScrollToVerticalOffset(sv.VerticalOffset - e.Delta);
            }
            else
            {
                // nothing special: scroll the dg if it is the case (WPF does this automatically)
            }

            int idx = 0;

            // if scrolling down
            if (e.Delta < 0)
            {
                // see the sketch
                idx = dictLastVisible[m_dg] + 1;

                Debug.WriteLine($"FROM {(m_dg == dg ? 1 : 2)} DOWN {idx}");
            }
            // if scrolling up
            else if (e.Delta > 0)
            {
                // see the sketch
                idx = dictFirstVisible[m_dg] - 1;

                Debug.WriteLine($"FROM {(m_dg == dg ? 1 : 2)} UP {idx}");
            }

            // if the index does not represent nothing
            if (idx != 0)
            {
                // transform index to be 0-based
                --idx;

                // scroll that row into view
                m_dg.ScrollIntoView(m_dg.Items[idx]);
            }
        }

        /// <summary>
        /// Source: https://stackoverflow.com/a/41136328/258462
        /// </summary>
        /// <param name="element"></param>
        /// <returns></returns>
        public static ScrollViewer GetScrollViewer(UIElement element)
        {
            if (element == null) return null;

            ScrollViewer retour = null;
            for (int i = 0; i < VisualTreeHelper.GetChildrenCount(element) && retour == null; i++)
            {
                if (VisualTreeHelper.GetChild(element, i) is ScrollViewer)
                {
                    retour = (ScrollViewer)(VisualTreeHelper.GetChild(element, i));
                }
                else
                {
                    retour = GetScrollViewer(VisualTreeHelper.GetChild(element, i) as UIElement);
                }
            }
            return retour;
        }

        Dictionary<DataGrid, int> dictLastVisible = new Dictionary<DataGrid, int>();
        Dictionary<DataGrid, int> dictFirstVisible = new Dictionary<DataGrid, int>();

        private void dg_ScrollChanged(object sender, ScrollChangedEventArgs e)
        {
            var dg = (DataGrid)sender;

            int idxb = -1, idxe = -1; // b = beginning, e = end; both invalid initially

            // from the first row towards the last row
            for (int i = 0; i < dg.Items.Count; i++)
            {
                var v = (DataGridRow)dg.ItemContainerGenerator.ContainerFromItem(dg.Items[i]);

                if (v != null)
                {
                    idxb = i + 1; // compute the beginning row in the viewport
                    break;
                }
            }

            // from the beginning row towards the last row
            for (int i = idxb + 1; i < dg.Items.Count; i++)
            {
                var v = (DataGridRow)dg.ItemContainerGenerator.ContainerFromItem(dg.Items[i]);

                if (v == null)
                {
                    idxe = i - 1 + 1; // compute the end row in the viewport
                    break;
                }
            }

            // store the two indices in two dictionaries
            dictFirstVisible[dg] = idxb;
            dictLastVisible[dg] = idxe;
        }
    }

    public class Item
    {
        public string A { get; set; }
        public string B { get; set; }
        public string C { get; set; }

        public Item(string a, string b, string c)
        {
            A = a;
            B = b;
            C = c;
        }
    }
}

Possibly helping sketch

sketch

If the code does not run or has bugs, or the comments are not clear enough, please post a comment. I hope you will be able to extend the code for a dynamic number of DataGrids if you have this requirement.

silviubogan
  • 3,343
  • 3
  • 31
  • 57