Привязка ListView SelectedItems: почему список всегда равен null

я разрабатываю приложение UWP с MVVM Light и поведением SDK. Я определил несколько выбираемых ListView:

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

</ListView>

Я бы хотел, с кнопкой, привязанной к в MVVM-свет RelayCommand, чтобы получить список с выбранными элементами:

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

RelayCommand (рамки MVVM-света):

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

я поставил точку останова внутри команды, и я замечаю, что selectedMembers всегда null, хотя я выбираю различные предметы. На выходе консоли я не вижу никакой ошибки привязки или чего-то еще.

кроме того, если я передаю как CommandParameter весь список, и я помещаю точку останова в определение команды, я замечаю, что не могу получить доступ к значению SelectedItems или 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>

в чем причина? Как получить такой список?

5 ответов


одно из решений для передачи SelectedItems из ListView в ViewModel (с RelayCommands) описано в блоге igralli.

Pass ListView SelectedItems для ViewModel в универсальных приложениях

попробуйте следующий код, чтобы получить выбранные объекты из параметра.

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

Я сделал небольшой пример для вашего случая без MVVM-Light, и он отлично работает.

возможно, проблема находится в классе RelayCommand. Я написал следующее:--2-->

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; }
    }
}

Я не могу найти причину, но вы можете легко обойти проблему вообще, имея свойство "IsSelected" на Вашем объекте контакта и поставив флажок на свой шаблон:

<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>

и 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();
    }
}

просто мои пять копеек и может быть абсолютно длинный выстрел, но вы должны проверить этой:

Если ItemsSource реализует IItemsRangeInfo, коллекция SelectedItems не обновляется на основе выбора в списке. Вместо этого используйте свойство SelectedRanges.

Я просто предполагаю, основываясь на ответе Tomtom, так как он говорит, что он сделал почти точно так же, как вы, и получил рабочее решение. Возможно, разница в этой маленькой детали. Я сам еще не проверял.


благодаря ответу романа я понял, как решить проблему:

прежде всего, как Роман предложил:

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

затем XAML:

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

разница здесь в том, что я передал весь список как параметр, а не SelectedItems собственность. Затем, используя IValueConverter Я преобразован из ListView объект 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();
    }
}

таким образом RelayCommand<IList<object>> есть правильный список, а не значение null.