Silverlight BringIntoView() extension method (with OnGotFocus behavior)

It all started because I couldn't find a way to automatically scroll any element into view in Silverlight (a feature
that exists in WPF).  I take that back, I could get the job done with a ListBox's ScrollIntoView(ListBoxItem item)
method, but I hardly wanted everything on my screen to be wrapped as a ListBoxItem; it feels as dirty as it sounds.  

Anyways, here is the code.

/* Extension Methods */
public static class FrameworkElementExtensions
{
    private const int ScrollPadding = 10;

    public static void BringIntoView(this FrameworkElement frameworkElement)
    {
        var parent = VisualTreeHelper.GetParent(frameworkElement);
        while(parent != null)
        {
            parent = VisualTreeHelper.GetParent(parent);
            var scrollViewer = parent as ScrollViewer;
            if(scrollViewer != null)
            {
                frameworkElement.BringIntoViewForScrollViewer(scrollViewer);
                break;
            }
        }
    }

    public static void BringIntoViewForScrollViewer(this FrameworkElement frameworkElement, ScrollViewer scrollViewer)
    {
        var transform = frameworkElement.TransformToVisual(scrollViewer);
        var positionInScrollViewer = transform.Transform(new Point(0, 0));

        if (positionInScrollViewer.Y < 0 || positionInScrollViewer.Y > scrollViewer.ViewportHeight)
            scrollViewer.ScrollToVerticalOffset(scrollViewer.VerticalOffset + positionInScrollViewer.Y - ScrollPadding);
    }
} 

 

Bonus Behavior! (Behaviors?!? Here is an Introduction).
This behavior was created ensure that as a user tabs through the screen, scrolling automatically takes place.

Note: If you want to use behaviors in Silverlight, install the Blend 3 SDK and reference
Microsoft.Expression.Interactions.dll

/* Behavior class  */
public class BringIntoViewOnFocusBehavior : Behavior<FrameworkElement>
{
    protected override void OnAttached()
    {
        base.OnAttached();
        AssociatedObject.GotFocus += OnGotFocus;
    }

    protected override void OnDetaching()
    {
        base.OnDetaching();
        AssociatedObject.GotFocus -= OnGotFocus;
    }

    private void OnGotFocus(object sender, RoutedEventArgs e1)
    {
        AssociatedObject.BringIntoView();
    }        
}

 

<!-- XAML usage of custom behavior -->
<UserControl 
    xmlns:interactivity="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity" 
    xmlns:behaviors="clr-namespace:YourNamespaceToBehaviors.Behaviors">        
    <StackPanel>
        <TextBox>
            <interactivity:Interaction.Behaviors>
                <behaviors:BringIntoViewOnFocusBehavior />
            </interactivity:Interaction.Behaviors>
        </TextBox>
    </StackPanel>    
</UserControl>

4 Comments

  • hi Josh, do you know of a way to do this for version 2.0 of SL?

  • Great code! ive made a slight enhancement to the BringIntoViewForScrollViewer method so that if a framework element is partially cut off the screen at the bottom, it will show it.

    public static void BringIntoViewForScrollViewer(this FrameworkElement frameworkElement, ScrollViewer scrollViewer)
    {
    var transform = frameworkElement.TransformToVisual(scrollViewer);

    Point topPositionInScrollViewer = transform.Transform(new Point(0, 0));
    Point bottomPositionInScrollViewer = transform.Transform(new Point(0, frameworkElement.ActualHeight));

    if(topPositionInScrollViewer.Y scrollViewer.ViewportHeight)
    scrollViewer.ScrollToVerticalOffset(scrollViewer.VerticalOffset + bottomPositionInScrollViewer.Y - scrollViewer.ViewportHeight + ScrollPadding);
    }

  • Hi,

    I'm having some trouble using your extension. ViewportHeight is always 1, ViewportWidth is always infinity, and "positionInScrollViewer" is always the same (e.g. {5,2} or {1,2}).

    The ActualWidth and ActualHeight values appear valid (e.g. 28 and 431 respectively).

    Some background: The TextBox I'm testing on is inside a DataTemplate of an ItemTemplate.

    Any ideas?

  • Thanks for sharing the code and ideas.

    NOTE that it won't always work as nice on "new" items that haven't loaded yet.

    I found that when the ActualHeight of the item is 0, it still needs to be loaded in the visual tree. Adding this small piece of code to the start of the BringIntoView() method solved that problem for me.

    static void frameworkElement_Loaded_BringIntoView(object sender, RoutedEventArgs e)
    {
    ((FrameworkElement)sender).Loaded -= frameworkElement_Loaded_BringIntoView;
    ((FrameworkElement)sender).BringIntoView();
    }

    public static void BringIntoView(this FrameworkElement frameworkElement)
    {
    if (frameworkElement.ActualHeight == 0 /* it might be brand new, and haven't loaded yet */) {
    frameworkElement.Loaded += frameworkElement_Loaded_BringIntoView;
    return;
    }

    ... // normal code
    }

Comments have been disabled for this content.