列表视图选定项绑定:为什么列表始终为 null

本文关键字:列表 null 为什么 绑定 视图 | 更新日期: 2023-09-27 18:30:40

我正在开发一个UWP应用程序,使用Mvvm Light和Behaviors SDK。我定义了一个多选列表视图:

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

我想,使用绑定到 MVVM-Light RelayCommand 的按钮,获取包含所选项目的列表:

<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,尽管我选择了各种项目。通过控制台输出,我没有看到任何绑定错误或其他内容。

此外,如果我将整个列表作为命令参数传递,并在命令的定义中放置一个断点,我注意到我无法访问 SelectedItems 或 SelecteRange 值。

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

原因是什么?我怎样才能获得这样的列表?

列表视图选定项绑定:为什么列表始终为 null

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

将列表视图选定项传递到通用应用中的视图模型

请尝试以下代码从参数中获取所选对象。

    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-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; }
    }
}
多亏了

罗曼的回答,我想出了解决这个问题的方法:

首先,正如罗曼所建议的:

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属性。然后,使用我从ListView对象转换为SelectedMember IList<object>IValueConverter

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

我找不到原因,但您可以通过在联系人对象上放置"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();
    }
}

只是我的五美分,可能是一个绝对漫长的机会,但你应该检查一下:

如果 ItemsSource 实现 IItemsRangeInfo,则 SelectedItems 集合不会根据列表中的选择进行更新。请改用"选定范围"属性。

我只是根据Tomtom的回答进行猜测,因为他说他几乎和你完全一样,并得到了一个可行的解决方案。也许区别就在这个小细节上。我自己还没有检查过。