【问题标题】:How to scroll a DataGrid using scrollviewer of another DataGrid如何使用另一个 DataGrid 的 scrollviewer 滚动 DataGrid
【发布时间】:2016-01-06 00:11:12
【问题描述】:

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

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

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

【问题讨论】:

    标签: c# wpf datagrid


    【解决方案1】:

    您可以使用附加的属性和样式来同步滚动查看器。在下面的示例中,我使用一个附加属性来声明一个同步范围 à la 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>
    

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

    【讨论】:

      【解决方案2】:

      我猜你有两个数据网格具有完全相同的项目数。我有类似的东西 - 同步两个滚动视图。 您可能需要创建一个同步两个滚动条的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);
            }
         }
      }
      

      【讨论】:

        【解决方案3】:

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

        DataGrid.ScrollIntoView(object item, DataGridColumn column);
        

        设置DataGrid的位置。

        【讨论】:

          猜你喜欢
          • 2010-11-02
          • 2011-08-04
          • 1970-01-01
          • 2018-05-16
          • 2012-05-16
          • 1970-01-01
          • 1970-01-01
          • 2011-11-07
          • 2017-07-12
          相关资源
          最近更新 更多