How do you retain selection in a filterable ListBox?

Emon Haque 3,176 Reputation points
2021-05-26T22:42:22.083+00:00

Here in my Rent view I've SelectionMode = SelectionMode.Extended set for the ListBox and if I don't filter and keep selecting items it retains all selections. If I apply a filter and select an item, it retains that selection when I remove query. If I, however, apply another filter selections get removed:

99968-test.gif

I thought somewhere in my view I've some issue and tested in another app with these in MainWindow.xaml:

<Grid Margin="20">  
    <Grid.RowDefinitions>  
        <RowDefinition Height="Auto"/>  
        <RowDefinition Height="Auto"/>  
        <RowDefinition />  
    </Grid.RowDefinitions>  
    <TextBox Text="{Binding Query, UpdateSourceTrigger=PropertyChanged}"/>  
    <TextBlock Grid.Row="1" x:Name="countBlock" HorizontalAlignment="Right"/>  
    <ListBox Grid.Row="2"   
             x:Name="list"  
             ItemsSource="{Binding Entries}"  
             SelectionMode="Extended"  
             HorizontalContentAlignment="Stretch">  
        <ListBox.ItemTemplate>  
            <DataTemplate>  
                <Grid>  
                    <Grid.ColumnDefinitions>  
                        <ColumnDefinition Width="150"/>  
                        <ColumnDefinition />  
                        <ColumnDefinition Width="70"/>  
                        <ColumnDefinition Width="70"/>  
                        <ColumnDefinition Width="70"/>  
                    </Grid.ColumnDefinitions>  
                    <TextBlock Text="{Binding Date}"/>  
                    <TextBlock Grid.Column="1" Text="{Binding Particulars}" />  
                    <TextBlock Grid.Column="2" Text="{Binding Debit}" />  
                    <TextBlock Grid.Column="3" Text="{Binding Credit}" />  
                    <TextBlock Grid.Column="4" Text="{Binding Balance}" />  
                </Grid>  
            </DataTemplate>  
        </ListBox.ItemTemplate>  
    </ListBox>  
</Grid>  

and these in MainWindow.xaml.cs:

public partial class MainWindow : Window  
{  
    List<Entry> entries = new();  
    string query;  
    public string Query {  
        get { return query; }  
        set { query = value; Entries.Refresh(); }  
    }  
    public ICollectionView Entries { get; set; }  
    public MainWindow() {  
        InitializeComponent();  
        var particulars = new string[] { "A", "B", "C", "D", "E" };  
        Random rand = new();  
        for (int i = 0; i < 50; i++) {  
            entries.Add(new Entry() {  
                Date = new DateTime(rand.Next(2000,2021), rand.Next(1,13), rand.Next(1,29)),  
                Particulars = particulars[rand.Next(5)],  
                Debit = rand.Next(10,100000),  
                Credit = rand.Next(10,1000),  
                Balance = rand.Next(10,10000)  
            });  
        }  
        Entries = CollectionViewSource.GetDefaultView(entries);  
        DataContext = this;  
        list.SelectionChanged += onSelectionChanged;  
        Entries.Filter = filter;  
        Loaded += (s, e) => countBlock.Text = "0/" + list.Items.Count + " selected";  
    }  

    void onSelectionChanged(object sender, System.Windows.Controls.SelectionChangedEventArgs e) {  
        countBlock.Text = list.SelectedItems.Count + "/" + list.Items.Count + " selected";  
    }  

    bool filter(object o) {  
        if (string.IsNullOrWhiteSpace(Query)) return true;  
        return ((Entry)o).Particulars.ToLower().Contains(Query);  
    }  
}  
public class Entry  
{  
    public DateTime Date { get; set; }  
    public String Particulars { get; set; }  
    public int Debit { get; set; }  
    public int Credit { get; set; }  
    public int Balance { get; set; }  
}  

and it actually removes selections here as well! Is there something like SelectionRetention = RetentionMode.Retain? which I can use to keep those in SelectedItems or I've, no way but, to keep track of those manually?

Windows Presentation Foundation
Windows Presentation Foundation
A part of the .NET Framework that provides a unified programming model for building line-of-business desktop applications on Windows.
2,624 questions
0 comments No comments
{count} votes

Accepted answer
  1. DaisyTian-1203 11,611 Reputation points
    2021-05-27T05:03:19.03+00:00

    There is no SelectionRetention = RetentionMode.retain in the official Microsoft ListBox document now. Maybe there's a third tool that can do that, but if you just use ListBox, we need to implement it manually now. I add some parts to your demo code to implement it manually:

    1. Add a Flag property to your Entry like: private bool flag=false;
      public bool Flag
      {
      get{ return flag; }
      set
      {
      flag = value;
      OnPropertyChanged("Flag");
      }
      }

    2 . Update onSelectionChanged as:

     void onSelectionChanged(object sender, System.Windows.Controls.SelectionChangedEventArgs e)  
            {  
                int selectItem = 0;  
                foreach(Entry item in list.SelectedItems)  
                {  
                    item.Flag = !item.Flag;  
                }  
                foreach (Entry item in list.Items)  
                {  
                    if(item.Flag==true)  
                    {  
                        selectItem++;  
                    }  
                }  
                countBlock.Text = selectItem + "/" + list.Items.Count + " selected";  
            }  
    

    3.Update the Grid as:

     <Grid>  
                            <Grid.Style>  
                                <Style TargetType="Grid">  
                                    <Style.Triggers>  
                                        <DataTrigger Binding="{Binding Path=Text,ElementName=txtFlag}" Value="True" >  
                                            <Setter Property="Grid.Background" Value="{DynamicResource {x:Static SystemColors.HighlightBrushKey}}"/>  
                                        </DataTrigger>  
                                    </Style.Triggers>  
                                </Style>  
                            </Grid.Style>  
                            <Grid.ColumnDefinitions>  
                                <ColumnDefinition Width="150"/>  
                                <ColumnDefinition />  
                                <ColumnDefinition Width="70"/>  
                                <ColumnDefinition Width="70"/>  
                                <ColumnDefinition Width="70"/>  
                                <ColumnDefinition Width="70"/>  
                            </Grid.ColumnDefinitions>  
                            <TextBlock Text="{Binding Date}"/>  
                            <TextBlock Grid.Column="1" Text="{Binding Particulars}" />  
                            <TextBlock Grid.Column="2" Text="{Binding Debit}" />  
                            <TextBlock Grid.Column="3" Text="{Binding Credit}" />  
                            <TextBlock Name="txtFlag" Grid.Column="5" Text="{Binding Flag,Mode=TwoWay,UpdateSourceTrigger=LostFocus}" Visibility="Hidden"/>  
                        </Grid>  
    

    The result picture is:
    100092-3.gif


    If the response is helpful, please click "Accept Answer" and upvote it.
    Note: Please follow the steps in our documentation to enable e-mail notifications if you want to receive the related email notification for this thread.

    1 person found this answer helpful.
    0 comments No comments

1 additional answer

Sort by: Most helpful
  1. Emon Haque 3,176 Reputation points
    2021-06-11T11:31:31.7+00:00

    Another way is keep a List of SelectedItems (ExcludedLease), which I have in this app, and add one more line in the filter function:

    bool filter(object o) {  
        var lease = (Lease)o;  
        if (string.IsNullOrWhiteSpace(FilterName)) return !lease.IsExpired;  
        return   
            !lease.IsExpired &&   
            (ExcludedLeases.Contains(lease) ||  
            lease.TenantName.ToLower().Contains(FilterName) ||   
            lease.PlotName.ToLower().Contains(FilterName) ||  
            lease.SpaceName.ToLower().Contains(FilterName));  
    }  
    

    I've added ExcludedLeases.Contains(lease) || line in addition to what I'd earlier and it also works:

    104746-test.gif

    0 comments No comments