WPF - 如何动态地改变ListBox.ScrollViewer.HorizontalOffset的值?

14

我有一个包含Visual元素的ListBox。我需要找到其中一个元素的XPosition,并对ListBoxScrollViewerHorizontalOffset进行动画处理。实际上,我想创建一个带动画效果的ScrollIntoView方法。

这给了我一些问题。首先,如何获取ListBox的scrollviewer的引用?其次,如何获取ListBox中任意元素的相对XPositionHorizontalOffset?

我没有响应ListBox本身的任何输入,因此无法使用与Mouse相关的属性。

2个回答

33

我认为你无法使用WPFstoryboard进行动画处理,因为故事板是用于对WPF依赖属性进行动画处理的。你需要调用ScrollViewer.ScrollToHorizontalOffset(double)方法进行滚动。

你可以尝试创建一个自定义的依赖属性,在OnDependencyPropertyChanged()函数中调用SetHorizontalOffset方法。然后你就可以对该属性进行动画处理了。

public static readonly DependencyProperty ScrollOffsetProperty =
   DependencyProperty.Register("ScrollOffset", typeof(double), typeof(YOUR_TYPE),
   new FrameworkPropertyMetadata(0.0, new PropertyChangedCallback(OnScrollOffsetChanged)));


public double ScrollOffset
{
   get { return (double)GetValue(ScrollOffsetProperty); }
   set { SetValue(ScrollOffsetProperty, value); }
}

private static void OnScrollOffsetChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
{
   YOUR_TYPE myObj = obj as YOUR_TYPE;

   if (myObj != null)
      myObj.SCROLL_VIEWER.ScrollToHorizontalOffset(myObj.ScrollOffset);
}

要获取滚动查看器,您可以使用VisualTreeHelper搜索ListBox的可视子级。保存对ScrollViewer的引用,因为您后来需要它。试试这个:

public static childItem FindVisualChild<childItem>(DependencyObject obj)
   where childItem : DependencyObject
{
   // Iterate through all immediate children
   for (int i = 0; i < VisualTreeHelper.GetChildrenCount(obj); i++)
   {
      DependencyObject child = VisualTreeHelper.GetChild(obj, i);

      if (child != null && child is childItem)
         return (childItem)child;

      else
      {
         childItem childOfChild = FindVisualChild<childItem>(child);

         if (childOfChild != null)
            return childOfChild;
      }
   }

   return null;
}

此函数返回参数类型的第一个可见子元素。调用 FindVisualChild<ScrollViewer>(ListBox) 来获取 ScrollViewer。

最后,尝试使用 UIElement.TranslatePoint(Point, UIElement) 获取该项的X位置。在该项上调用此函数,将点传入0,0,并传入 ScrollViewer。

希望这能帮到您。


这个属性有可能是一个附加的DependencyProperty。这将允许在任何具有ScrollView的控件上重用此代码。 - Josh G
在翻译项的位置时,您实际上想要找到ItemsPresenter。例如: var itemContainer = listBox.ItemContainerGenerator.ContainerFromItem(listBox.SelectedItem); itemContainer.TranslatePoint(new Point(0, 0), FindVisualChild<ItemsPresenter>(scrollViewer)); - Flatliner DOA
非常感谢Josh!我编写了一个行为,监听ListBox上的PreviewKeyDown-事件,如果用户按下Ctrl+Key.Left或Ctrl+Key.Right,则ListBox的ScrollViewer应该滚动到开头或结尾。使用VisualTreeHelper解决方案找到ScrollViewer非常顺利。 - toolsche
FindVisualChild方法实现了经典的深度优先搜索;但是注释有误导性。 - bohdan_trotsenko
这是正确的。然而,评论的重点在于澄清外部循环遍历所有项的子项。更新后消除了广度优先的混淆,因为递归算法显然是深度优先的。 - Josh G
显示剩余3条评论

1

我不确定我的方法是否是良好的实践,但在我有限的时间内似乎还可以。我没有使用故事板,而是使用了一个DispatcherTimer。

ScrollLeftButtonCommand = new DelegateCommand(
    o =>
       {
           var scrollViewer = (ScrollViewer)o;

           scrollTimer = new DispatcherTimer();

           scrollTimer.Start();

           scrollTimer.Interval = TimeSpan.FromMilliseconds(30);

           scrollTimer.Tick += (s, e) =>
           {          
               scrollViewer.ScrollToHorizontalOffset(scrollViewer.HorizontalOffset - 50);

               if (scrollViewer.HorizontalOffset <= 0)
               {
                   scrollTimer.Stop();
               }
           };
       });

确保它是一个DispatchTimer,这样线程才能控制UI元素

同时记得在视图中绑定你的对象!

<Button CommandParameter="{Binding ElementName=MyScrollViewer }"
        Command="{Binding ScrollLeftButtonCommand }"/>

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接