[第三次编辑] - 对于阅读此问题的任何人:千万不要使用本问题中概述的方法。这是一场编程恐慌。我自由地承认这一点,因为所有程序员在过去都曾陷入困境,而且(特别是在学习新技术时)我们都曾被网络上其他好心的开发人员误导。请先阅读Robert的答案,然后再阅读这个问题。请。
[第二次编辑b]
对于问题长度我深感歉意-最后真正的问题藏在问题之中,但我希望确保源代码是明确的。无论如何。
[第二次编辑] - 将问题标题更改为更准确地反映问题。
[编辑] - 我在这里更新了更多关于我如何设计/编写代码的历史记录:Obligatory Blog Post。如果它有助于澄清下面的问题,请随时阅读它...
原始问题
我正在处理的应用程序使用Prism和WPF,其中包含多个模块(目前为3个),其中一个模块托管应用程序菜单。最初,该菜单是静态的,并使用CompositeCommand / DelegateCommands中的钩子,这非常适用于将按钮按下路由到适当的Presenter。每个菜单项在其标题中使用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中的处理程序。有几篇关于这个主题的优秀文章-我看过的两篇文章是使用分层数据模板构建数据绑定的WPF菜单和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的ContentPresenter中的原始文本被擦除。
那么问题来了,有没有一种方法可以在Load事件中设置MenuItem的Header属性,使其能够正确地显示在UserControl上? Header中的项目未显示是否表明存在数据绑定问题?如果是,将Header绑定到暂时对象(在load事件处理程序中创建的StackPanel)的正确方法是什么?
我愿意更改以上代码的任何内容,这些都是试验性的,试图找到处理动态菜单创建的最佳方法。 谢谢!