[编辑#3] - 阅读此问题的任何人:在任何情况下都不要使用此问题中概述的方法. 这是一个编码恐怖片.我很自然地承认这一点,因为我知道所有的程序员都曾在过去的一个角落里工作过(尤其是在学习新技术的时候),我们都被其他善意的开发人员误入歧途.首先阅读罗伯特的答案,然后阅读这个问题.请.
[编辑#2b]
我为这个问题的长度道歉 - 这里有一个问题(最后!),但我想确保源代码是明确的.无论如何.
[编辑#2] - 问题标题更改为更准确地反映...问题.
[编辑] - 我已经更新了一些关于我如何最终得到我在这里做的设计/代码的历史:强制性博客文章.如果它有助于澄清下面的问题,请随时阅读它...
原始问题
我正在使用的应用程序使用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>
Run Code Online (Sandbox Code Playgroud)
不幸的是,应用程序变得有点复杂,并且希望其他模块在菜单中注册自己 - 因此,我一直在考虑使菜单动态化.目标是让其他模块(通过服务)能够随意向菜单添加命令 - 例如,模块A将在工具栏模块中添加一个菜单项,该模块调用模块A中的处理程序.有一些优秀的文章关于这个主题 - 我看过的两个是使用HierarchicalDataTemplate和WPF示例系列构建数据绑定WPF菜单- 数据绑定HierarchicalDataTemplate菜单示例.按照本文中的建议,我设法创建一个动态构造的菜单,没有明显的数据绑定问题 - 它可以创建一个菜单,其中的项目链接到我的表示模型,反映了演示模型中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>
Run Code Online (Sandbox Code Playgroud)
视图背后的代码完成了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);
}
}
}
Run Code Online (Sandbox Code Playgroud)
正如您所看到的,我正在尝试重新创建原始菜单的StackPanel/Image/Name组合,只是在后面的代码中这样做.试图做到这一点并没有那么好 - 虽然菜单对象肯定是在创建的,但它们并不像"空白",可点击的对象那样"出现" - StackPanel,Image,Name等都没有被渲染.有趣的是,它还会导致HierarchicalDataTemplate中ContentPresent中的原始文本被删除.
那么问题是,有没有办法在Load事件中设置MenuItem的Header属性,以便它在UserControl上正确显示?是否显示标题中的项目表示DataBinding问题?如果是这样,将Header绑定到瞬态对象(在load事件处理程序中创建的StackPanel)的正确方法是什么?
我愿意改变上面代码中的任何内容 - 这就是各种原型,试图找出处理动态菜单创建的最佳方法.谢谢!
我承认我没有像我想的那样深入挖掘你的例子,但是每当我看到搜索可视树的代码隐藏时,我认为,这可以在视图模型中更明确地处理吗?
在我看来,在这种情况下,你可以想出一个非常简单的视图模型-一个对象暴露Text,Image,Command,和Children特性,例如-然后创建一个简单的数据模板呈现它作为一个MenuItem.然后,任何需要改变菜单内容的东西都会操纵这个模型.
编辑:
看了你更详细的内容,以及你在博客文章中链接的两个例子,我正在敲打桌子.这两个开发人员似乎都误以为在模板生成的菜单项上设置属性的方法ContentPresenter.Load是在创建事件后搜索事件中的可视树.不是这样.这就是ItemContainerStyle它的用途.
如果您使用它,那么创建您所描述类型的动态菜单非常简单.您需要一个MenuItemViewModel已INotifyPropertyChanged实现并公开这些公共属性的类:
string Text
Uri ImageSource
ICommand Command
ObservableCollection<MenuItemViewModel> Children
Run Code Online (Sandbox Code Playgroud)
使用这个:
<Menu DockPanel.Dock="Top" ItemsSource="{DynamicResource Menu}"/>
Run Code Online (Sandbox Code Playgroud)
其中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>
Run Code Online (Sandbox Code Playgroud)
在窗口的菜单中究竟代表什么集合中,并作为项目添加和删除动态更新,既顶级项目,并给后代.
有没有关于在视觉树,无需手动创建对象的攀登,没有后台代码(比视图模型等,并在任何填充在首位的集合).
我已经建立了一个非常完整的例子; 你可以在这里下载项目.
| 归档时间: |
|
| 查看次数: |
10577 次 |
| 最近记录: |