VirtualizingStackPanel + MVVM + 多选

28

我已经实现了一个类似于此帖子中描述的选择模式,使用ViewModel存储IsSelected值,并将 ListViewItem.IsSelected 绑定到ViewModel的IsSelected属性:

<ListView.ItemContainerStyle>
    <Style TargetType="{x:Type ListViewItem}">
        <Setter Property="IsSelected" Value="{Binding Mode=TwoWay, Path=IsSelected}"/>
    </Style>
</ListView.ItemContainerStyle>

它通常有效,但我遇到了一个严重的问题。如果在列表视图中使用 VirtualizingStackPanel 作为面板,只有可见的 ListViewItem 会被创建。如果我使用 "Ctrl+A" 来选择所有项目,或者通过使用如 "Shift+Ctrl+End" 这样的快捷键组合来选择第一个项目,所有项目都会被选中,但对于不可见的项目,ViewModel 的 IsSelected 属性没有被设置为 true。这是合乎逻辑的,因为如果 ListViewItem 没有被创建,绑定就无法工作。

有人遇到过相同的问题,并找到了解决方案(除了不使用 VirtualizingStackPanel)吗?


尝试使用以下完整解决方案来解决此问题:http://stackoverflow.com/a/29545790 - qwerty176
3个回答

32

我发现了在MVVM模式中处理选择的另一种方法,这种方法解决了我的问题。不再在ViewModel中维护选择状态,而是从ListView/ListBox中获取选择项,并将其作为参数传递给命令。这一切都在XAML中完成:

<ListView 
    x:Name="_items"
    ItemsSource="{Binding Items}" ... />

<Button 
    Content="Remove Selected" 
    Command="{Binding RemoveSelectedItemsCommand}" 
    CommandParameter="{Binding ElementName=_items, Path=SelectedItems}"/>

在我的ViewModel中:

private void RemoveSelection(object parameter)
{
    IList selection = (IList)parameter;
    ...
}

7
我喜欢这种方法,但是你不能用这种方式从视图模型编程地选择项目。 - sourcenouveau
2
如果ListView和按钮位于不同的用户控件范围内,它也无法工作。 - Rakshit Bakshi

8
在我的情况下,我通过从ListBox派生出一个ListBoxEx类,并添加代码以响应选择更改,在项目视图模型上强制执行选择状态来解决了这个问题。
private readonly List<IListItemViewModelBase> selectedItems = new List<IListItemViewModelBase>();

protected override void OnSelectionChanged(SelectionChangedEventArgs e)
{
    base.OnSelectionChanged(e);

    bool isVirtualizing = VirtualizingStackPanel.GetIsVirtualizing(this);
    bool isMultiSelect = (SelectionMode != SelectionMode.Single);

    if (isVirtualizing && isMultiSelect)
    {
        var newSelectedItems = SelectedItems.Cast<IListItemViewModelBase>();

        foreach (var deselectedItem in this.selectedItems.Except(newSelectedItems))
        {
            deselectedItem.IsSelected = false;
        }

        this.selectedItems.Clear();
        this.selectedItems.AddRange(newSelectedItems);

        foreach (var newlySelectedItem in this.selectedItems)
        {
            newlySelectedItem.IsSelected = true;
        }
    }
}

完美。通过保持视图模型同步解决了根本问题! - RandomEngy
终于解决了困扰我多年的虚拟化/MVVM/IsSelected问题! - Andrew Stephens

1

除了不使用 VirtualizingStackPanel,我能想到的唯一方法就是捕获那些键盘快捷键,并编写方法以修改您 ViewModel 中某个范围的项,使它们的 IsSelected 属性设置为 True(例如,SelectAll()SelectFromCurrentToEnd())。基本上绕过 BindingListViewItem 上控制这种情况下的选择。


3
不要忘记键盘或鼠标能产生这个问题的各种不同方式!Ctrl+A,Shift+End,Shift+Home,以及你可以用鼠标Shift-点击任意范围;所有这些都可能导致相同的问题。 - Qwertie

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接