WPF 命令不适用于 MVVM 应用程序中的子菜单项

WPF command not working for submenu items in MVVM application

我有一个菜单,它是在运行时从集合构建的。这一切都如图所示。

但是,如果菜单包含子项(Child1、Child2 等),则 ReactiveCommand MenuCommand 永远不会被调用。

如果我从菜单中删除所有子项,以便菜单仅包含父项,那么将调用 MenuCommand 。我是 WPF 的新手。我在示例应用程序(下面的代码)中重新创建了问题。 VS 中没有可见的绑定错误。

    public partial class MainWindow : Window
                {
                    public MainWindow()
                    {
                        InitializeComponent();
                        DataContext = new MainWindowViewModel();
                    }
                }


            public class Service
            {
                public Service(string menuHeading, string menuSubHeading)
                {
                    MenuHeading = menuHeading;
                    MenuSubHeading = menuSubHeading;
                }

                public string MenuHeading { get; set; }
                public string MenuSubHeading { get; set; }
            }


            public static class MenuBuilder
            {
                public static ReactiveList<MenuItem> Build(ReactiveList<Service> services)
                {
                    ReactiveList<MenuItem> menuItems = new ReactiveList<MenuItem>();

                    foreach (var service in services)
                    {
                        AddOrUpdate(menuItems, service);
                    }

                    return menuItems;
                }

                private static void AddOrUpdate(ReactiveList<MenuItem> menu, Service service)
                {
                    if (menu.Any((_ => _.Header.ToString() == service.MenuHeading)))
                    {
                        var item = menu.FirstOrDefault(x => x.Header.ToString() == service.MenuHeading);
                        item.Items.Add(new MenuItem() { Header = service.MenuSubHeading }); 
                        //if above line removed MenuCommand works
                    }
                    else
                    {
                        menu.Add(new MenuItem() { Header = service.MenuHeading });
                        var item = menu.FirstOrDefault(x => x.Header.ToString() == service.MenuHeading);
                        item.Items.Add(new MenuItem() { Header = service.MenuSubHeading }); 
                        //if above line removed MenuCommand works
                    }
                }
            }


            public class MainWindowViewModel : ReactiveObject
                    {
                        public MainWindowViewModel()
                        {
                            MenuCommand = ReactiveCommand.Create<Object>(selectedItem => OnMenuItemSelected(selectedItem));
                            MenuCommand.Execute().Subscribe();
                        }

                        public ReactiveCommand<Object, Unit> MenuCommand { get; }

                        private ReactiveList<MenuItem> servicesMenu;

                         private ReactiveList<Service> Services = new ReactiveList<Service>()
                        {
                          new Service("Parent1", "Child1"),
                          new Service("Parent2", "Child1"),
                          new Service("Parent2", "Child2"),
                        };


                        public ReactiveList<MenuItem> ServicesMenu
                        {
                            get
                            {
                                if (servicesMenu == null)
                                {
                                    servicesMenu = MenuBuilder.Build(Services);
                                    return servicesMenu;
                                }
                                else
                                {
                                    return servicesMenu;
                                }
                            }
                        }

                        private void OnMenuItemSelected(Object selectedItem)
                        {
                             //This method is only called when the menu does not contain any child items
                        } 
                    }


<Grid>
        <StackPanel Orientation="Vertical">
            <Button Name="Button" Content="Button" Padding="5" HorizontalAlignment="Left" 
                    Tag="{Binding RelativeSource={RelativeSource Self}, Path=DataContext}">

                <Button.ContextMenu>
                    <ContextMenu x:Name="MainMenu" ItemsSource="{Binding ServicesMenu}"
                            DataContext="{Binding Path=PlacementTarget.Tag, RelativeSource={RelativeSource Self}}">
                        <ContextMenu.ItemContainerStyle>
                            <Style TargetType="MenuItem">
                                <Setter Property="Command"
                                        Value="{Binding DataContext.MenuCommand, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type Button}}}" />
                                <Setter Property="CommandParameter"
                                        Value="{Binding RelativeSource={RelativeSource Self}}" />
                            </Style>
                        </ContextMenu.ItemContainerStyle>
                    </ContextMenu>
                </Button.ContextMenu>
            </Button>
        </StackPanel>
    </Grid>

根据 Glenn

的建议更新 XAML
 <Grid>
    <StackPanel Orientation="Vertical">
        <Button Name="Button" Content="Button" Padding="5" HorizontalAlignment="Left" 
                Tag="{Binding RelativeSource={RelativeSource Self}, Path=DataContext}">

            <Button.ContextMenu>
                <ContextMenu x:Name="MainMenu" ItemsSource="{Binding ServicesMenu}"
                        DataContext="{Binding Path=PlacementTarget.Tag, RelativeSource={RelativeSource Self}}">

                    <ContextMenu.ItemContainerStyle>
                        <Style TargetType="MenuItem">
                            <Setter Property="Header" Value="{Binding Header}" />
                            <Setter Property="Command" Value="{Binding Command}" />
                            <!--<Setter Property="Command" Value="{Binding MenuCommand}" /> was also tried-->

                            <Setter Property="CommandParameter" Value="{Binding}" />

                        </Style>
                    </ContextMenu.ItemContainerStyle>
                </ContextMenu>
            </Button.ContextMenu>
        </Button>
    </StackPanel>
</Grid>

我怀疑这是因为子项放置目标不会像您期望的那样是 Button,而是父 MenuItem。

我过去解决这个问题的一种方法是对这些类型的菜单项使用 MVVM 方法。

为您的项目创建一个菜单项 VM(您在上面称它们为服务)(类似于您已经在做的)。在 VM 中有一个命令 属性 并将您的命令作为其构造函数的一部分传递。然后你可以从你的项目容器样式中执行 {Binding MenuCommand} 。

也不要直接在您的 ViewModel 中创建 MenuItem,而是直接绑定到服务。我还建议直接在您的服务中将您的子服务创建为 ObservableCollection,然后在您的项目容器中设置 ItemsSource 属性 以绑定到您的服务的子子项。