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

时间:2018-01-05 13:16:20

标签: c# wpf xaml mvvm reactiveui

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

Working Menu 但是,如果菜单包含子项(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>

1 个答案:

答案 0 :(得分:1)

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

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

为您的项目创建一个菜单项VM(您在上面称它们为Service)(类似于您已经在做的事情)。在VM中有一个Command属性,并将命令作为其构造函数的一部分传递。然后你可以从你的Item Container Style中做{Binding MenuCommand}。

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