我在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”不在视野中。
答案 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中,您必须使用“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事件。笨手笨脚,效率低下 - 只是简单不喜欢这样。
为了找到更好的解决方案,我不打算回答这个问题。只是把这个放在好奇或其他可能正在寻找类似问题的人身上。