ListBox with live shaping/grouping - how to keep selection when item is regrouped?
Asked Answered
H

2

9

I have an ObservableCollection in my view-model, and a CollectionViewSource and ListBox in my view.

The ListBox binds to the CollectionViewSource. The CollectionViewSource binds to the ObservableCollection, sorting the items and arranging them into groups. I have live sorting and live grouping enabled via the IsLiveGroupingRequested and IsLiveSortingRequested properties on the CollectionViewSource, so whenever the underlying view-model objects change, they are re-sorted and re-grouped in the ListBox. This all works fine.

The problem has to do with the selection. If I select an item in the ListBox, and it is then re-grouped due to the view-model object being changed in some way, the item will be un-selected when it is moved to the new group.

How can I keep the selection when the selected item is re-grouped?

Here is a simple trimmed-down XAML example showing the problem. If the Category property of one of the objects in AllItems changes, the item will be correctly re-grouped thanks to live shaping. However, if that item was selected, it will become unselected.

<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>
Helicograph answered 27/6, 2013 at 23:57 Comment(0)
F
2

Currently there is no simple solution.

I can see two solutions:

1) Manually stop live updates by user. It is error prone to allow work with jumping data.

Example: Pause button in WCF log viewer from MS.

2) Before you start to update data remember selected item. When update will finish just return selection.

Example: How To Prevent WPF DataGrid From De-Selecting SelectedItem When Items Updated?

Formation answered 28/8, 2013 at 11:41 Comment(0)
S
0

In my case, I rarely have to set the selected item to null in the view-model. So I use the following workaround. Note the awkward way of having to call a different method to set the selected value to 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));
}
Selfdevotion answered 1/2, 2022 at 20:48 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.