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:
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 withOnCheckedCommand
andOnUnCheckedCommand
.I prefere handling
Checked
andUnChecked
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.