1

I have TabControl that has a DataGrid inside each TabItem. It's all populated via binding. I use the row details expansion functionality, so have set the VirtualizingPanel ScrollUnit to Pixel, so scrolling is a bit more natural.

When changing between TabItems I have row selection behaving correctly. However, setting the vertical offset on the DataGrid's ScrollViewer so it is in the exact same position as it was when you left the TabItem is not working correctly.

The way it works at the moment is, I have a behavior class on the DataGrid. On a Scrollviewer ScrollChangedEvent it saves the VerticalOffset. Upon changing to a new TabItem and changing back to the original TabItem, in the DataGrid's DataContextChanged event I set the ScrollViewer's VerticalOffset to the saved VerticalOffset.

public class DataGridBehaviors : Behavior<DataGrid>
{
    protected override void OnAttached()
    {
        base.OnAttached();
        this.AssociatedObject.DataContextChanged += DataGrid_DataContextChanged;
        this.AssociatedObject.AddHandler(ScrollViewer.ScrollChangedEvent, new ScrollChangedEventHandler(DataGridScrollViewer_ScrollChanged));
    }

    protected override void OnDetaching()
    {
        Console.WriteLine("OnDetaching");
        this.AssociatedObject.RemoveHandler(ScrollViewer.ScrollChangedEvent, new ScrollChangedEventHandler(DataGridScrollViewer_ScrollChanged));
        this.AssociatedObject.DataContextChanged -= DataGrid_DataContextChanged;
        base.OnDetaching();
    }

    private void DataGrid_DataContextChanged(object sender, DependencyPropertyChangedEventArgs e)
    {
        ModuleGeometry oldModuleGeometry = (ModuleGeometry)e.OldValue;
        ModuleGeometry newModuleGeometry = (ModuleGeometry)e.NewValue;
        ScrollViewer scrollViewer = GetVisualChild<ScrollViewer>(this.AssociatedObject);
        if (scrollViewer != null)
        {
            scrollViewer.ScrollToVerticalOffset(newModuleGeometry.VerticalScrollPosition);
        }
    }

    private void DataGridScrollViewer_ScrollChanged(object sender, ScrollChangedEventArgs e)
    {
        ModuleGeometry modGeom = (ModuleGeometry)this.AssociatedObject.DataContext;
        modGeom.VerticalScrollPosition = e.VerticalOffset;
    }

    private static T GetVisualChild<T>(DependencyObject parent) where T : Visual
    {
        T child = default(T);

        int numVisuals = VisualTreeHelper.GetChildrenCount(parent);
        for (int i = 0; i < numVisuals; i++)
        {
            Visual v = (Visual)VisualTreeHelper.GetChild(parent, i);
            child = v as T;
            if (child == null)
            {
                child = GetVisualChild<T>(v);
            }
            if (child != null)
            {
                break;
            }
        }
        return child;
    }
}

What is happening is you scroll down and the top DataGridRow is partially displayed (You only see half of it). Then when you toggle between two TabItems, the program sets the VerticalOffset correctly, but then it resets again automatically to the top of the partially displayed row (showing it fully).

Before toggling. Saves VertcialOffset to 4327.2 enter image description here After toggling back to the original TabItem Sets VerticalOffset to 4327.2, then for some reason and somehow automatically resets VerticalOffset to 4321.5, which is the top of the 'previously' partially visible row enter image description here

It gets even weirder when you have an expanded row loaded in the VirtualizingPanel, the jump is more dramatic.

Before toggling enter image description here After toggling back to the original TabItem enter image description here

I would like to see the scroll position in exactly the same spot as when I left it, how can I accomplish this?

Hank
  • 2,456
  • 3
  • 35
  • 83

1 Answers1

1

The easiest approach would probably be to stop the TabControl from unloading the visual tree when you switch tabs. Then the content of each tab should be preserved.

Please refer to the following links for more information about this.

How to stop Wpf Tabcontrol to unload Visual tree on Tab change

WPF - Elements inside DataTemplate property issue when no binding?

mm8
  • 163,881
  • 10
  • 57
  • 88
  • Appreciate the links and the time you've taken with these similar questions, however, I am not looking for the easiest solution. – Hank Sep 11 '17 at 12:38
  • 1
    Well, the easiest and the best are probably the same in this case. The other option would be to capture the vertical offset of the ScrollBar, bind it to some source property and then set it back when the tab is being switched backed to. Sounds like a lot of (unnecessary? )effort. – mm8 Sep 11 '17 at 12:41
  • Obviously you don't want to unload the contents if you don't want the ScrollBar position to be reset. – mm8 Sep 11 '17 at 12:41
  • Maybe, there is going to be alot of data in these grids. I've already captured the vertical offset, that bit didn't take long, but when I set it back it again, it shifts as I've mentioned above. At what point should the VerticalOffset be setm – Hank Sep 11 '17 at 12:57
  • It shifts when you set it back how? – mm8 Sep 11 '17 at 12:58
  • I mean I set it in DataContextChanged maybe it should be in a later event to take hold correctly – Hank Sep 11 '17 at 12:59
  • I mention how it shifts up in the main question backed up with screen shots, where it shifts to the top of a partially visible row. – Hank Sep 11 '17 at 13:16
  • 1
    Yes, resetting the position of a scrollviewer and then set it back to the *exact* same position is harder than you think. I have given you my suggestion on how to solve this. – mm8 Sep 11 '17 at 13:19