我正在使用Mvvm Light和Behaviors SDK开发UWP应用程序。我定义了一个多选ListView:
<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
,尽管我选择了各种项目。通过控制台输出我没有看到任何绑定错误或其他。
另外,如果我作为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>
是什么原因?我怎样才能获得这样的清单?
答案 0 :(得分:6)
在igralli的博客中描述了从ViewModel中的ListView传递SelectedItems(使用RelayCommands)的解决方案之一。
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();
}));
}
}
答案 1 :(得分:1)
我为没有MVVM-Light的情况做了一个小例子,它完美无缺。
问题可能在RelayCommand类中。我写了以下内容:
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; }
}
}
答案 2 :(得分:1)
感谢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
对象转换为IList<object>
的{{1}}:
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();
}
}
获得了正确的列表,而不是空值。
答案 3 :(得分:0)
我找不到理由,但您可以通过在Contact对象上添加“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>
和VM等:
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();
}
}
答案 4 :(得分:0)
只需我的五美分,可能绝对是一个很长的镜头,但你应该检查this:
如果ItemsSource实现IItemsRangeInfo,则不会根据列表中的选择更新SelectedItems集合。请改用SelectedRanges属性。
我只是根据Tomtom的答案猜测,因为他说他几乎和你一样,并且得到了一个有效的解决方案。也许差异在于这个小细节。我自己还没有检查过。