Binding ListView SelectedItems: Why a List Is Always Null

I am developing a UWP application with the Mvvm Light and Behaviors SDKs. I defined a multi-user ListView:

<ListView x:Name="MembersToInviteList" IsMultiSelectCheckBoxEnabled="True" SelectionMode="Multiple" ItemsSource="{Binding Contacts}" ItemTemplate="{StaticResource MemberTemplate}"> </ListView> 

I would like the button to snap to the MVVM-Light RelayCommand to get a list with the selected items:

 <Button Command="{Binding AddMembersToEvent}" CommandParameter="{Binding ElementName=MembersToInviteList, Path=SelectedItems}" Content="Ok"/> 

RelayCommand (from MVVM-Light structure):

 private RelayCommand<object> _addMembersToEvent; public RelayCommand<object> AddMembersToEvent { get { return _addMembersToEvent ?? (_addMembersToEvent = new RelayCommand<object>( (selectedMembers) => { // Test // selectedMembers is always null! })); } } 

I set a breakpoint inside the command and I noticed that selectedMembers is always null , although I select various elements. At the console output, I do not see any binding errors or anything else.

Also, if I pass the entire list as CommandParameter, and I set a breakpoint in the command definition, I noticed that I cannot access the values ​​of SelectedItems or SelecteRanges.

 <DataTemplate x:Name="MemberTemplate"> <Viewbox MaxWidth="250"> <Grid Width="250" Margin="5, 5, 5, 5" Background="{StaticResource MyLightGray}" BorderBrush="{StaticResource ShadowColor}" BorderThickness="0, 0, 0, 1" CornerRadius="4" Padding="5"> <Grid.ColumnDefinitions> <ColumnDefinition Width="Auto" /> <ColumnDefinition Width="1*" /> </Grid.ColumnDefinitions> <Grid Grid.Column="0" Width="45" Height="45" Margin="5,0,5,0" VerticalAlignment="Center" CornerRadius="50"> <Grid.Background> <ImageBrush AlignmentX="Center" AlignmentY="Center" ImageSource="{Binding Image.Url, Converter={StaticResource NullGroupImagePlaceholderConverter}}" Stretch="UniformToFill" /> </Grid.Background> </Grid> <TextBlock Grid.Column="1" Margin="3" VerticalAlignment="Center" Foreground="{StaticResource ForegroundTextOverBodyColor}" Style="{StaticResource LightText}" Text="{Binding Alias}" /> </Grid> </Viewbox> </DataTemplate> 

What reason? How can I get such a list?

+7
c # uwp binding xaml mvvm-light
source share
5 answers

One solution for passing SelectedItems from a ListView to a ViewModel (with RelayCommands) is described on the igralli blog.

Skip ListView SelectedItems for ViewModel in Generic Applications

Try the following code to get the selected objects from the parameter.

  private RelayCommand<IList<object>> _addMembersToEvent; public RelayCommand<IList<object>> AddMembersToEvent { get { return _addMembersToEvent ?? (_addMembersToEvent = new RelayCommand<IList<object>>( selectedMembers => { List<object> membersList = selectedMembers.ToList(); })); } } 
+6
source share

I made a small example for your case without MVVM-Light, and it works great.

Perhaps the problem is in the RelayCommand class. I wrote the following:

 public class RelayCommand<T> : ICommand { private readonly Action<T> execute; private readonly Predicate<T> canExecute; public RelayCommand(Action<T> execute, Predicate<T> canExecute = null ) { if (execute == null) throw new ArgumentNullException("execute"); this.execute = execute; this.canExecute = canExecute; } public bool CanExecute(object parameter) { if (canExecute == null) return true; return canExecute((T) parameter); } public void Execute(object parameter) { execute((T) parameter); } public event EventHandler CanExecuteChanged { add { CommandManager.RequerySuggested += value; } remove { CommandManager.RequerySuggested -= value; } } } 
+1
source share

I can’t find the reason, but you can easily work around this problem by adding the "IsSelected" property to your Contact object and checking the box in your template:

 <Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"> <Grid.RowDefinitions> <RowDefinition Height="*"></RowDefinition> <RowDefinition Height="Auto"></RowDefinition> <RowDefinition Height="Auto"></RowDefinition> </Grid.RowDefinitions> <ListView ItemsSource="{Binding Contacts}"> <ListView.ItemTemplate> <DataTemplate> <Grid> <CheckBox Content="{Binding Name}" IsChecked="{Binding IsSelected, Mode=TwoWay}"></CheckBox> </Grid> </DataTemplate> </ListView.ItemTemplate> </ListView> <TextBlock Grid.Row="1" Text="{Binding SelectedItemsOutput}"></TextBlock> <Button Grid.Row="2" Content="What is checked?" Command="{Binding GoCommand}"></Button> </Grid> 

and virtual machines, etc .:

 public class MainViewModel : INotifyPropertyChanged { private string _selectedItemsOutput; public ObservableCollection<Contact> Contacts { get; set; } = new ObservableCollection<Contact> { new Contact() { Id = 1, Name = "Foo" }, new Contact() { Id = 2, Name = "Bar" } }; public ICommand GoCommand => new RelayCommand(Go); public string SelectedItemsOutput { get { return _selectedItemsOutput; } set { if (value == _selectedItemsOutput) return; _selectedItemsOutput = value; OnPropertyChanged(); } } void Go() { SelectedItemsOutput = string.Join(", ", Contacts.Where(x => x.IsSelected).Select(x => x.Name)); } public event PropertyChangedEventHandler PropertyChanged; } public class Contact : INotifyPropertyChanged { private bool _isSelected; public int Id { get; set; } public string Name { get; set; } public bool IsSelected { get { return _isSelected; } set { if (value == _isSelected) return; _isSelected = value; OnPropertyChanged(); } } public override string ToString() { return Name; } public event PropertyChangedEventHandler PropertyChanged; } public sealed partial class MainPage : Page { public MainPage() { this.InitializeComponent(); DataContext = new MainViewModel(); } } 
0
source share

Only my five cents and maybe a completely long shot, but you should check this out :

If the ItemsSource implements IItemsRangeInfo, the SelectedItems collection is not updated based on the selection in the list. Use the SelectedRanges property instead.

I just guess based on Tomtom's answer, as he says he did it almost the same way as you did, and got a working solution. Maybe the difference is in this little detail. I have not tested it myself yet.

0
source share

Thanks to the Roman answer, I figured out how to solve the problem:

First of all, as Roman suggested:

 private RelayCommand<IList<object>> _addMembersToEvent; public RelayCommand<IList<object>> AddMembersToEvent { get { return _addMembersToEvent ?? (_addMembersToEvent = new RelayCommand<IList<object>>( selectedMembers => { List<object> membersList = selectedMembers.ToList(); })); } } 

Then XAML:

 <Button Command="{Binding AddMembersToEvent}" CommandParameter="{Binding ElementName=MembersToInviteList, Converter={StaticResource ListViewSelectedItemsConverter}}" Content="Ok"/> 

The difference here is that I passed the entire list as a parameter, not its SelectedItems property. Then, using IValueConverter I, converted from a ListView to an IList<object> from SelectedMember :

 public class ListViewSelectedItemsConverter : IValueConverter { public object Convert(object value, Type targetType, object parameter, string language) { var listView = value as ListView; return listView.SelectedItems; } public object ConvertBack(object value, Type targetType, object parameter, string language) { throw new NotImplementedException(); } } 

Thus, RelayCommand<IList<object>> got the correct list, not a null value.

0
source share

All Articles