Search code examples
c#wpflistviewdata-binding

Databinding between 2 listViews selecting/deselecting records


I have 2 listviews, one that is prepopulated with a list of values and an empty list that should populate based on what is selected/deselected in the first view. I can get the second view to populate when a value is selected but I'm not sure how to remove an item if it's been deselected in the first view. If I do deselect a record it adds a null value to the 2nd list. I'm thinking there is a way around this with if/else statements but I'm thinking there might be a more elegant way to accomplish this.

View Model

private ObservableCollection<string> _firstList;
public ObservableCollection<string> FirstList
{
    get => _firstList;
    set
    {
        if (_firstList!= value)
        {
            _firstList= value;
            RaisePropertyChanged(nameof(FirstList));
        }
    }
}

private string _selectedRecord;
public string SelectedRecord
{
    get => _selectedRecord;
    set
    {
        if (_selectedRecord!= value)
        {
            _selectedRecord= value;
            RaisePropertyChanged(nameof(SelectedRecord));
            _secondList.Add(_selectedRecord);
        }
    }
}

private ObservableCollection<string> _secondList= 
    new ObservableCollection<string>();
public ObservableCollection<string> SecondList
{
    get => _secondList;
    set
    {
        if (_secondList!= value)
        {
            _secondList= value;
            RaisePropertyChanged(nameof(SecondList));
        }
    }
}

XAML -

<ListView ItemsSource="{Binding FirstList}"
SelectedItem="{Binding SelectedRecord}">
<ListView.ItemsPanel>
    <ItemsPanelTemplate>
        <StackPanel Orientation="Vertical"></StackPanel>
    </ItemsPanelTemplate>
</ListView.ItemsPanel>
<ListView.ItemTemplate>
    <DataTemplate>
        <StackPanel Orientation="Horizontal">
            <CheckBox IsChecked="{Binding IsSelected, 
                RelativeSource={RelativeSource 
                AncestorType=ListViewItem}}"/>
            <TextBlock Text="{Binding}" />
        </StackPanel>
    </DataTemplate>
  </ListView.ItemTemplate>
</ListView>

<ListView ItemsSource="{Binding SecondList}">
    <ListView.ItemsPanel>
        <ItemsPanelTemplate>
            <StackPanel Orientation="Vertical"></StackPanel>
        </ItemsPanelTemplate>
    </ListView.ItemsPanel>
    <ListView.ItemTemplate>
        <DataTemplate>
            <StackPanel Orientation="Horizontal">
                <TextBlock Text="{Binding}" />
            </StackPanel>
        </DataTemplate>
    </ListView.ItemTemplate>
</ListView>

Solution

  • Replace the string with a custom type and handle your logic of adding and removing items in the view model:

    public class ListItem : INotifyPropertyChanged
    {
        public ListItem(string value) =>
            Value = value;
    
        public string Value { get; }
    
        private bool _isSelected;
        public bool IsSelected
        {
            get { return _isSelected; }
            set { _isSelected = value; RaisePropertyChanged(nameof(IsSelected)); }
        }
    
        public event PropertyChangedEventHandler PropertyChanged;
        private void RaisePropertyChanged(string propertyName) =>
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
    

    View Model:

    public ViewModel()
    {
        InitializeComponent();
        DataContext = this;
    
        FirstList.CollectionChanged += (s, e) =>
        {
            if (e.NewItems != null)
                foreach (var newItem in e.NewItems.OfType<INotifyPropertyChanged>())
                    newItem.PropertyChanged += OnItemIsSelectedChanged;
            if (e.OldItems != null)
                foreach (var oldIrem in e.OldItems.OfType<INotifyPropertyChanged>())
                    oldIrem.PropertyChanged -= OnItemIsSelectedChanged;
        };
    
        FirstList.Add(new ListItem("a"));
        FirstList.Add(new ListItem("b"));
        FirstList.Add(new ListItem("c"));
    }
    
    private void OnItemIsSelectedChanged(object sender, PropertyChangedEventArgs e)
    {
        ListItem listItem = (ListItem)sender;
        if (listItem.IsSelected)
        {
            if (!SecondList.Contains(listItem))
                SecondList.Add(listItem);
        }
        else
            SecondList.Remove(listItem);
    }
    
    public ObservableCollection<ListItem> FirstList { get; } =
        new ObservableCollection<ListItem>();
    
    public ObservableCollection<ListItem> SecondList { get; }
        = new ObservableCollection<ListItem>();
    

    View:

    <ListView ItemsSource="{Binding FirstList}" SelectedItem="{Binding SelectedRecord}">
        <ListView.ItemTemplate>
            <DataTemplate>
                <StackPanel Orientation="Horizontal">
                    <CheckBox IsChecked="{Binding IsSelected}"/>
                    <TextBlock Text="{Binding Value}" />
                </StackPanel>
            </DataTemplate>
        </ListView.ItemTemplate>
    </ListView>
    
    
    
    <ListView ItemsSource="{Binding SecondList}">
        <ListView.ItemTemplate>
            <DataTemplate>
                <StackPanel Orientation="Horizontal">
                    <TextBlock Text="{Binding Value}" />
                </StackPanel>
            </DataTemplate>
        </ListView.ItemTemplate>
    </ListView>
    

    This is MVVM in a nutshell.