Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I ScrollIntoView after changing the filter on a ListView in a MVVM (WPF) App?

I have an ObservableCollection in the VM that is displayed in the view in a ListView. When the selected item changes, the SelectionChanged event fires nicely. Below is how I have the ListView configured:

<ListView Grid.Row="3" Margin="5" AlternationCount="2" Name="_lvSettings" 
          IsSynchronizedWithCurrentItem="True"
          ItemsSource="{Binding Path=CollectionView}" 
          SelectedIndex="{Binding Path=SelectedSettingIndex}"
          SelectionChanged="OnSelectionChanged"  >
    <ListView.View>
        <GridView>
            <GridViewColumn Width="170" 
                            Header="{Binding Path=ShowAllDisplay}"
                            x:Name="_colSettings"  
                            DisplayMemberBinding="{Binding Path=Setting}"/>
            <GridViewColumn Header="Old Value" Width="150" 
                            DisplayMemberBinding="{Binding Path=OldVal}"/>
            <GridViewColumn Header="New Value" 
                            DisplayMemberBinding="{Binding Path=NewVal}" />
        </GridView>
    </ListView.View>
</ListView>

The problem I have is when I change the filter on the collection. The selected item remains the same, which is good, but the ListView changes to display from the first item, and often the selected item is out of view (but still the selected item).

In the VM, I have the property "SelectedSettingIndex" that throws the PropertyChanged event when it changes. Even if I raise the event myself manually (base.OnPropertyChanged("SelectedSettingIndex");) from the VM when the filter changes, the event does not seem to really get raised as the property did not really change. There must be a way to call ScrollIntoView or something similar in this scenario, but I can't figure out the correct event or trigger to do so. What is it that I am missing?

EDIT

Here is a, hopefully better, description of the issue I am concerned with:

1) I am using a CollectionViewSource in the VM to filter data.

2) There is a button for the user to toggle between a filter.

3) Lets assume the ListView has room to show up to 10 items at any given time.

4) The user selects item "A" in the filtered view which is at index 50 in the listview.

5) The user then clicks the button to turn filtering off.

Expected Results: The ListView is populated with the unfiltered list, Item "A" remains selected, and the ListView is "scrolled" such that item "A" is still visible.

Actual Results: The ListView is populated with the unfiltered list, Item "A" remains selected, and the ListView is "scrolled" to the top and showing the first 10 items. Item "A" is not in view.

like image 538
K J Avatar asked Dec 07 '22 15:12

K J


2 Answers

If you are using MVVM then you need to make sure that you have set the binding to the selected item in viewModel and that too with Mode=TwoWay ... and for scrolling on Selection we have to use a behavior on ListView(avoiding code behind)

You have to add a reference to System.Windows.Interactivity to use Behavior<T> class

Behavior

public class ScrollIntoViewForListView : Behavior<ListView>
{
    /// <summary>
    ///  When Beahvior is attached
    /// </summary>
    protected override void OnAttached()
    {
        base.OnAttached();
        this.AssociatedObject.SelectionChanged += AssociatedObject_SelectionChanged;
    }

    /// <summary>
    /// On Selection Changed
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="e"></param>
    void AssociatedObject_SelectionChanged(object sender,
                                           SelectionChangedEventArgs e)
    {
        if (sender is ListView)
        {
            ListView listview = (sender as ListView);
            if (listview.SelectedItem != null)
            {
                listview.Dispatcher.BeginInvoke(
                    (Action) (() =>
                                  {
                                      listview.UpdateLayout();
                                      if (listview.SelectedItem !=
                                          null)
                                          listview.ScrollIntoView(
                                              listview.SelectedItem);
                                  }));
            }
        }
    }
    /// <summary>
    /// When behavior is detached
    /// </summary>
    protected override void OnDetaching()
    {
        base.OnDetaching();
        this.AssociatedObject.SelectionChanged -=
            AssociatedObject_SelectionChanged;

    }
}

Usage

Add alias to XAML as xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"

then in your Control DisplayMemberBinding="{Binding Path=Setting}"/>

Now When ever "MySelectedItem" property is set in ViewModel the List will be scrolled when changes are refelected.

Change Notification

In viewModel should invoke INotifyProperty changed in the setter of properties that you have binded to your xaml so that changes in viewModel can be refelected to View ...

Using SelectionChanged Event in MVVM

Also in MVVM you dant have to use the "SelectionChnaged Event" as you can call a function in the Setter of MySelectedItem property or you can use EventToCommand class for explicit Event call..

Filtering

Google usage of ColletionViewSource for features like sorting ,filtering..etc

Hope it helps...

like image 77
Ankesh Avatar answered Dec 09 '22 05:12

Ankesh


Keep the SelectedItem of your ListView in a property:

public MyTypeOfObject SelectedItem { get; set; }

Assign the binding to XAML:

<ListView Name="MyListView" SelectedItem="{Binding SelectedItem}"...></ListView>

Now whenever you change filter do:

if (SelectedItem != null)
    MyListView.ScrollIntoView(SelectedItem);

EDIT:

To do it in your user control, in order to leave you view model clean from control references (ListView), catch a standard CollectionView event there or define your own that will be fired after filter or other job happened.

like image 39
Dummy01 Avatar answered Dec 09 '22 03:12

Dummy01