views:

3828

answers:

2

I have a collection of Visuals in a ListBox. I need to find the XPosition of an element inside it and then animate the HorizontalOffset of the ListBox's ScrollViewer. Essentially I want to created an animated ScrollIntoView method.

This gives me a couple of problems. Firstly, how can I get a reference to the ListBoxs scrollviewer? Secondly, how can i get the relative XPosition or HozintalOfffset of an arbitrary element in the ListBox?

I'm not reponding to any input on the ListBox itself so I can't use Mouse related properties.

+14  A: 

I don't think you will be able to use a WPF storyboard for the animation because storyboards animate WPF dependency properties. You will need to call ScrollViewer.SetHorizontalOffset(double) to scroll.

You could try creating a custom dependency property that calls SetHorizontalOffset in the OnDependencyPropertyChanged() function. Then you could animate this property.

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);
}

To get the scroll viewer you can use the VisualTreeHelper to search the visual children of the ListBox. Save a reference to the ScrollViewer because you will need it later. Try this:

public static childItem FindVisualChild<childItem>(DependencyObject obj)
   where childItem : DependencyObject
{
   // Search immediate children first (breadth-first)
   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;
}

This function returns the first visual child of the parameter type. Call FindVisualChild<ScrollViewer>(ListBox) to get the ScrollViewer.

Finally, try using UIElement.TranslatePoint(Point, UIElement) to get the X position of the item. Call this function on the item, pass in 0,0 for the point, and pass in the ScrollViewer.

Hope this helps.

Josh G
My Lord, that's quite a job! Thanks for the help Josh, it's pointed me in the right direction at least.
Stimul8d
Do you mean ScrollToHorizontalOffset instead of SetHorizontalOffset?
Ashley Davis
Yes, you're right. Thanks!
Josh G
Great idea. It helped me out!
John Chuckran
It's possible that this property could be an attached DependencyProperty. That would allow this code to be reused on any control that has a ScrollView in it's visual tree.
Josh G
You actually want to find the ItemsPresenter when translating the position of the item. e.g. var itemContainer = listBox.ItemContainerGenerator.ContainerFromItem(listBox.SelectedItem); itemContainer.TranslatePoint(new Point(0, 0), FindVisualChild<ItemsPresenter>(scrollViewer));
Flatliner DOA
A: 

Hi I tried using this method to implement smooth scrolling and I get the error: " Cannot animate the 'ScrollOffset' property on 'System.Windows.Media.MatrixTransform' because the object is sealed or frozen. "

Any ideas? Below is the code I'm using to animate...

        DoubleAnimation moveAnimation = new DoubleAnimation();
        moveAnimation.From = scrollViewer.VerticalOffset;
        if (scrollViewer.VerticalOffset >=100)
            moveAnimation.To = scrollViewer.VerticalOffset - 100;
        else
            moveAnimation.To = 0;
        moveAnimation.Duration = new Duration(new TimeSpan(0, 0, 0, 0, 500));
        this.RenderTransform.BeginAnimation(ActionScreen.ScrollOffsetProperty, moveAnimation);
Matt B