Home > Back-end >  Databinding between 2 listViews selecting/deselecting records
Databinding between 2 listViews selecting/deselecting records

Time:09-16

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>

CodePudding user response:

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.

CodePudding user response:

You can trigger a command when a checkbox became UnChecked..

<CheckBox xmlns:b="http://schemas.microsoft.com/xaml/behaviors" IsChecked="{Binding IsSelected, RelativeSource={RelativeSource AncestorType=ListViewItem}}">
    <b:Interaction.Triggers>
        <b:EventTrigger EventName="Unchecked">
            <b:InvokeCommandAction Command="{Binding DataContext.OnUnCheckedCommand, ElementName=myList}" PassEventArgsToCommand="True" />
        </b:EventTrigger>
    </b:Interaction.Triggers>
</CheckBox>

Add x:Name="myList" to the first <ListView/>.

The command will be defined in your ViewModel like this (I use Prism DelegateCommand)

public DelegateCommand<RoutedEventArgs> OnUnCheckedCommand { set; get; }
public MyViewModel(){
    OnUnCheckedCommand = new DelegateCommand<RoutedEventArgs>(args =>
    {
        if ((args.Source as FrameworkElement)?.DataContext is string rowItem)
            SecondList.Remove(rowItem);
    });
    // ..
}

Notes:

  1. You can add another command to detect the Checked event using <b:EventTrigger EventName="Checked">.. I see the behavior works well when you click on the row, but when you click on the checkbox itself, the row will not be selected, so you can handle this with OnCheckedCommand and OnUnCheckedCommand.

  2. I prefere handling Checked and UnChecked events in .xaml.cs code and from there I will communicate with the ViewModel to execute the appropriate function.


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.

No, there is nothing wrong with if/else, keep it simple

Replace

_secondList.Add(_selectedRecord);

With

if(_selectedRecord != null)
   SecondList.Add(_selectedRecord); // not _secondList

Note: no need for setters and getters in FirstList and SecondList properties, as you are not assigning = values to them.

  • Related