具有实时成形/分组的ListBox-如何在项目重新分组时保持选择

本文关键字:项目 选择 ListBox- 实时 | 更新日期: 2023-09-27 18:20:07

我的视图模型中有一个ObservableCollection,视图中有一个子CollectionViewSourceListBox

ListBoxCollectionViewSource结合。CollectionViewSource绑定到ObservableCollection,对项目进行排序并将它们分组。我通过CollectionViewSource上的IsLiveGroupingRequestedIsLiveSortingRequested属性启用了实时排序和实时分组,因此每当底层视图模型对象发生更改时,它们都会在ListBox中重新排序和分组。这一切都很好。

这个问题与选择有关。如果我在ListBox中选择了一个项目,然后由于视图模型对象以某种方式发生了更改而对其重新分组,则当该项目移动到新组时,该项目将被取消选择

重新分组所选项目时,如何保留所选内容

下面是一个简单的XAML示例,显示了这个问题。如果AllItems中某个对象的Category属性发生更改,则由于实时成形,该项目将正确地重新分组。但是,如果该项目已被选中,它将变为未被选中。

<Grid>
    <Grid.Resources>
        <CollectionViewSource x:Key="MyItems" Source="{Binding AllItems}" IsLiveGroupingRequested="True" IsLiveSortingRequested="True">
            <CollectionViewSource.SortDescriptions>
                <componentModel:SortDescription PropertyName="Category" />
                <componentModel:SortDescription PropertyName="Name" />
            </CollectionViewSource.SortDescriptions>
            <CollectionViewSource.GroupDescriptions>
                <PropertyGroupDescription PropertyName="Category" />
            </CollectionViewSource.GroupDescriptions>
        </CollectionViewSource>
    </Grid.Resources>
    <ListBox ItemsSource="{Binding Source={StaticResource MyItems}}">
        <ListBox.GroupStyle>
            <GroupStyle>
                <GroupStyle.HeaderTemplate>
                        <DataTemplate>
                                <TextBlock Text="{Binding Name}" />
                        </DataTemplate>
                </GroupStyle.HeaderTemplate>
            </GroupStyle>
        </ListBox.GroupStyle>
    </ListBox>
</Grid>

具有实时成形/分组的ListBox-如何在项目重新分组时保持选择

目前没有简单的解决方案。

我可以看到两种解决方案:

1) 用户手动停止实时更新。允许使用跳跃数据是很容易出错的。

示例:MS 中WCF日志查看器中的暂停按钮

2) 在开始更新数据之前,请记住所选项目。当更新完成时,只返回选择。

示例:如何防止WPF数据网格在更新项目时取消选择所选项目?

在我的情况下,我很少需要在视图模型中将所选项目设置为null。因此,我使用以下变通方法。请注意,必须调用不同的方法才能将所选值设置为null的尴尬方式。

private ItemViewModel selectedItem;
private ItemViewModel prevSelectedItem;
/// <summary>
/// Gets or sets the selected item. Use <see cref="ClearSelectedItem"/> to set it to null.
/// </summary>
public ItemViewModel SelectedItem
{
    get => selectedItem;
    set
    {
        if (!Equals(selectedItem, value))
        {
            prevSelectedItem = selectedItem;
            selectedItem = value;
            RaisePropertyChanged();
            if (value == null)
            {
                // Ignore null set by the live grouping/sorting/filtering in the CollectionViewSource
                System.Windows.Application.Current.MainWindow.Dispatcher.BeginInvoke(
                    new Action(() =>
                    {
                        SelectedItem = prevSelectedItem;
                    }));
            }
        }
    }
}
/// <summary>
/// Sets <see cref="SelectedItem"/> to null.
/// </summary>
public void ClearSelectedItem()
{
    selectedItem = null;
    RaisePropertyChanged(nameof(SelectedItem));
}
public event PropertyChangedEventHandler PropertyChanged;
/// <summary>
/// Raises a <see cref="PropertyChanged"/> event for the specified property name.
/// </summary>
/// <param name="propertyName">The name of the property to notify.</param>
protected void RaisePropertyChanged([CallerMemberName] string propertyName = null)
{
    PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}