Make the ListPicker or RadListPicker behave in a ScrollViewer

I’ve seen a lot of applications that have a page in there that just doesn’t behave the way it should. Either there’s a textbox that’s placed in a not so brilliant way, where a lot of controls are covered by the onscreen keyboard and there’s not a nice way to close the keyboard (tapping somewhere on the screen is not a nice way, is it?). The same goes for ListPickers, when they are placed in a ScrollViewer at the bottom of the screen chances are that you’re only seeing one item once you open de ListPicker, the rest is at the bottom of the screen.

A picture is worth a thousand words, so here the situation of the ListPicker I’m explaining, closed (no trouble) and opened where the user hardly sees that there are options at all.

closed

opened wrong

 

 

 

 

 

 

 

 

Of course I want to fix this. And I even know I fixed this before, but I had to think hard in which app I fixed this problem. I found the code but in the old app I was making use of the ListPicker from the Windows Phone Toolkit and now I’m making use of the RadListPicker by Telerik. Though besides that I didn’t find anyone blogging about this before. That’s why I’m writing this. The code I show in here could have been written by someone else, but I can’t remember.

ListPicker from the Windows Phone Toolkit solution

The original ListPicker can react to the GotFocus event to then bind to the SizeChanged event in which the actual logic for making the control visible in the scrollviewer is called. The magic piece is shown in the end, it’s shared by both solutions. The magic is in line 8 of course.

NewTracksToDownloadPicker.GotFocus += PickerGotFocus;

private void PickerGotFocus(object sender, RoutedEventArgs eventArgs)
{
    var picker = sender as ListPicker;
    if (picker != null)
    {
        picker.SizeChanged += (s, e) => picker.EnsureVisibleInScrollViewer();
    }
}

RadListPicker by Telerik solution

The RadListPicker by Telerik doesn’t fire the GotFocus event at the same time, so I had to pick and try to correct event to listen to. I found out that the StateChanged event works best for me.

NewTracksToDownloadPicker.StateChanged += PickerStateChanged;

private void PickerStateChanged(object sender, ListPickerStateChangedEventArgs listPickerStateChangedEventArgs)
{
    var picker = sender as RadListPicker;
    if (picker != null)
    {
        picker.SizeChanged += (s, e) => picker.EnsureVisibleInScrollViewer();
    }
}

 

So now it’s time for some magic!

No not really, but the code can be quite handy for other situations as well when you want to be sure a control is completely visible inside a ScrollViewer.

The magic consists of two parts, finding the parent ScrollViewer of the Control you want to make visible. With all kinds of nesting, it could be difficult to tell how many levels are there. So with the help of the VisualTreeHelper and a little bit of recursion you get this pieces of code.

public static T FindParentOf<T>(this UIElement element) where T : UIElement
{
    var found = FindParentOf(element, typeof(T)) as T;
    return found;
}

public static DependencyObject FindParentOf(DependencyObject element, Type type)
{
    if (type.IsInstanceOfType(element))
        return element;
    DependencyObject parent = VisualTreeHelper.GetParent(element);
    if (parent == null)
        return null;
    return FindParentOf(parent, type);
}

 

So that piece of magic is gone. Next is the actual making sure that the control is visible in the ScrollViewer. A little bit of measuring and making the ScrollViewer to scroll to the measured position. What magic were you talking about?

public static void EnsureVisible(this ScrollViewer scroller, UIElement element)
{
    scroller.UpdateLayout();

    double maxScrollPos = scroller.ExtentHeight - scroller.ViewportHeight;
    double scrollPos =
        scroller.VerticalOffset -
        scroller.TransformToVisual(element).Transform(new Point(0, 0)).Y;

    if (scrollPos > maxScrollPos) scrollPos = maxScrollPos;
    else if (scrollPos < 0) scrollPos = 0;

    scroller.ScrollToVerticalOffset(scrollPos);
}

public static void EnsureVisibleInScrollViewer(this UIElement element)
{
    var scrollViewer = element.FindParentOf<ScrollViewer>();
    if (scrollViewer != null)
        scrollViewer.EnsureVisible(element);
}

 

Yes, I know you want to see the end result. Have fun with it!

opened correctly
Gravatar