列表视图选定项绑定:为什么列表始终为 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>
原因是什么?我怎样才能获得这样的列表?
在 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的回答进行猜测,因为他说他几乎和你完全一样,并得到了一个可行的解决方案。也许区别就在这个小细节上。我自己还没有检查过。