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?

Francesco Bonizzi
  • 5,142
  • 6
  • 49
  • 88

5 Answers5

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();
                       }));
        }
    }
ROMAN
  • 1,476
  • 2
  • 11
  • 24
  • 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. – Francesco Bonizzi Jan 20 '16 at 20:53
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; }
    }
}
Tomtom
  • 9,087
  • 7
  • 52
  • 95
  • 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) – Francesco Bonizzi Jan 19 '16 at 20:50
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.

Francesco Bonizzi
  • 5,142
  • 6
  • 49
  • 88
  • 1
    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. – Kylo Ren Jan 21 '16 at 10:21
  • In the solution above the RelayCommand is still MVVM-Light one (as in the question). – Francesco Bonizzi Jan 21 '16 at 10:24
  • 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. – Kylo Ren Jan 21 '16 at 10:26
  • 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. – Kylo Ren Jan 21 '16 at 10:31
  • 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. – Kylo Ren Jan 21 '16 at 10:38
  • @Alok I'm sorry, but I think it isn't open source, so I can't access to ICommand implementation! – Francesco Bonizzi Jan 21 '16 at 10:44
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/101280/discussion-between-alok-and-francesco-b). – Kylo Ren Jan 21 '16 at 10:46
  • 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 :) – rhcpfan Jan 26 '16 at 07:20
  • Probably for the bounty? :-) – Tom B. Jan 26 '16 at 09:03
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();
    }
}
Dominic Shaw
  • 210
  • 2
  • 10
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.

Péter Bozsó
  • 1,294
  • 8
  • 18