Search code examples
c#wpfmvvmselecteditem

Selected Items implementation in a ListBox acts odd in WPF and MVVM


I made quite research to implement selected items via MVVM in WPF. I thought I had success but now the selection is made according to scroll position. I select all items in the listbox but only first 11 marked as selected. If I scroll more, more selected. If I scroll to the bottom all items selected. Is there solution for this problem?

XAML:

<ListBox x:Name="DataListBox" SelectionMode="Extended" HorizontalAlignment="Left" Margin="5,5,0,0" Grid.Row="1"  Grid.Column="0" Grid.RowSpan="8" 
                                 VerticalAlignment="Top" Height="200" Width="200"                                 
                                 ItemsSource="{Binding DataListBoxItemsSource, UpdateSourceTrigger=PropertyChanged}"
                                 SelectedItem="{Binding DataListBoxSelectedItem, UpdateSourceTrigger=PropertyChanged}"
                                >

                            <ListBox.InputBindings>
                                <KeyBinding Command="ApplicationCommands.SelectAll" Modifiers="Ctrl" Key="A" />
                            </ListBox.InputBindings>

                            <ListBox.ItemContainerStyle>
                                <Style TargetType="ListBoxItem">
                                    <Setter Property="IsSelected" Value="{Binding IsSelected}"/>
                                </Style>
                            </ListBox.ItemContainerStyle>

                            <i:Interaction.Triggers>
                                <i:EventTrigger EventName="SelectionChanged" >
                                    <i:CallMethodAction TargetObject="{Binding}" MethodName="DataListBox_SelectionChanged"/>
                                </i:EventTrigger>
                            </i:Interaction.Triggers>
                        </ListBox>

View Model

        public async void CreateLayersTOC()
    {
        if (MapView.Active != null)
        {
            if (DataListBoxSelectedItem != null || FavoriteTabsSelectedItem != null)
                MainStackPanelIsEnabled = false;
            LayerNames = new List<string>();
            await Task.Run(() =>
            {
                MessageBox.Show("source count " + DataListBoxItemsSource.Count);//58 items all selected
                if (DataListBoxSelectedItem != null)
                    foreach (ItemPresenter itemP in DataListBoxItemsSource)
                    {
                        if (itemP.IsSelected)
                        {
                            if (LayerNames.Contains(itemP.ToString()) == false)
                                LayerNames.Add(itemP.ToString());
                        }

                    }

                if (FavoriteTabsSelectedItem != null)
                {
                    foreach (ItemPresenter itemP in FavListBoxItemsSource)
                    {
                        if (itemP.IsSelected)
                        {
                            if (LayerNames.Contains(itemP.ToString()) == false)
                                LayerNames.Add(itemP.ToString());
                        }
                    }
                }

                MessageBox.Show("Coll" + LayerNames.Count);//Count depends on scroll position
            });

            //do stuff
        }
        else
            MessageBox.Show("Make sure to have a map available before adding layers to a map");
        MainStackPanelIsEnabled = true;
    }

 public class ItemPresenter : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;
        protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
        {
            this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }

        private readonly string _value;

        public ItemPresenter(string value)
        {
            _value = value;
        }

        public override string ToString()
        {
            return _value;
        }

        private bool _isSelected;

        public bool IsSelected
        {
            get { return _isSelected; }
            set
            {
                if (_isSelected != value)
                {
                    _isSelected = value;
                    OnPropertyChanged();
                }
            }
        }
    }

Solution

  • Here is a simple example of everything that is necessary to bind the SelectedItem of a ListBox and the IsSelected property of the ListBoxItems.

    XAML:

    <ListBox ItemsSource="{Binding Items}"
             SelectedItem="{Binding SelectedItem}"
             SelectionMode="Extended">
        <ListBox.ItemContainerStyle>
            <Style TargetType="ListBoxItem">
                <Setter Property="IsSelected" Value="{Binding Selected}"/>
            </Style>
        </ListBox.ItemContainerStyle>
        <ListBox.ItemTemplate>
            <DataTemplate>
                <TextBlock Text="{Binding Name}"/>
            </DataTemplate>
        </ListBox.ItemTemplate>
    </ListBox>
    

    View Model:

    public class DataItem : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;
    
        public string Name { get; set; }
    
        private bool selected;
    
        public bool Selected
        {
            get { return selected; }
            set
            {
                selected = value;
                PropertyChanged?.Invoke(this,
                    new PropertyChangedEventArgs(nameof(Selected)));
    
                Debug.WriteLine(Name + " selected: " + selected);
            }
        }
    }
    
    public class ViewModel : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;
    
        public ObservableCollection<DataItem> Items { get; }
            = new ObservableCollection<DataItem>();
    
        public IEnumerable<DataItem> SelectedItems
        {
            get { return Items.Where(i => i.Selected); }
        }
    
        private DataItem selectedItem;
    
        public DataItem SelectedItem
        {
            get { return selectedItem; }
            set
            {
                selectedItem = value;
                PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(SelectedItem)));
            }
        }
    }
    
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
    
            var vm = new ViewModel();
            vm.Items.Add(new DataItem { Name = "Item 1" });
            vm.Items.Add(new DataItem { Name = "Item 2" });
            vm.Items.Add(new DataItem { Name = "Item 3" });
            vm.Items.Add(new DataItem { Name = "Item 4" });
            vm.Items.Add(new DataItem { Name = "Item 5" });
    
            DataContext = vm;
        }
    }