ListView SelectedItems binding: why the list is always null
Asked Answered
E

5

10

I'm developing a UWP App, with Mvvm Light and Behaviours SDK. I defined a multi selectable ListView:

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

</ListView>

I'd like, with a button binded to a MVVM-Light RelayCommand, to obtain a list with the selected items:

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

The RelayCommand (of MVVM-Light framework):

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

I put a breakpoint inside the command, and I notice that selectedMembers is always null, although I select various items. By the console output I don't see any binding error or something else.

Also, if I pass as CommandParameter the whole list, and I put a breakpoint inside command's definition, i notice that I can't access to SelectedItems nor SelecteRanges value.

<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's the reason? How can I obtain such list?

Expressly answered 16/1, 2016 at 0:57 Comment(3)
Is the RelayCommand an ICommand-Implementation written by you or does it come from the MVVM-Light framework?Doggerel
@Doggerel Mvvm MVVM-Light frameworkExpressly
Can you show us your ItemTemplate?Doggerel
L
7

One of the solutions to pass SelectedItems from ListView in ViewModel (with RelayCommands) is described in igralli's blog.

Pass ListView SelectedItems to ViewModel in Universal apps

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();
                       }));
        }
    }
Lanta answered 20/1, 2016 at 16:13 Comment(1)
Your answer helped me a lot to solve the issue. The code you posted isn't enough to make it work; also the link you posted sees the problem in another way: it binds the list of selected items every time you select an item, my problem were to bind it one time when a user clicks the button. Also I didn't used behaviours sdk.Expressly
D
1

I've made a small example for your case without MVVM-Light and it works perfect.

Maybe the problem is within the RelayCommand-class. I've written 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; }
    }
}
Doggerel answered 19/1, 2016 at 9:36 Comment(1)
I tried the other implementation of command, but it isn't working. Same issue. I just changed a tag to be more specific: UWP. (Before I couldn't put it due to max tags)Expressly
E
1

Thanks to Roman's answer I figured out how to solve the issue:

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, the XAML:

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

The difference here is that I passed the whole list as parameter, not it's SelectedItems property. Then, using an IValueConverter I converted from the ListView object to IList<object> of 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();
    }
}

In this way the RelayCommand<IList<object>> got the right list and not a null value.

Expressly answered 20/1, 2016 at 20:59 Comment(9)
you may have found your solution but I ran your code given above without changing anything(with my relay command class) and it worked fine. selectedMembers always has values. I think the issue was with your RelayCommand Class.Darin
In the solution above the RelayCommand is still MVVM-Light one (as in the question).Expressly
can you add this class in the question also? as I don't have MVVM-Light and also other who also go through this post might want that.Darin
actually what I can't fathom in the solution is that if value was reaching NULL to RelayCommand, how is a converter able to provide it. I mean it's just another class and also doesn't do anything that would change anything in listview.Darin
Could you paste the code of RelayCommand class in the question instead of web link as web link also doesn't have class def. I've to download the MVVM light and I can't install anything on my system. sorry for your troubles.Darin
@Alok I'm sorry, but I think it isn't open source, so I can't access to ICommand implementation!Expressly
Let us continue this discussion in chat.Darin
If you found the solution "Thanks to Roman's answer", why you mark your own answer as a solution? It's like liking your own photos on Facebook :)Sultry
Probably for the bounty? :-)Nicolas
G
0

I can't find a reason but you can easily skirt the issue altogether by having an "IsSelected" property on your Contact object and putting a check box on 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 VMs 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();
    }
}
Glyn answered 19/1, 2016 at 9:35 Comment(1)
You're right, but I can't put an "IsSelected" property to my contact model because I don't really have access to that class.Expressly
S
0

Just my five cents and might be an absolutely long shot, but you should check this:

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

I am just guessing based on Tomtom's answer, since he says he did almost exactly as you and got a working solution. Maybe the difference is in this little detail. I haven't checked it myself yet.

Sisneros answered 19/1, 2016 at 21:17 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.