代码之家  ›  专栏  ›  技术社区  ›  Matt Jordan

如何将菜单项(带标题)动态添加到WPF菜单

  •  8
  • Matt Jordan  · 技术社区  · 14 年前

    [编辑3] -对于任何阅读这个问题的人:做 在任何情况下,使用本问题中概述的方法。 这是一个编码恐怖 . 我自由地承认这一点,因为我知道过去所有的程序员都在一个角落里工作,而且(尤其是在学习新技术的时候),我们都被互联网上其他善意的开发人员引入了歧途。先读罗伯特的答案,然后读这个问题。拜托。

    [编辑2b]

    我为这个问题的长度道歉-这里有个问题(结尾!)但我想确保源代码是明确的。不管怎样。

    [编辑2] -问题标题更改为更准确地反映…问题。

    [编辑] -我已经更新了更多的历史,关于我是如何最终得到我在这里所做的设计/代码的: Obligatory Blog Post . 如果它有助于澄清下面的问题,请随意阅读…

    原始问题

    我正在开发的应用程序使用prism和wpf,其中包含许多模块(当前为3个),其中一个模块承载应用程序菜单。最初,菜单是静态的,带有到compositecommand/delegatecommands的钩子,这对于将按钮按到适当的演示者非常有用。每个菜单项在其标题中使用stackpanel将内容显示为图像和文本标签的组合-这是我要查找的外观:

    <Menu Height="48" Margin="5,0,5,0" Name="MainMenu" VerticalAlignment="Top" Background="Transparent">
                    <MenuItem Name="MenuFile" AutomationProperties.AutomationId="File">
                        <MenuItem.Header>
                            <StackPanel>
                                <Image Height="24" VerticalAlignment="Center" Source="../Resources/066.png"/>
                                <ContentPresenter Content="Main"/>
                            </StackPanel>
                        </MenuItem.Header>
                        <MenuItem AutomationProperties.AutomationId="FileExit" Command="{x:Static local:ToolBarCommands.FileExit}">
                            <MenuItem.Header>
                                <StackPanel>
                                    <Image Height="24" VerticalAlignment="Center" Source="../Resources/002.png"/>
                                    <ContentPresenter Content="Exit"/>
                                </StackPanel>
                            </MenuItem.Header>
                        </MenuItem>
                    </MenuItem>
                    <MenuItem  Name="MenuHelp" AutomationProperties.AutomationId="Help" Command="{x:Static local:ToolBarCommands.Help}">
                        <MenuItem.Header>
                            <StackPanel>
                                <Image Height="24" VerticalAlignment="Center" Source="../Resources/152.png"/>
                                <ContentPresenter Content="Help"/>
                            </StackPanel>
                        </MenuItem.Header>
                    </MenuItem>               
                </Menu>
    

    不幸的是,应用程序变得更复杂了,它希望其他模块在菜单中注册它们自己-因此,我一直在寻找使菜单动态化的方法。目标是让其他模块(通过服务)能够随意地向菜单添加命令-例如,模块A将在工具栏模块中添加一个菜单项,该菜单项在模块A中调用一个处理程序。有一些关于这个主题的优秀文章-我看过的两篇是 Building a Databound WPF Menu Using a HierarchicalDataTemplate WPF Sample Series - Databound HierarchicalDataTemplate Menu Sample . 按照本文中的建议,我成功地创建了一个动态构建的菜单,没有明显的数据绑定问题——它可以创建一个菜单,其中项目链接到我的展示模型,反映了展示模型中可观察到的集合的结构。

    当前,我的XAML如下所示:

    <UserControl x:Class="Modules.ToolBar.Views.ToolBarView"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:model="clr-namespace:Modules.ToolBar.PresentationModels"
        xmlns:local="clr-namespace:Modules.ToolBar">
        <UserControl.Resources>
            <model:ToolBarPresentationModel x:Key="modelData" />
            <HierarchicalDataTemplate DataType="{x:Type model:ToolbarObject}"
                                      ItemsSource="{Binding Path=Children}">
                <ContentPresenter Content="{Binding Path=Name}"
                                  Loaded="ContentPresenter_Loaded"
                                  RecognizesAccessKey="True"/>
            </HierarchicalDataTemplate>
        </UserControl.Resources>
        <UserControl.DataContext>
            <Binding Source="{StaticResource modelData}"/>
        </UserControl.DataContext>
            <Menu Height="48" Margin="5,0,5,0" Name="MainMenu" VerticalAlignment="Top" Background="Transparent"
                ItemsSource="{Binding}">
            </Menu>
        </Grid>
    </UserControl>
    

    视图背后的代码会在ContentPresenter加载方法中起重作用:

       private void ContentPresenter_Loaded(object sender, System.Windows.RoutedEventArgs e)
        {
        ContentPresenter presenter = sender as ContentPresenter;
        if (sender != null)
        {
            DependencyObject parentObject = VisualTreeHelper.GetParent(presenter);
            bool bContinue = true;
            while (bContinue
                || parentObject == null)
            {
                if (parentObject is MenuItem)
                    bContinue = false;
                else
                    parentObject = VisualTreeHelper.GetParent(parentObject);
            }
            var menuItem = parentObject as MenuItem;
            if (menuItem != null)
            {
                ToolbarObject toolbarObject = menuItem.DataContext as ToolbarObject;
                StackPanel panel = new StackPanel();
                if (!String.IsNullOrEmpty(toolbarObject.ImageLocation))
                {
                    Image image = new Image();
                    image.Height = 24;
                    image.VerticalAlignment = System.Windows.VerticalAlignment.Center;
                    Binding sourceBinding = new Binding("ImageLocation");
                    sourceBinding.Mode = BindingMode.TwoWay;
                    sourceBinding.Source = toolbarObject;
                    image.SetBinding(Image.SourceProperty, sourceBinding);
                    panel.Children.Add(image);
                }
                ContentPresenter contentPresenter = new ContentPresenter();
                Binding contentBinding = new Binding("Name");
                contentBinding.Mode = BindingMode.TwoWay;
                contentBinding.Source = toolbarObject;
                contentPresenter.SetBinding(ContentPresenter.ContentProperty, contentBinding);
                panel.Children.Add(contentPresenter);
                menuItem.Header = panel;
                Binding commandBinding = new Binding("Command");
                commandBinding.Mode = BindingMode.TwoWay;
                commandBinding.Source = toolbarObject;
                menuItem.SetBinding(MenuItem.CommandProperty, commandBinding);                    
            }
        }
    }
    

    如您所见,我正试图重新创建原始菜单的stackpanel/image/name组合,只需在后面的代码中这样做。尝试这样做的效果不太好-虽然菜单对象确实正在创建,但它们不会“显示”为除空白、可单击对象之外的其他任何对象-不会呈现StackPanel、图像、名称等。有趣的是,它还导致层次结构数据模板中存在的内容中的原始文本被擦除。

    那么,问题是,有没有一种方法可以在加载事件中设置menuitem的header属性,使其正确显示在用户控件上?是否显示标题中的项表示数据绑定问题?如果是这样,将头绑定到临时对象(在加载事件处理程序中创建的stackpanel)的正确方法是什么?

    我愿意更改上面代码中的任何内容——这是所有类型的原型设计,试图找出处理动态菜单创建的最佳方法。 谢谢!

    2 回复  |  直到 14 年前
        1
  •  9
  •   Community leo1    12 年前

    我承认,我没有像应该的那样深入地研究您的示例,但是每当我看到后面的代码在搜索可视化树时,我想,是否可以在视图模型中更明确地处理这个问题?

    在我看来,在这种情况下,您可以想出一个非常简单的视图模型-一个对象公开 Text , Image , Command Children 例如,属性-然后创建一个简单的数据模板,用于将其显示为 MenuItem . 然后,任何需要更改菜单内容的操作都将操纵此模型。

    编辑:

    在详细了解了你要做的事情,以及你在博客中链接到的两个例子之后,我的头撞在了桌子上。这两个开发人员似乎都误解了在模板生成的菜单项上设置属性的方法是通过 ContentPresenter.Load 创建后的事件。不是这样。这就是 ItemContainerStyle 是的。

    如果您使用它,那么创建所描述类型的动态菜单非常简单。你需要一个 MenuItemViewModel 具有的类 INotifyPropertyChanged 实现并公开这些公共属性:

    string Text
    Uri ImageSource
    ICommand Command
    ObservableCollection<MenuItemViewModel> Children
    

    使用此:

    <Menu DockPanel.Dock="Top" ItemsSource="{DynamicResource Menu}"/>
    

    在哪里 ItemsSource 是一个 ObservableCollection<MenuItemViewModel> ,并使用此模板:

    <HierarchicalDataTemplate DataType="{x:Type local:MenuItemViewModel}"
                              ItemsSource="{Binding Path=Children}">
        <HierarchicalDataTemplate.ItemContainerStyle>
            <Style TargetType="MenuItem">
                <Setter Property="Command"
                        Value="{Binding Command}" />
            </Style>
        </HierarchicalDataTemplate.ItemContainerStyle>
        <StackPanel Orientation="Horizontal">
            <Image Source="{Binding ImageSource}" />
            <Label Content="{Binding Text}" />
        </StackPanel>
    </HierarchicalDataTemplate>
    

    窗口中的菜单完全代表集合中的内容,并在添加和删除项时动态更新,包括添加到顶级项和子项。

    在可视化的树中不需要爬来爬去,不需要手动创建对象,也不需要隐藏代码(除了视图模型中的代码,以及最初填充集合的任何代码)。

    我已经建立了一个非常彻底的例子;你可以下载这个项目 here .

        2
  •  0
  •   Damian Schenkelman    14 年前

    另一种可能的方法是让菜单成为一个区域并同意一个约定,因此添加到该区域的所有视图都具有一个名为menuheader的属性的ViewModel。这样,区域适配器就可以简单地从视图的数据上下文中获取菜单头,并在添加时将其设置为项。

    类似的事情在棱镜中完成,视图添加到选项卡区域。你可以读更多 here .

    我希望这能提供一些有用的指导。

    谢谢, 达米安