在多重绑定期间,多个命令参数值未传递给视图模型
Multiple Command parameter values are not getting passed to view-model during multi-binding
我嵌套了绑定到名为 'CollectionOfAuthors' 的可观察集合的菜单项。
这是菜单项层次结构:
作者 -->AuthorName1-->BookName1,BookName2,BookName3
作者是在作者姓名列表中打开的 TopLevelMenuItem,这样每个作者姓名都会在书籍列表中打开。
当通过 NavigateToBook 命令点击每个 BookName 菜单项时,我想将 BookName、AuthorName 和 AuthorID 作为命令参数发送到 ViewModel,
但我发现空值作为 (DependencyProperty.UnsetValue) 传递给 ViewModel。
想知道需要什么更正吗?
View.xaml
<Menu>
<MenuItem Header="Authors" x:Name="TopLevelMenuItem"
ItemsSource="{Binding CollectionOfAuthors, Mode=TwoWay}">
<in:Interaction.Triggers>
<in:EventTrigger EventName="PreviewMouseLeftButtonDown">
<in:InvokeCommandAction Command="{Binding DataContext.RefreshAuthorsList,RelativeSource={RelativeSource AncestorType=Menu}}"/>
</in:EventTrigger>
</in:Interaction.Triggers>
<MenuItem.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Path=Books}">
<StackPanel>
<TextBlock x:Name="tbAuthor" Text="{Binding AuthorName}"/>
<TextBlock x:Name="tbAuthorID" Text="{Binding AuthorID}" Visibility="Collapsed"/>
</StackPanel>
<HierarchicalDataTemplate.ItemTemplate>
<DataTemplate>
<TextBlock x:Name="tbBookName" Text="{Binding}">
<TextBlock.InputBindings>
<MouseBinding Command="{Binding DataContext.NavigateToBook, RelativeSource={RelativeSource AncestorType=Menu}}" MouseAction="LeftClick" >
<MouseBinding.CommandParameter>
<MultiBinding Converter="{StaticResource MultiCommandConverter}">
<Binding Path="Text" ElementName="tbBookName"/>
<Binding Path="DataContext.AuthorName" RelativeSource="{RelativeSource AncestorLevel=2, AncestorType=MenuItem}" />
<Binding Path="DataContext.AuthorID" RelativeSource="{RelativeSource AncestorLevel=2, AncestorType=MenuItem}" />
</MultiBinding>
</MouseBinding.CommandParameter>
</MouseBinding>
</TextBlock.InputBindings>
</TextBlock>
</DataTemplate>
</HierarchicalDataTemplate.ItemTemplate>
</HierarchicalDataTemplate>
</MenuItem.ItemTemplate>
</MenuItem>
</Menu>
ViewModel.cs
public ICommand NavigateToBook
{
get { return new DelegateCommand(NavigateToBookExecute); }
}
private void NavigateToBookExecute(object obj)
{
string selectedBookName = ((object[])obj)[0].ToString();
string selectedAuthorName = ((object[])obj)[1].ToString();
string selectedAuhorID = ((object[])obj)[2].ToString();
}
public ICommand RefreshAuthorsList
{
get { return new DelegateCommand(RefreshAuthorsListExecute); }
}
private void RefreshAuthorsListExecute(object m)
{
CollectionOfAuthors = new ObservableCollection<Author>();
//Here AuthorDetails is another global collection which gets loaded during constructor call
foreach (var objAuthorItem in AuthorDetails)
{
CollectionOfAuthors.Add(new Author
{
AuthorName = objAuthorItem.DisplayName,
Books = objAuthorItem.ListOfBooks,
AuthorID = objAuthorItem.Id,
});
}
}
private ObservableCollection<Author> _collectionOfAuthors;
public ObservableCollection<Author> CollectionOfAuthors
{
get { return _collectionOfAuthors; }
set { SetProperty(ref _collectionOfAuthors, value); }
}
Author.cs
public class Author
{
public string AuthorName { get; set; }
public string AuthorID { get; set; }
List<string>Books = new List<string>();
}
MultiCommandConverter.cs
public class MultiCommandConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
return values.Clone();
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
由于您在顶级菜单项中有命令,因此该命令甚至会在您的内部命令之前尝试调用,无论什么事件应该触发它。
作为解决方法,您可以将 TopMenuItem 的 IsSubmenuOpen
属性 作为 CommandParameter 传递,并检查菜单是否打开,然后在命令的执行操作中,您可以检查菜单是否打开然后继续或 return。这将阻止您的项目被刷新。
你的命令的CallStack是:
- 点击图书菜单项
- 刷新列表命令运行
- 正在刷新项目,删除旧项目
- 绑定正在尝试从刚刚删除的项目中获取属性
示例解决方案:
View.xaml
<Menu>
<MenuItem Header="Authors" Background="Red" x:Name="TopLevelMenuItem"
ItemsSource="{Binding CollectionOfAuthors, Mode=TwoWay}">
<in:Interaction.Triggers>
<in:EventTrigger EventName="PreviewMouseLeftButtonDown">
<in:InvokeCommandAction Command="{Binding DataContext.RefreshAuthorsList,RelativeSource={RelativeSource AncestorType=Menu}}" CommandParameter="{Binding IsSubmenuOpen , ElementName=TopLevelMenuItem}"/>
</in:EventTrigger>
</in:Interaction.Triggers>
<MenuItem.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Path=Books}">
<StackPanel DataContext="{Binding}">
<TextBlock x:Name="tbAuthor" Text="{Binding AuthorName}"/>
<TextBlock x:Name="tbAuthorID" Text="{Binding AuthorID}" Visibility="Collapsed"/>
</StackPanel>
<HierarchicalDataTemplate.ItemTemplate>
<DataTemplate>
<TextBlock x:Name="tbBookName" DataContext="{Binding}" Text="{Binding}">
<in:Interaction.Triggers>
<in:EventTrigger EventName="MouseDown">
<in:InvokeCommandAction Command="{Binding DataContext.NavigateToBook, RelativeSource={RelativeSource AncestorType=Menu}}" >
<in:InvokeCommandAction.CommandParameter>
<MultiBinding Converter="{StaticResource MultiCommandConverter}">
<Binding Path="Text" ElementName="tbBookName"/>
<Binding Path="DataContext.AuthorName" RelativeSource="{RelativeSource AncestorLevel=1, AncestorType=StackPanel}" />
<Binding Path="DataContext.AuthorID" RelativeSource="{RelativeSource AncestorLevel=1, AncestorType=StackPanel}" />
</MultiBinding>
</in:InvokeCommandAction.CommandParameter>
</in:InvokeCommandAction>
</in:EventTrigger>
</in:Interaction.Triggers>
</TextBlock>
</DataTemplate>
</HierarchicalDataTemplate.ItemTemplate>
</HierarchicalDataTemplate>
</MenuItem.ItemTemplate>
</MenuItem>
</Menu>
然后在你的 RefreshAuthorsListExecute
private void RefreshAuthorsListExecuteExecute(object m)
{
if ((bool)m)
return;
我嵌套了绑定到名为 'CollectionOfAuthors' 的可观察集合的菜单项。
这是菜单项层次结构: 作者 -->AuthorName1-->BookName1,BookName2,BookName3
作者是在作者姓名列表中打开的 TopLevelMenuItem,这样每个作者姓名都会在书籍列表中打开。
当通过 NavigateToBook 命令点击每个 BookName 菜单项时,我想将 BookName、AuthorName 和 AuthorID 作为命令参数发送到 ViewModel, 但我发现空值作为 (DependencyProperty.UnsetValue) 传递给 ViewModel。
想知道需要什么更正吗?
View.xaml
<Menu>
<MenuItem Header="Authors" x:Name="TopLevelMenuItem"
ItemsSource="{Binding CollectionOfAuthors, Mode=TwoWay}">
<in:Interaction.Triggers>
<in:EventTrigger EventName="PreviewMouseLeftButtonDown">
<in:InvokeCommandAction Command="{Binding DataContext.RefreshAuthorsList,RelativeSource={RelativeSource AncestorType=Menu}}"/>
</in:EventTrigger>
</in:Interaction.Triggers>
<MenuItem.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Path=Books}">
<StackPanel>
<TextBlock x:Name="tbAuthor" Text="{Binding AuthorName}"/>
<TextBlock x:Name="tbAuthorID" Text="{Binding AuthorID}" Visibility="Collapsed"/>
</StackPanel>
<HierarchicalDataTemplate.ItemTemplate>
<DataTemplate>
<TextBlock x:Name="tbBookName" Text="{Binding}">
<TextBlock.InputBindings>
<MouseBinding Command="{Binding DataContext.NavigateToBook, RelativeSource={RelativeSource AncestorType=Menu}}" MouseAction="LeftClick" >
<MouseBinding.CommandParameter>
<MultiBinding Converter="{StaticResource MultiCommandConverter}">
<Binding Path="Text" ElementName="tbBookName"/>
<Binding Path="DataContext.AuthorName" RelativeSource="{RelativeSource AncestorLevel=2, AncestorType=MenuItem}" />
<Binding Path="DataContext.AuthorID" RelativeSource="{RelativeSource AncestorLevel=2, AncestorType=MenuItem}" />
</MultiBinding>
</MouseBinding.CommandParameter>
</MouseBinding>
</TextBlock.InputBindings>
</TextBlock>
</DataTemplate>
</HierarchicalDataTemplate.ItemTemplate>
</HierarchicalDataTemplate>
</MenuItem.ItemTemplate>
</MenuItem>
</Menu>
ViewModel.cs
public ICommand NavigateToBook
{
get { return new DelegateCommand(NavigateToBookExecute); }
}
private void NavigateToBookExecute(object obj)
{
string selectedBookName = ((object[])obj)[0].ToString();
string selectedAuthorName = ((object[])obj)[1].ToString();
string selectedAuhorID = ((object[])obj)[2].ToString();
}
public ICommand RefreshAuthorsList
{
get { return new DelegateCommand(RefreshAuthorsListExecute); }
}
private void RefreshAuthorsListExecute(object m)
{
CollectionOfAuthors = new ObservableCollection<Author>();
//Here AuthorDetails is another global collection which gets loaded during constructor call
foreach (var objAuthorItem in AuthorDetails)
{
CollectionOfAuthors.Add(new Author
{
AuthorName = objAuthorItem.DisplayName,
Books = objAuthorItem.ListOfBooks,
AuthorID = objAuthorItem.Id,
});
}
}
private ObservableCollection<Author> _collectionOfAuthors;
public ObservableCollection<Author> CollectionOfAuthors
{
get { return _collectionOfAuthors; }
set { SetProperty(ref _collectionOfAuthors, value); }
}
Author.cs
public class Author
{
public string AuthorName { get; set; }
public string AuthorID { get; set; }
List<string>Books = new List<string>();
}
MultiCommandConverter.cs
public class MultiCommandConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
return values.Clone();
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
由于您在顶级菜单项中有命令,因此该命令甚至会在您的内部命令之前尝试调用,无论什么事件应该触发它。
作为解决方法,您可以将 TopMenuItem 的 IsSubmenuOpen
属性 作为 CommandParameter 传递,并检查菜单是否打开,然后在命令的执行操作中,您可以检查菜单是否打开然后继续或 return。这将阻止您的项目被刷新。
你的命令的CallStack是:
- 点击图书菜单项
- 刷新列表命令运行
- 正在刷新项目,删除旧项目
- 绑定正在尝试从刚刚删除的项目中获取属性
示例解决方案:
View.xaml
<Menu>
<MenuItem Header="Authors" Background="Red" x:Name="TopLevelMenuItem"
ItemsSource="{Binding CollectionOfAuthors, Mode=TwoWay}">
<in:Interaction.Triggers>
<in:EventTrigger EventName="PreviewMouseLeftButtonDown">
<in:InvokeCommandAction Command="{Binding DataContext.RefreshAuthorsList,RelativeSource={RelativeSource AncestorType=Menu}}" CommandParameter="{Binding IsSubmenuOpen , ElementName=TopLevelMenuItem}"/>
</in:EventTrigger>
</in:Interaction.Triggers>
<MenuItem.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Path=Books}">
<StackPanel DataContext="{Binding}">
<TextBlock x:Name="tbAuthor" Text="{Binding AuthorName}"/>
<TextBlock x:Name="tbAuthorID" Text="{Binding AuthorID}" Visibility="Collapsed"/>
</StackPanel>
<HierarchicalDataTemplate.ItemTemplate>
<DataTemplate>
<TextBlock x:Name="tbBookName" DataContext="{Binding}" Text="{Binding}">
<in:Interaction.Triggers>
<in:EventTrigger EventName="MouseDown">
<in:InvokeCommandAction Command="{Binding DataContext.NavigateToBook, RelativeSource={RelativeSource AncestorType=Menu}}" >
<in:InvokeCommandAction.CommandParameter>
<MultiBinding Converter="{StaticResource MultiCommandConverter}">
<Binding Path="Text" ElementName="tbBookName"/>
<Binding Path="DataContext.AuthorName" RelativeSource="{RelativeSource AncestorLevel=1, AncestorType=StackPanel}" />
<Binding Path="DataContext.AuthorID" RelativeSource="{RelativeSource AncestorLevel=1, AncestorType=StackPanel}" />
</MultiBinding>
</in:InvokeCommandAction.CommandParameter>
</in:InvokeCommandAction>
</in:EventTrigger>
</in:Interaction.Triggers>
</TextBlock>
</DataTemplate>
</HierarchicalDataTemplate.ItemTemplate>
</HierarchicalDataTemplate>
</MenuItem.ItemTemplate>
</MenuItem>
</Menu>
然后在你的 RefreshAuthorsListExecute
private void RefreshAuthorsListExecuteExecute(object m)
{
if ((bool)m)
return;