如何使用另一个DataGrid的scrollviewer滚动DataGrid

时间:2015-10-08 11:36:24

标签: c# wpf datagrid

我有两个数据网格。隐藏第一个数据网格的垂直滚动条。所需的场景是我想在第二个数据网格滚动时让第一个数据网格滚动其内容。用户无法手动滚动第一个数据网格,但每当滚动第二个数据网格时,第一个数据网格应与其并行移动。

我尝试更改第一个datagrid的垂直滚动条的值,因为第二个datagrid的垂直滚动条的值发生了变化,但这只是改变了滚动条的位置,但是没有滚动数据网格的内容。

如何将第一个数据网格的滚动条与第二个数据网格同步?它应该看起来好像两者都是同一个UI元素的一部分,因此滚动条应该理想地滚动它们。

3 个答案:

答案 0 :(得分:0)

在WPF中,我认为您可以使用以下方法:

DataGrid.ScrollIntoView(object item, DataGridColumn column);

设置DataGrid的位置。

答案 1 :(得分:0)

我猜你有两个数据网格具有完全相同的项目数。我有类似的东西 - 同步两个滚动视图。 您可能需要创建一个同步两个滚动条的VerticalOffset的行为。 我是怎么做的:

<强> XAML

<Window x:Class="Sandbox.MainWindow"
   xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
   xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
   xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
   xmlns:behaviors="clr-namespace:Sandbox.Behaviors"
   Title="MainWindow" Height="350" Width="300"
>
   <Grid>
      <Grid.ColumnDefinitions>
         <ColumnDefinition Width="1*" />
         <ColumnDefinition Width="1*" />
      </Grid.ColumnDefinitions>
      <ScrollViewer CanContentScroll="True" Height="200" VerticalScrollBarVisibility="Hidden" HorizontalScrollBarVisibility="Disabled">
         <i:Interaction.Behaviors>
            <behaviors:VerticalOffsetBehaviour Value="{Binding ElementName=otherScroller,Path=VerticalOffset}" />
         </i:Interaction.Behaviors>
         <TextBlock TextWrapping="Wrap">
            Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque blandit, dolor vitae accumsan pellentesque, justo quam vehicula ante, vitae viverra enim nibh ac ex. Quisque efficitur lobortis lorem, id tempor nibh efficitur eget. Ut id felis enim. Aliquam commodo massa non dolor sollicitudin, pharetra bibendum turpis malesuada. Vestibulum vulputate blandit aliquam. Vestibulum ut varius mi. Phasellus ut massa turpis. In hac habitasse platea dictumst. Vestibulum efficitur elit et lobortis euismod. Mauris vitae ultricies velit.
         </TextBlock>
      </ScrollViewer>
      <ScrollViewer x:Name="otherScroller" Grid.Column="1" Height="200">
         <TextBlock TextWrapping="Wrap">
            Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque blandit, dolor vitae accumsan pellentesque, justo quam vehicula ante, vitae viverra enim nibh ac ex. Quisque efficitur lobortis lorem, id tempor nibh efficitur eget. Ut id felis enim. Aliquam commodo massa non dolor sollicitudin, pharetra bibendum turpis malesuada. Vestibulum vulputate blandit aliquam. Vestibulum ut varius mi. Phasellus ut massa turpis. In hac habitasse platea dictumst. Vestibulum efficitur elit et lobortis euismod. Mauris vitae ultricies velit.
         </TextBlock>
      </ScrollViewer>
   </Grid>
</Window>

<强>行为

using System.Windows;
using System.Windows.Controls;
using System.Windows.Interactivity;

namespace Sandbox.Behaviors
{
   public sealed class VerticalOffsetBehaviour : Behavior<ScrollViewer>
   {
      public static readonly DependencyProperty ValueProperty = DependencyProperty.Register("Value", typeof(double), typeof(VerticalOffsetBehaviour), new PropertyMetadata(VerticalOffsetBehaviour.OnValueChanged));

      public double Value
      {
         get { return (double)this.GetValue(ValueProperty); }
         set { this.SetValue(ValueProperty, value); }
      }

      private static void OnValueChanged(object source, DependencyPropertyChangedEventArgs args)
      {
         var behavior = (VerticalOffsetBehaviour)source;
         behavior.AssociatedObject.ScrollToVerticalOffset(behavior.Value);
      }
   }
}

答案 2 :(得分:0)

您可以使用附加属性和样式来同步滚动查看器。在下面的示例中,我使用一个附加属性声明同步范围àlaGrid.IsSharedSizeScope,然后使用样式在IsSynchronized的{​​{1}}上设置DataGrid属性

SynchronizedScrollViewer.cs:

ScrollViewer

使用示例:

[Flags]
public enum SynchronizedScrollViewerMode
{
    Horizontal = 0x1,
    Vertical = 0x2,
    HorizontalAndVertical = Horizontal | Vertical,
    Disabled = 0
}

public sealed class SynchronizedScrollViewer : DependencyObject
{
    public SynchronizedScrollViewerMode Mode { get; }
    public VerticalAlignment VerticalAlignment { get; private set; }
    public HorizontalAlignment HorizontalAlignment { get; private set; }

    private class SyncrhonizedScrollViewerChild
    {
        public readonly ScrollViewer ScrollViewer;
        public bool IsDirty;

        public SyncrhonizedScrollViewerChild(ScrollViewer child)
        {
            if (child == null)
            {
                throw new ArgumentNullException(nameof(child));
            }

            this.ScrollViewer = child;
        }
    }

    private readonly List<SyncrhonizedScrollViewerChild> Children;

    public SynchronizedScrollViewer(SynchronizedScrollViewerMode mode)
    {
        if (mode == SynchronizedScrollViewerMode.Disabled)
        {
            throw new ArgumentNullException(nameof(mode));
        }

        this.Mode = mode;
        this.Children = new List<SyncrhonizedScrollViewerChild>();
    }

    #region Attached Properties

    public static SynchronizedScrollViewerMode GetScopeMode(DependencyObject obj)
    {
        return (SynchronizedScrollViewerMode)obj.GetValue(ScopeModeProperty);
    }

    public static void SetScopeMode(DependencyObject obj, SynchronizedScrollViewerMode value)
    {
        obj.SetValue(ScopeModeProperty, value);
    }

    public static readonly DependencyProperty ScopeModeProperty =
        DependencyProperty.RegisterAttached("ScopeMode", typeof(SynchronizedScrollViewerMode),
            typeof(SynchronizedScrollViewer), new PropertyMetadata(SynchronizedScrollViewerMode.Disabled));

    public static HorizontalAlignment GetHorizontalAlignment(DependencyObject obj)
    {
        return (HorizontalAlignment)obj.GetValue(HorizontalAlignmentProperty);
    }

    public static void SetHorizontalAlignment(DependencyObject obj, HorizontalAlignment value)
    {
        obj.SetValue(HorizontalAlignmentProperty, value);
    }

    public static readonly DependencyProperty HorizontalAlignmentProperty =
        DependencyProperty.RegisterAttached("HorizontalAlignment", typeof(HorizontalAlignment),
            typeof(SynchronizedScrollViewer), new PropertyMetadata(HorizontalAlignment.Left, Alignment_Changed));

    public static VerticalAlignment GetVerticalAlignment(DependencyObject obj)
    {
        return (VerticalAlignment)obj.GetValue(VerticalAlignmentProperty);
    }

    public static void SetVerticalAlignment(DependencyObject obj, VerticalAlignment value)
    {
        obj.SetValue(VerticalAlignmentProperty, value);
    }

    public static readonly DependencyProperty VerticalAlignmentProperty =
        DependencyProperty.RegisterAttached("VerticalAlignment", typeof(VerticalAlignment),
            typeof(SynchronizedScrollViewer), new PropertyMetadata(VerticalAlignment.Top, Alignment_Changed));

    public static bool GetIsSynchronized(DependencyObject obj)
    {
        return (bool)obj.GetValue(IsSynchronizedProperty);
    }

    public static void SetIsSynchronized(DependencyObject obj, bool value)
    {
        obj.SetValue(IsSynchronizedProperty, value);
    }

    public static readonly DependencyProperty IsSynchronizedProperty =
        DependencyProperty.RegisterAttached("IsSynchronized", typeof(bool),
            typeof(SynchronizedScrollViewer), new FrameworkPropertyMetadata(false, IsSynchronized_Changed));

    public static SynchronizedScrollViewer GetScope(DependencyObject obj)
    {
        return (SynchronizedScrollViewer)obj.GetValue(ScopeProperty);
    }

    public static void SetScope(DependencyObject obj, SynchronizedScrollViewer value)
    {
        obj.SetValue(ScopeProperty, value);
    }

    public static readonly DependencyProperty ScopeProperty =
        DependencyProperty.RegisterAttached("Scope", typeof(SynchronizedScrollViewer),
            typeof(SynchronizedScrollViewer), new PropertyMetadata(null));

    #endregion

    private static void Alignment_Changed(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        var scope = GetScope(d);
        if (scope == null)
        {
            // will be set later
        }
        else
        {
            scope.HorizontalAlignment = GetHorizontalAlignment(d);
            scope.VerticalAlignment = GetVerticalAlignment(d);
        }
    }

    private static void IsSynchronized_Changed(DependencyObject d, DependencyPropertyChangedEventArgs args)
    {
        var target = (ScrollViewer)d;
        var newValue = (bool)args.NewValue;

        if (newValue)
        {
            var scope = FindSynchronizationScope(target);
            scope.AddSynchronizedChild(target);
        }
    }

    private void AddSynchronizedChild(ScrollViewer target)
    {
        if (this.Children.Any(c => c.ScrollViewer == target))
        {
            throw new InvalidOperationException("Child is already synchronized");
        }

        this.Children.Add(new SyncrhonizedScrollViewerChild(target));
        target.ScrollChanged += Target_ScrollChanged;
    }

    private void Target_ScrollChanged(object sender, ScrollChangedEventArgs e)
    {
        var sv = (ScrollViewer)sender;
        var child = Children.Single(s => s.ScrollViewer == sv);

        if (child.IsDirty)
        {
            // we just called "Set*Offset" on this child, so we don't wan't a loop
            // no-op
            child.IsDirty = false;
        }
        else
        {
            foreach (var otherChild in Children)
            {
                if (otherChild == child)
                {
                    // don't update the sender
                    continue;
                }

                var osv = otherChild.ScrollViewer;
                if (this.Mode.HasFlag(SynchronizedScrollViewerMode.Horizontal)
                    && otherChild.ScrollViewer.HorizontalOffset != child.ScrollViewer.HorizontalOffset)
                {
                    // already in sync
                    otherChild.IsDirty = true;
                    var targetOffset = sv.HorizontalOffset;
                    if (HorizontalAlignment == HorizontalAlignment.Center
                        || HorizontalAlignment == HorizontalAlignment.Stretch)
                    {
                        double scrollPositionPct = sv.HorizontalOffset / (sv.ExtentWidth - sv.ViewportWidth);
                        targetOffset = (osv.ExtentWidth - osv.ViewportWidth) * scrollPositionPct;
                    }
                    else if (HorizontalAlignment == HorizontalAlignment.Right)
                    {
                        targetOffset = otherChild.ScrollViewer.ExtentWidth - (sv.ExtentWidth - sv.HorizontalOffset);
                    }
                    otherChild.ScrollViewer.ScrollToHorizontalOffset(targetOffset);
                }

                if (this.Mode.HasFlag(SynchronizedScrollViewerMode.Vertical)
                    && otherChild.ScrollViewer.VerticalOffset != child.ScrollViewer.VerticalOffset)
                {
                    // already in sync
                    otherChild.IsDirty = true;
                    var targetOffset = sv.VerticalOffset;
                    if (VerticalAlignment == VerticalAlignment.Center
                        || VerticalAlignment == VerticalAlignment.Stretch)
                    {
                        double scrollPositionPct = sv.VerticalOffset / (sv.ExtentHeight - sv.ViewportHeight);
                        targetOffset = (osv.ExtentHeight - osv.ViewportHeight) * scrollPositionPct;
                    }
                    else if (VerticalAlignment == VerticalAlignment.Bottom)
                    {
                        targetOffset = otherChild.ScrollViewer.ExtentHeight - (sv.ExtentHeight - sv.VerticalOffset);
                    }
                    otherChild.ScrollViewer.ScrollToVerticalOffset(targetOffset);
                }
            }
        }
    }

    private static SynchronizedScrollViewer FindSynchronizationScope(ScrollViewer target)
    {
        for (DependencyObject obj = target; obj != null;
            // ContentPresenter seems to cause VisualTreeHelper to return null when FrameworkElement.Parent works.
            // http://stackoverflow.com/questions/6921881/frameworkelement-parent-and-visualtreehelper-getparent-behaves-differently
            obj = VisualTreeHelper.GetParent(obj) ?? (obj as FrameworkElement)?.Parent)
        {
            var mode = GetScopeMode(obj);
            if (mode != SynchronizedScrollViewerMode.Disabled)
            {
                var scope = GetScope(obj);
                if (scope == null)
                {
                    scope = new SynchronizedScrollViewer(mode);
                    scope.HorizontalAlignment = GetHorizontalAlignment(obj);
                    scope.VerticalAlignment = GetVerticalAlignment(obj);
                    SetScope(obj, scope);
                }
                return scope;
            }
        }

        throw new InvalidOperationException("A scroll viewer is set as synchronized, but no synchronization scope was found.");
    }
}

使用均匀和不均匀内容的示例:

gif showing synchronized scrolling of two datagrids with identical column widths gif showing synchronized scrolling of two uneven scrollviewers