在 TabControl TabItems 之间切换时捕获和设置 DataGrid 滚动位置

Capturing and setting DataGrid scroll position when toggling between TabControl TabItems

我有 TabControl,每个 TabItem 里面有一个 DataGrid。它都是通过绑定填充的。我使用行详细信息扩展功能,因此将 VirtualizingPanel ScrollUnit 设置为 Pixel,这样滚动会更自然一些。

在 TabItems 之间切换时,我的行选择行为正确。但是,在 DataGrid 的 ScrollViewer 上设置垂直偏移,使其与您离开 TabItem 时的位置完全相同,但无法正常工作。

目前它的工作方式是,我在 DataGrid 上有一个行为 class。在 Scrollviewer ScrollChangedEvent 上它保存 VerticalOffset。在更改为新 TabItem 并改回原始 TabItem 后,在 DataGrid 的 DataContextChanged 事件中,我将 ScrollViewer 的 VerticalOffset 设置为保存的 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;
    }
}

发生的情况是您向下滚动并且顶部的 DataGridRow 部分显示(您只看到它的一半)。然后,当您在两个 TabItem 之间切换时,程序会正确设置 VerticalOffset,但随后它会再次自动重置到部分显示行的顶部(完全显示)。

切换前。将 VertcialOffset 保存为 4327.2 切换回原始 TabItem 后将 VerticalOffset 设置为 4327.2,然后出于某种原因自动将 VerticalOffset 重置为 4321.5,这是 'previously' 部分可见行的顶部

当您在 VirtualizingPanel 中加载展开的行时,它变得更加奇怪,跳跃更加引人注目。

切换前 切换回原始 TabItem 后

我希望看到滚动位置与我离开时的位置完全相同,我该如何实现?

最简单的方法可能是在切换选项卡时阻止 TabControl 卸载可视化树。那么每个tab的内容都要保留。

有关这方面的更多信息,请参阅以下链接。

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

WPF - Elements inside DataTemplate property issue when no binding?