如何动态添加MenuItems(带标题)到WPF菜单(How to dynamically add MenuItems (with a header) to a WPF menu)
[编辑#3] - 给阅读此问题的任何人:在任何情况下都不要使用此问题中概述的方法。 这是一个编码恐怖 。 我毫不犹豫地承认这一点,知道所有程序员都在过去的一个角落工作,并且(尤其是在学习新技术的时候),我们都被互联网上的其他好的开发人员误导了。 先阅读罗伯特的答案,然后阅读这个问题。 请。
[编辑#2b]
我对这个问题的长度表示歉意 - 这里有一个问题(最后!),但我想确保源代码是明确的。 无论如何。
[编辑#2] - 问题标题更改为更准确地反映...问题。
[编辑] - 我已经更新了一些关于我如何结束在这里所做的设计/代码的历史: Obligatory Blog Post 。 如果它有助于澄清下面的问题,请随时阅读它...
原始问题
我正在使用的应用程序使用Prism和WPF,并带有许多模块(当前为3个),其中一个模块托管应用程序菜单。 最初,该菜单是静态的,可以挂接到CompositeCommand / DelegateCommands中,这对于将按钮路由到相应的演示者非常有用。 每个MenuItem在其标题中使用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中的处理程序。有几篇优秀的文章在这个主题上 - 我看过的两个是使用HierarchicalDataTemplate和WPF示例系列 - Databound HierarchicalDataTemplate菜单示例 构建Databound WPF菜单 。 按照文章中的建议,我设法制作了一个动态构建的菜单,没有明显的数据绑定问题 - 它可以创建一个菜单项,并链接到我的演示文稿模型,反映了演示模型中ObservableCollection的结构
目前,我的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_Loaded方法中负担过重:
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,Image,Name等不会被呈现。 有趣的是,它也导致HierarchicalDataTemplate中的ContentPresent中的原始文本被删除。
那么问题是,有没有办法在Load事件中设置MenuItem的Header属性,以便它能正确显示在UserControl上? 事实上,标题中的项目没有被显示,指示DataBinding问题? 如果是这样,将Header绑定到一个临时对象(在load事件处理程序中创建的StackPanel)的正确方法是什么?
我愿意改变上述代码中的任何东西 - 这是各种原型设计,试图找出处理动态菜单创建的最佳方式。 谢谢!
[Edit #3] - to anyone reading this question: do not under any circumstance use the approach outlined in this question. It is a Coding Horror. I freely admit this, knowing that all programmers have worked themselves into a corner in the past, and (especially when learning a new technology) we all have been led astray by other, well-meaning developers on the interweb. Read the answer by Robert first, then read this question. Please.
[Edit #2b]
I apologize for the length of this question - there is a question in here (at the end!), but I wanted to make sure the source code was explicit. Anyway.
[Edit #2] - question title changed to more accurately reflect the... question.
[Edit] - I've updated some more of the history as to how I ended up at the design / code that I did here: Obligatory Blog Post. If it helps clarify the question below, feel free to read it...
Original question
The application I'm working on uses Prism and WPF, with a number of modules (currently 3), one of which hosts the application menu. Originally, the menu was static with hooks into CompositeCommand / DelegateCommands, which worked great for routing button presses to the appropriate presenter. Each MenuItem used a StackPanel in its header to display the content as a combination of an image and a text label - which was the look I was going for:
<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>
Unfortunately, the application has gotten a bit more complex and it is desireable to have other modules register themselves with the menu - hence, I've been looking at making the menu dynamic. The goal is to have other modules (through a service) be able to add commands to the menu at will - for example, Module A will add a menu item in the Toolbar module that calls a handler in Module A. There's a few excellent articles out there on this subject - the two I've looked at are Building a Databound WPF Menu Using a HierarchicalDataTemplate and WPF Sample Series - Databound HierarchicalDataTemplate Menu Sample. Following the advice in the article, I have managed to make a dynamically constructed menu with no obvious data binding problems - it can create a menu with items linked backed to my presentation model, reflecting the structure of an ObservableCollection in the presentation model
Currently, my XAML looks like the following:
<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>
The code behind for the view does the heavy lifting in the ContentPresenter_Loaded method:
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); } } }
As you can see, I'm attempting to recreate the StackPanel / Image / Name combination of the original menu, just doing so in the code behind. Attempting to do this has not worked out so well - while the menu objects are certainly being created, they don't "appear" as anything other than blank, clickable objects - the StackPanel, Image, Name, etc. aren't being rendered. Interestingly enough, it also is causing the original text in the ContentPresent in the HierarchicalDataTemplate to be erased.
The question then, is there a way to set a MenuItem's Header property in the Load event such that it will display on the UserControl properly? Is the fact that the items in the header are not being displayed indicative of a DataBinding problem? If so, what would be the proper way to bind the Header to a transient object (the StackPanel that was created in the load event handler)?
I'm open to changing anything in the code above - this is all sort of prototyping along, trying to figure out the best way to handle dynamic menu creation. Thanks!
原文:https://stackoverflow.com/questions/3723580
最满意答案
相关问答
更多-
您需要包含定义unique_ptr和shared_ptr标头 #include
正如你已经知道你需要用c++11标志进行编译 g++ main.cpp -o run -std=c++11 // ^ You need to include header where unique_ptr and shared_ptr are defined #include As you already knew that you need to co ... -
GCC libstdc++在您编译的任何版本中都不支持这些C ++ 11操纵符。 一个月前提交了一个补丁 GCC libstdc++ just doesn't support these C++11 manipulators in any of the versions you've compiled against. A patch was submitted exactly one month ago
-
您需要包含
标题。 You need to include the header. -
MinGW不支持std :: defaultfloat,如何引导它呢?(MinGW do not support std::defaultfloat, how to lead with it?)[2022-08-23]
为什么不写: stream.unsetf(std::ios_base::floatfield); stream << value; 这将是有效的。 (它不是很漂亮,但它比#ifdef更漂亮。) 或者你可以编写自己的操纵器来做同样的操作。 Why not just write: stream.unsetf(std::ios_base::floatfield); stream << value; That will just work. (It's not as pretty ... -
cout不是std的成员(cout is not a member of std)[2022-05-04]
将#include添加到io.cpp的开头。 add #include to the start of io.cpp too. -
你只需要包含头
#include 如果编译器不支持std::array那么在这种情况下,它将发出错误,指出找不到此标头。 You just need to include header #include If the compiler does not support std::array then in this case it will issue an error that this header is not found. -
关于最终的代码示例, 你不能在一个函数体外面使用这个语句: std::newvector.push_back(5); 将它放在main的函数体中, 并删除std::前缀。 还要注意的是,对于可能的矢量修改,矢量不能是const 。 用圆括号初始化为该类的构造函数提供参数。 必须有一个相应的构造函数。 并且没有对应于四个参数的std::vector构造函数 vector
myVector (5,4,3,4); 相反 vector myVector = {5,4,3,4}; 如果 ... -
看起来你的计算机上的Clang版本(伪装成g++ ,Mac上常见的设置)默认仍然使用libstdc ++(GCC的C ++标准库)。 OS X附带的libstdc++版本很古老,并且没有C ++ 11支持( std::stod是C ++ 11的补充)。 将-stdlib=libc++传递给编译器,使其使用Clang的标准库。 Looks like the version of Clang (masquerading as g++, a common setup on Mac) on your compute ...
-
android NDK抱怨std :: nearbyint不是std的成员(android NDK complains std::nearbyint is not a member of std)[2023-10-24]
这是https://github.com/android-ndk/ndk/issues/82的一部分。 GNU的libstdc ++的功能保护过于宽泛。 我们正致力于稳定NDK的libc ++解决方案,因此我们可以完全转向它。 This is part of https://github.com/android-ndk/ndk/issues/82. GNU's libstdc++ is overly broad with their feature guards. We're working on stab ... -
从概念上讲, std::find只需要两个InputIterator而不是std::vector 。 因此,一个实现适用于所有容器,包括STL容器和标准数组 ,以及任何可以提供InputIterator ,包括例如istream_iterator() - 很好! 因此,不是为每个容器提供一个find()方法(并且考虑到对于某些不可能的方法,如标准数组),为所有容器提供了一个单一的通用find()函数。 这可能会使您的代码更易于更改,而不是为每个容器添加find()方法,因为它提供了一个在任何集合中进行搜索的 ...