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
对象转换为 SelectedMember
的 IList<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>>
得到了正确的列表而不是空值。
我正在使用 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
对象转换为 SelectedMember
的 IList<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>>
得到了正确的列表而不是空值。