簡體   English   中英

如何使用另一個 DataGrid 的滾動查看器滾動 DataGrid

[英]How to scroll a DataGrid using scrollviewer of another DataGrid

我有兩個數據網格。 第一個數據網格的垂直滾動條被隱藏。 所需的場景是,每當第二個數據網格滾動時,我都想讓第一個數據網格滾動其內容。 用戶無法手動滾動第一個數據網格,但每當滾動第二個數據網格時,第一個數據網格應與其平行移動。

我試圖更改第一個數據網格的垂直滾動條的值,因為第二個數據網格的垂直滾動條的值發生了變化,但這只會更改滾動條的位置,但不會滾動數據網格的內容。

如何將第一個數據網格的滾動條與第二個數據網格同步? 它應該看起來好像兩者都是同一個 UI 元素的一部分,因此理想情況下滾動條應該滾動兩者。

在WPF中,我認為您可以使用以下方法:

DataGrid.ScrollIntoView(object item, DataGridColumn column);

設置DataGrid 的位置。

我猜你有兩個數據網格,它們的項目數量完全相同。 我有類似的東西 - 同步兩個滾動視圖。 您可能需要創建一個同步兩個滾動條的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);
      }
   }
}

您可以使用附加的屬性和樣式來允許滾動查看器的同步。 在下面的示例中,我使用一個附加屬性來聲明Grid.IsSharedSizeScope的同步范圍,然后使用一種樣式在DataGridScrollViewer上設置IsSynchronized屬性。

SynchronizedScrollViewer.cs:

[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.");
    }
}

使用示例:

<Grid local:SynchronizedScrollViewer.ScopeMode="HorizontalAndVertical" 
      local:SynchronizedScrollViewer.HorizontalAlignment="Center">
    <Grid.RowDefinitions>
        <RowDefinition Height="1*"/>
        <RowDefinition Height="1*"/>
    </Grid.RowDefinitions>

    <DataGrid Grid.Row="0" x:Name="dgTarget1" FrozenColumnCount="3">
        <DataGrid.Resources>
            <Style TargetType="{x:Type ScrollViewer}" BasedOn="{StaticResource {x:Type ScrollViewer}}">
                <Setter Property="local:SynchronizedScrollViewer.IsSynchronized" Value="True" />
            </Style>
        </DataGrid.Resources>
    </DataGrid>

    <DataGrid Grid.Row="1" x:Name="dgTarget2" FrozenColumnCount="3">
        <DataGrid.Resources>
            <Style TargetType="{x:Type ScrollViewer}" BasedOn="{StaticResource {x:Type ScrollViewer}}">
                <Setter Property="local:SynchronizedScrollViewer.IsSynchronized" Value="True" />
            </Style>
        </DataGrid.Resources>
    </DataGrid>
</Grid>

使用均勻和不均勻內容的示例:

gif 顯示兩個列寬相同的數據網格的同步滾動 gif 顯示兩個不均勻滾動查看器的同步滾動

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM