Multiple Command parameter values are not getting

2019-08-20 11:43发布

问题:

I've nested menuitems bounded to observable collection named 'CollectionOfAuthors'.

Here's the MenuItem Hierarchy: Author -->AuthorName1-->BookName1,BookName2,BookName3

Author is TopLevelMenuItem which opens in list of Author names such that each Author name opens into list of Books.

While Clicking on each BookName menuitem through NavigateToBook command, I want to send the BookName, AuthorName and AuthorID to ViewModel as command parameters, But I am finding empty values as (DependencyProperty.UnsetValue) passed to ViewModel.

Need to know what correction is required?

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();
        }
    }

回答1:

Since you have Command at top level menu item, this Command will try to call even before your inner Command, no mather what event should trigger it. As Workaround you could pass IsSubmenuOpen property of TopMenuItem as CommandParameter, and check if Menu is opened, and then in Command's execute action you could check if menu is Opened then continue or return. This will stop your items from being refreshed.

CallStack of your command is:

  • Click on book menuItem
  • RefreshListCommand runs
  • items are being refreshed, old ones are removed
  • Binding is trying to get properites from just removed items

Sample solution:

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>

And then in your RefreshAuthorsListExecute

private void RefreshAuthorsListExecuteExecute(object m)
{
    if ((bool)m)
        return;