So my question on StackOverflow ended up getting a great answer that worked perfectly. Where did I go wrong?
I listened to advice on teh internets (and I'm still learning WPF, which, if I knew it better, would have precluded me from going down this wonky path at all). The "path" I ended up going down had a dynamic menu being created in a HierarchicalDataTemplate (good), but the actual content of the menu being generated in a load event in the code-behind (bad).
It's not that I'm against code in the code-behind. I think there are times where it does make sense - take click event handlers in a ListView, for example. There isn't a great way to route that through an ICommand object, at least that I'm aware of. Often you aren't going to really do anything in the click handler - you just need to pass it back to the PresentationModel / ViewModel, which has all the logic to do the action of the click anyway. You just need to handle the click. And, unlike a menu, toolbar, or other button, you probably have a one-to-one relationship between the click handler and the method in PM/VM that will handle the action (so the benefit of an ICommand object is lessened somewhat). Anyway.
That rant over, I pretty much knew I was hosing myself when I had written this blob of code:
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);
}
}
}Yeah, that's no good at all. The end result of this was a "blank" menu, where the non-rendered items were clickable, but no rendering was done on the StackPanel embedded in the MenuItem's Header. Close, but no cigar.
Luckily, you can handle this pretty easily using the ItemContainerStyle property of a HierarchicalDataTemplate. (I added the additional Style directive to handle the case where the command needs to occur on an item in the first row of the Menu)
<UserControl.Resources>
<model:RecapToolBarPresentationModel x:Key="modelData" />
<Style TargetType="MenuItem">
<Setter Property="Command" Value="{Binding Path=Command}"/>
</Style>
<HierarchicalDataTemplate DataType="{x:Type model:ToolbarObject}"
ItemsSource="{Binding Path=Children}">
<HierarchicalDataTemplate.ItemContainerStyle>
<Style TargetType="MenuItem">
<Setter Property="Command" Value="{Binding Path=Command}"/>
</Style>
</HierarchicalDataTemplate.ItemContainerStyle>
<StackPanel VerticalAlignment="Top" Margin="5,0,5,0">
<Image Height="24" VerticalAlignment="Center" Source="{Binding Path=ImageLocation}"/>
<ContentPresenter Content="{Binding Path=Name}" HorizontalAlignment="Center"/>
</StackPanel>
</HierarchicalDataTemplate>
</UserControl.Resources>Lessons Learned:
- I've got a lot to learn in WPF still. Knowing enough to get some basic ListViews displayed (and enough to get Prism more or less working for me) is great and all, but the more complex DataTemplating is still a learning ground. Good stuff though - the power of WPF when compared against WinForms (or worse, MFC) is simply astounding.
- Watch out for advice coming teh internets. StackOverflow's ratings really do provide a nice level of confidence on the advice you get (which, incidentally, you certainly aren't getting by reading this blog)
Can you post a sample project with the above code snippet please?
ReplyDelete