Scroll into View for ListViewBase controls when using MVVM in Windows 8.1 Store apps

When you implement a windows store app which can navigate between the item list page and the item detail page, you may hope that the view can go to the last focused item when the page navigates from the item detail page to the item list page. Thus the end users don’t need to scroll the view again and again.

It’s quite easy to implement using ScrollIntoView (when the listViewBase control isn’t in a SemanticZoom control) or MakeVisible (when the listViewBase control is used as a view in a SemanticZoom control) functions. But if you want to use these two functions directly, it will break your MVVM structure. Additionally, ScrollIntoView and MakeVisible can only validate in the specific state. For me, Page.Loaded is the safe place to call ScrollIntoView and ListviewBese.Loaded is the safe place to call MakeVisible. So I decide to write a behavior to implement scroll into view functionality.

To implement a behavior in Windows 8.1, you should define a class which implement IBehavior interface. The interface request you to implement one property and two functions:

public interface IBehavior

{

    DependencyObject AssociatedObject { get; }

    void Attach(DependencyObject associatedObject);

    void Detach();

}

It’s easy to implement AssociatedObject. We can get the value from Attach function. So here is the implementation of AssociatedObject:

private DependencyObject _associatedObject;

        public DependencyObject AssociatedObject

        {

            get

            {

                return _associatedObject;

            }

        }

We also define a DependencyProperty LastFocusedItem which is used to get the last focused item.

private static readonly DependencyProperty LastFocusedItemProperty = DependencyProperty.Register("LastFocusedItem", typeof(object), typeof(ScrollIntoViewBehavior), null);

public object LastFocusedItem

{

        get

        {

            return (object)base.GetValue(LastFocusedItemProperty);

        }

        set

        {

            base.SetValue(LastFocusedItemProperty, (object)value);

        }

}

In Attach function, we can get the associatedObject from the argument. associatedObject is the object which your behavior attaches to. Since we attach this behavior to the listViewBase control, we can add ListViewBase.Loaded event handle in this function:

        public void Attach(DependencyObject associatedObj)

        {

            if (associatedObj != _associatedObject)

            {

                _associatedObject = associatedObj;

                var lv = _associatedObject as ListViewBase;

                if (lv != null)

                {

                    lv.Loaded += lv_Loaded;

                    lv.Unloaded += lv_Unloaded;

                }

 

            }

        }

In lv_Loaded event handler, we can check if SemanticZoom is used, if so, we call MakeVisible to scroll into the LastFocusedItem. If not, we find the Page object and add Page.Loaded event handler:

void lv_Loaded(object sender, RoutedEventArgs e)

        {

            var sz = FindSemanticZoom();

            if (sz != null)

            {

                if (_associatedObject != null)

                {

                    ListViewBase lv = _associatedObject as ListViewBase;

                    SemanticZoomLocation szLocation = new SemanticZoomLocation() { Item = LastFocusedItem };

                    lv.MakeVisible(szLocation);

                }

            }

            else

            {

                var page = FindPage();

                if (page != null)

                {

                    page.Loaded += page_Loaded;

 

                }

            }

        }

Here we introduce two helper functions FindSemanticZoom and FindPage to get the SemanticZoom and Page object:

        private Page FindPage()

        {

            DependencyObject parent;

            for (DependencyObject obj = _associatedObject; obj != null; obj = parent)

            {

                parent = VisualTreeHelper.GetParent(obj);

                Page page = parent as Page;

                if (page != null)

                {

                    return page;

                }

            }

            return null;

        }

 

        private SemanticZoom FindSemanticZoom()

        {

            DependencyObject parent;

            for (DependencyObject obj = _associatedObject; obj != null; obj = parent)

            {

                parent = VisualTreeHelper.GetParent(obj);

                SemanticZoom sz = parent as SemanticZoom;

                if (sz != null)

                {

                    return sz;

                }

            }

            return null;

        }

In page_Loaded event handler, we can call ScrollIntoView safely to scroll into view.

       void page_Loaded(object sender, RoutedEventArgs e)

        {

            if (_associatedObject != null)

            {

                ListViewBase lv = _associatedObject as ListViewBase;

                lv.ScrollIntoView(LastFocusedItem);

            }

        }

Finally, we use this behavior in our xaml code:

<GridView SelectedItem="{Binding SelectedItem, Mode=TwoWay}">

    <Interactivity:Interaction.Behaviors>

<utility:ScrollIntoViewBehavior LastFocusedItem="{Binding SelectedItem}"/>

    </Interactivity:Interaction.Behaviors>

</GridView>

Using this way, we can successfully to scroll the view position with or without SemanticZoom control. Here I attached the ScrollIntoViewBehavior code for your reference.

 

ScrollIntoViewBehavior .cs