如何在 MVVM (WPF) 应用中更改列表视图上的筛选器后滚动到视图

本文关键字:视图 筛选 列表 滚动 MVVM WPF 应用 | 更新日期: 2023-09-27 18:31:03

我在 VM 中有一个 ObservableCollection,该集合显示在列表视图的视图中。当所选项发生更改时,SelectionChanged 事件会很好地触发。以下是我配置列表视图的方式:

<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 事件。即使我自己手动提出事件(基础。OnPropertyChanged("SelectedSettingIndex");)当筛选器更改时,事件似乎并未真正引发,因为属性并未真正更改。在这种情况下,必须有一种方法可以调用 ScrollIntoView 或类似的东西,但我无法找出正确的事件或触发器来执行此操作。我错过了什么?

编辑

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

1) 我在 VM 中使用 CollectionViewSource 来筛选数据。

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

3) 假设 ListView 在任何给定时间都有空间显示多达 10 个项目。

4) 用户在列表视图中的索引为 50 的过滤视图中选择项目"A"。

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

预期结果: 使用未筛选的列表填充列表视图,项目"A"保持选中状态,并且列表视图"滚动",以便项目"A"仍然可见。

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

如何在 MVVM (WPF) 应用中更改列表视图上的筛选器后滚动到视图

如果您使用的是 MVVM,那么您需要确保已将绑定设置为视图模型中的选定项,并且也使用 Mode=TwoWay ...为了在选择上滚动,我们必须在列表视图上使用行为(避免代码隐藏)

您必须添加对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}"/>

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

变更通知

在视图中模型应该调用 INotify属性 在已绑定到 xaml 的属性的集合器中更改,以便视图模型中的更改可以重新选择以查看...

在 MVVM 中使用选择更改事件

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

滤波

谷歌使用ColletionViewSource进行排序,过滤等功能。等

希望对你有帮助...

将列表视图的选定项保留在属性中:

public MyTypeOfObject SelectedItem { get; set; }

将绑定分配给 XAML:

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

现在,每当您更改过滤器时,请执行以下操作:

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

编辑:

若要在用户控件中执行此操作,为了使视图从控件引用 (ListView) 中清除,请在此处捕获标准CollectionView事件或定义自己的事件,该事件将在筛选器或其他作业发生后触发。

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

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

2) 将处理程序中当前选定项上的 ScrollToView 调用 ListView 的 LayoutUpdated 事件。粗暴,效率低下 - 只是普通不喜欢这样。

我不会为了更好的解决方案而标记这个答案。只是把这个放在这里给好奇的人或其他可能正在研究类似问题的人。

从其他一些帖子中找到了一个解决方案(信用),将附加属性绑定到集合视图过滤器的计数:

附加属性:

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);
        });
    }
}

视图:

<Listbox
    design:SelectingItemAttachedProperty.SelectingItem="{Binding CollectionViewFromVM.Count}"
   ...>

这似乎在大多数情况下工作正常=)