在MVVM(WPF)应用程序中更改ListView上的过滤器后,如何ScrollIntoView?

时间:2012-04-13 05:46:46

标签: c# wpf listview mvvm filter

我在VM中有一个ObservableCollection,它显示在ListView的视图中。当所选项更改时,SelectionChanged事件将很好地触发。下面是我如何配置ListView:

<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>

我遇到的问题是当我更改集合上的过滤器时。所选项目保持不变,这很好,但ListView会更改为从第一个项目显示,并且所选项目通常不在视图范围内(但仍然是所选项目)。

在VM中,我有属性“SelectedSettingIndex”,它在更改时抛出PropertyChanged事件。即使我在过滤器更改时从VM手动提升事件(base.OnPropertyChanged(“SelectedSettingIndex”);),事件似乎也没有真正引发,因为属性没有真正改变。在这种情况下必须有一种方法可以调用ScrollIntoView或类似的东西,但我无法弄清楚正确的事件或触发器这样做。我错过了什么?

修改

以下是对我所关注的问题的一个希望更好的描述:

1)我在VM中使用CollectionViewSource来过滤数据。

2)有一个按钮供用户在过滤器之间切换。

3)让我们假设ListView有空间在任何给定时间显示最多10个项目。

4)用户在列表视图中的索引为50的筛选视图中选择项目“A”。

5)然后用户单击该按钮以关闭过滤。

预期结果:ListView填充了未过滤的列表,项目“A”保持选中状态,ListView被“滚动”,以便项目“A”仍然可见。

实际结果:ListView填充了未过滤的列表,项目“A”保持选中状态,ListView“滚动”到顶部并显示前10个项目。项目“A”不在视野中。

4 个答案:

答案 0 :(得分:12)

如果您正在使用MVVM,那么您需要确保已在viewModel中设置绑定到所选项目,并且使用Mode=TwoWay设置绑定...并且对于选择滚动我们必须使用行为ListView(避免代码隐藏)

您必须添加对System.Windows.Interactivity的引用才能使用Behavior<T> class

行为

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;

    }
}

用法

XAML的别名添加到xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"

然后在你的Control                                                                                                                                    DisplayMemberBinding =“{Binding Path = Setting}”/&gt;                                                             

现在当ViewModel中设置了“MySelectedItem”属性时,将在重新选择更改时滚动列表。

更改通知

在viewModel中应调用已绑定到xaml的属性设置器中更改的INotifyProperty,以便可以将viewModel中的更改重新选择为View ...

在MVVM中使用SelectionChanged事件

同样在MVVM中,您必须使用“SelectionChnaged Event”,因为您可以在Setter of MySelectedItem属性中调用函数,或者您可以使用EventToCommand类进行显式事件调用。

过滤

Google使用ColletionViewSource来处理排序,过滤等内容。

希望它有所帮助...

答案 1 :(得分:1)

将ListView的SelectedItem保存在属性中:

public MyTypeOfObject SelectedItem { get; set; }

将绑定分配给XAML:

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

现在每当你更改过滤器时都会执行:

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

修改

要在您的用户控件中执行此操作,以便从控件引用(ListView)中查看模型清理,在那里捕获标准CollectionView事件或定义您自己的事件将在过滤器或其他工作发生后触发

答案 2 :(得分:1)

从其他帖子(Credit)找到解决方案,将附加属性绑定到collectionview过滤器的计数:

附属物:

<div *ngFor="#value of columnsNames">
    {{value }}<input type = "text" [(ngModel)]="SomeDynamicValue[value]">
</div>

查看:

public class SelectingItemAttachedProperty
{
    public static readonly DependencyProperty SelectingItemProperty = DependencyProperty.RegisterAttached(
        "SelectingItem",
        typeof(int),
        typeof(SelectingItemAttachedProperty),
        new PropertyMetadata(default(int), OnSelectingItemChanged));

    public static int GetSelectingItem(DependencyObject target)
    {
        return (int)target.GetValue(SelectingItemProperty);
    }

    public static void SetSelectingItem(DependencyObject target, int value)
    {
        target.SetValue(SelectingItemProperty, value);
    }

    static void OnSelectingItemChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
    {
        var lb = sender as ListBox;
        if (lb?.SelectedItem == null)
            return;

        lb.Dispatcher.InvokeAsync(() =>
        {
            lb.UpdateLayout();
            lb.ScrollIntoView(lb.SelectedItem);
        });
    }
}

这似乎适用于大多数情况=)

答案 3 :(得分:0)

好的 - 所以我找到了2个有效的解决方案,但不是100%满意:

1)使用ViewModel的Mediator模式通知视图过滤器已更改。然后视图将在当前选定的项目上调用ScrollToView。虽然我喜欢用于VM到VM通知的Mediator,但在ViewModel及其匹配的View之间使用它会感觉很脏。

2)将处理程序内当前所选项目上的ScrollToView调用到ListView的LayoutUpdated事件。笨手笨脚,效率低下 - 只是简单不喜欢这样。

为了找到更好的解决方案,我不打算回答这个问题。只是把这个放在好奇或其他可能正在寻找类似问题的人身上。