4

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

4 回答 4

13

如果您使用的是 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;

    }
}

用法

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

然后在你的Control DisplayMemberBinding="{Binding Path=Setting}"/>

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

变更通知

在 viewModel 中应该调用 INotifyProperty 在您绑定到 xaml 的属性设置器中更改,以便 viewModel 中的更改可以反映到 View ...

在 MVVM 中使用 SelectionChanged 事件

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

过滤

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

希望能帮助到你...

于 2012-04-13T07:59:07.730 回答
1

将 ListView 的 SelectedItem 保存在属性中:

public MyTypeOfObject SelectedItem { get; set; }

将绑定分配给 XAML:

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

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

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

编辑:

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

于 2012-04-13T06:18:42.507 回答
1

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

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

2) 在处理程序内的当前选定项上调用 ScrollToView 到 ListView 的 LayoutUpdated 事件。笨手笨脚,效率低下——就是不喜欢这样。

我不会将此标记为已回答,希望有更好的解决方案。把它放在这里是为了好奇或其他可能正在寻找类似问题的人。

于 2012-04-13T23:55:05.207 回答
1

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

附加属性:

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}"
   ...>

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

于 2017-03-15T20:49:19.307 回答