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

标签 c# wpf xaml mvvm reactiveui

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

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

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

    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 中有一个 Command 属性,并将您的命令作为其构造函数的一部分传入。然后您可以从您的项目容器样式中执行 {Binding MenuCommand}。

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

关于c# - WPF 命令不适用于 MVVM 应用程序中的子菜单项,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/48114412/

相关文章:

c# - Windows Phone 8.1 XAML 应用程序。如何防止不恰当的自动调焦?

c# - 使用 asp 服务器控件 DataList 时,如何将 Eval 数据绑定(bind)表达式绑定(bind)到 Label?

c# - 有没有办法从字符串创建 SyndicateFeed?

.net - WPF 中的圆括号在从右到左的流向中未正确显示

wpf - 展开/折叠所有扩展器

c# - 如何在 XAML WPF 中访问自定义控件的属性

c# - 是否有使用属性将注入(inject)参数绑定(bind)到成员字段的包?

c# - 在 C# 中等效于 Java 的 "ByteBuffer.putType()"

c# - CheckBox 双向绑定(bind)不起作用

c# - 部署时 Log4Net 不记录