如何在 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,那么您需要确保已将绑定设置为视图模型中的选定项,并且也使用 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}"
...>
这似乎在大多数情况下工作正常=)