ListView SelectedItems 绑定:为什么列表始终为空

ListView SelectedItems binding: why the list is always null

我正在使用 Mvvm Light 和 Behaviors SDK 开发 UWP 应用程序。我定义了一个多 selectable ListView:

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

</ListView>

我想用一个绑定到 MVVM-Light RelayCommand 的按钮来获取包含 selected 项的列表:

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

RelayCommand(MVVM-Light 框架):

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

我在命令中放置了一个断点,我注意到 selectedMembers 始终是 null,尽管我 select 各种项目。通过控制台输出,我没有看到任何绑定错误或其他错误。

此外,如果我将整个列表作为 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>

这是什么原因?我怎样才能得到这样的名单?

我找不到原因,但您可以通过在您的联系人对象上添加 "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>

和虚拟机等:

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

我已经为你的案例制作了一个没有 MVVM-Light 的小例子,它完美无缺。

可能问题出在 RelayCommand-class 内。我写了以下内容:

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

只是我的五美分,可能绝对不可能,但你应该检查 this:

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

我只是根据 Tomtom 的回答进行猜测,因为他说他所做的几乎与您完全一样,并且得到了一个可行的解决方案。也许不同之处在于这个小细节。我自己还没查过

在 igralli 的博客中描述了在 ViewModel(使用 RelayCommands)中从 ListView 传递 SelectedItems 的解决方案之一。

Pass ListView SelectedItems to ViewModel in Universal apps

尝试使用以下代码从参数中获取选定的对象。

    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 的回答,我找到了解决问题的方法:

首先,正如 Roman 所建议的:

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 对象转换为 SelectedMemberIList<object>:

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>> 得到了正确的列表而不是空值。