How to update Dynamic Resource within a Dynamic Resource? - wpf

I have a visual brush which is a group of shapes, the main colour of which is a dynamic resource itself - so the shape is for example MyShape and the Colour, MyColour which is referenced by the Shape object.
My problem is when I update the colour for this - it only happens the first time the shape is loaded (the colour needs to be set first) however as much as I change the colour it won't update the dynamic resource that uses the colour - how do I make this work?
Just need to make a dynamic resource work within another dynamic resource and have them both update when I change the colour.
I have no idea how to get this to work - I spent time creating a colour-picker for WPF only to find I cannot change the colour of this item - 1-Tier resources work where I set the brush/colour directly but not a colour within another object or 2-Tier Resource.
Edit: My problem seems to be specific to using these in a seperate Resource / Dictionary as my program needs to access this item from a class not the Window, the main example mentioned does not work when the MyColor is in a seperate Resource.

Unless I misunderstand the situation, exactly what you're talking about works pretty well. I just tried it out with this Xaml:
<Window x:Class="ConditionalTest.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window1" Height="300" Width="300">
<Window.Resources>
<SolidColorBrush x:Key="MyColor" Color="Aqua" />
<VisualBrush x:Key="MyBrush">
<VisualBrush.Visual>
<Ellipse Height="50" Width="100" Fill="{DynamicResource MyColor}" />
</VisualBrush.Visual>
</VisualBrush>
</Window.Resources>
<Grid Background="{DynamicResource MyBrush}">
<Button Height="30" Width="Auto" VerticalAlignment="Center" HorizontalAlignment="Center" Content="ChangeColor" Click="Button_Click" />
</Grid>
</Window>
And then changed the color in the click handler for that button:
private void Button_Click(object sender, RoutedEventArgs e)
{
((SolidColorBrush)Resources["MyColor"]).Color = Colors.Purple;
}
And it worked like a champ.

Can you post an example of how you are attempting to change the color in the resource dictionary?
When I make a sample app and try to change the resource value it appears that the SolidColorBrush in the resource dictionary has been frozen so it can't be modified. To get around this I just set the new value to a new SolidColorBrush.

Related

Skinning Control Backgrounds - Better Performance?

sorry if this question is overly simple, but I'm having a hard time figuring out how to create backgrounds to controls - in the hopes that it will improve app performance.
I have 9 different controls. All of them have a background. The backgrounds are made up of either images, other controls or both. All of those backgrounds have another background.
Think of this like Power Point with slides, slide layouts and slide masters - inherited in that order. I have 9 slides / controls.
The first 3 controls have the same "control layout" (let's call it
ControlLayout1). ControlLayout1 gets some of it's elements from ControlMaster1.
The second 3 controls also have the same control layout, but it is
different from the first. Let's call it ControlLayout2. It also
inherits from ControlMaster1.
The final set of 3 controls are different again. We can call them
ControlLayout3. But this time, they inherit from a different master - ControlMaster2.
Right now in each control I'm writing out all the XAML each time separately. I'm thinking there must be a way to not write these in each of these each item. Ideally, what I would like to create is one set of XAML that can be reused.
Here's some pseudo-XAML:
<UserControl x:Name="Control1">
<MyBackground (ControlLayout1)/>
</UserControl>
<UserControl x:Name="Control2">
<MyBackground (ControlLayout2)/>
</UserControl>
<UserControl x:Name="Control3">
<MyBackground (ControlLayout3)/>
</UserControl>
And then somewhere for ControlLayouts (I don't know, like Application.Resources or elsewhere)
<Canvas x:Name="ControlLayout1">
<MyMasterBackground (ControlMaster1)/>
</Canvas>
<Canvas x:Name="ControlLayout2">
<MyMasterBackground (ControlMaster1)/>
<TextBox Text="The Control 2">
</Canvas>
<Canvas x:Name="ControlLayout3">
<MyMasterBackground (ControlMaster2)/>
<TextBox Text="The Control 3">
</Canvas>
And then for the ControlMasters
<Canvas x:Name="ControlMaster1">
<Canvas.Background>
<ImageBrush ImageSource="/Images/image1.jpg" />
</Canvas.Background>
</Canvas>
<Canvas x:Name="ControlMaster2">
<Canvas.Background>
<ImageBrush ImageSource="/Images/image2.jpg" />
</Canvas.Background>
<TextBox Text="Control Master 1">
</Canvas>
Once defined, the ControlLayouts and ControlMasters never need to change - they are static.
Beyond just having a smaller XAP if I can put these all in one location and reuse the XAML, I'm hoping performance will be improved in my app as the ControlLayouts automatically get BitmapCached or something like that.
So first, is there a good strategy to implement the above (the ControlLayouts and Masters do not have any code-behind)? Secondly will performance be improved in loading of Control1, Control2, etc.? Finally, if they were pure usercontrols (i.e. they had some code behind), would that be better for performance?
Thanks in advance!
What you ask for is a combination of a few things:
About the Background thing: just create a dependency property (let's call it MyBackgroundDP) of type Brush in the code behind of a UserControl, and bind it to your XAML like:
<UserControl ...>
<Grid Background={"Binding MyBackgroundDP, RelativeSource={RelativeSource Mode=FindAncestor, AncestoryType=UserControl}}">
<!-- More XAML declarations -->
</Grid>
</UserControl>
To create the dependency property, you can use the built in snippet in visual studio: propdp
Simply write "propdp" and that TAB twice. Fill up the fields and it's all good.
Alright so that was easy enough, right? ;)
Now the tougher part: making so-called master pages.
Actually it's not that much different from the background thing.
Declare another dependency property, only this time of type object, or FrameworkElement (better).
Then in your XAML, you declare a kind of placeholder: ContentControl. Let's call it MyContentDP for this example:
<UserControl ...>
<Grid Background={"Binding MyBackgroundDP, RelativeSource={RelativeSource Mode=FindAncestor, AncestoryType=UserControl}}">
<ContentControl ContentTemplate="{Binding MyContentDP, RelativeSource={RelativeSource Mode=FindAncestor, AncestoryType=UserControl}}" />
</Grid>
</UserControl>
You can then fine tune whatever else you want to provide in this "master view", add a border around the Grid, put some flowers, you name it.
Once you're done, this is how you use it, assuming it was called MyUserControl
<Window ...
xmlns:local="...reference_to_your_usercontrol_dll/exe">
<Grid>
<local:MyUserControl MyBackgroundDP="Red">
<local:MyUserControl.MyContentDP>
<!-- More XAML declarations here -->
</local:MyUserControl.MyContentDP>
</local:MyUserControl>
</Grid>
</Window>
Now the performance point:
If you put all the XAML for this as a Custom control (which is DIFFERENT from a UserControl), you can then put all the XAML in your App.xaml
Why? because parsing XAML can be an intensive operation, and if you make WP7/SL parse it at runtime whenever you need it, you lose performance.
Instead, your App.xaml gets parsed at startup, then it's in memory. That's what's done in the loading of your application. You would get a performance boost, although it would be minimal for controls made of few XAML, it is still a good practice.
Hope this helps,
Bab.

UI elements obscured from UIAutomation by UserControl

I have an automation client that uses the AutomationElement.FromPoint method to get the AutomationElement under the cursor:
AutomationElement element = AutomationElement.FromPoint(point);
Typically this works great, but I keep running into a problem with certain WPF applications. The problem occurs when there is a UserControl on the same UI level as another important UI element.
For example:
<Window x:Class="wpfTestApp.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525" xmlns:c="clr-namespace:wpfTestApp">
<Window.Resources>
<c:NameList x:Key="NameListData"/>
</Window.Resources>
<Grid>
<ListBox ItemsSource="{Binding Source={StaticResource NameListData}}"
Height="300" HorizontalAlignment="Left" Margin="10,10,0,0" Name="listBox1"
VerticalAlignment="Top" Width="153" >
</ListBox>
<UserControl Name="exampleUserControl">
<TextBlock Visibility="Hidden">Loading...</TextBlock>
</UserControl>
</Grid>
</Window>
When I attempt to point to any of the listbox items (or even the listbox itself), all I get is the "exampleUserControl".
I know that there are other methods for obtaining AutomationElements that don't depend on location, but in this case that is my only option, since we are trying to get the element under the cursor. The problem is, in this case, the important element (i.e. the listbox items) are covered up by this unimportant item ("exampleUserControl" containing "Loading..." text).
Is there any alternative to the FromPoint method, or some way that I can get it to ignore such elements?
There are several methods can find/search the Element.
You can find these interfaces on the MSDN http://msdn.microsoft.com/en-us/library/ms606809
For instance:
AutomationElement.FindFirst - http://msdn.microsoft.com/en-us/library/system.windows.automation.automationelement.findfirst
AutomationElement.FindAll - http://msdn.microsoft.com/en-us/library/system.windows.automation.automationelement.findall
// Sample: Find the first Element and the classname = ""SciCalc"
AutomationElement calcRoot = AutomationElement.RootElement.FindFirst(TreeScope.Children,
new PropertyCondition(AutomationElement.ClassNameProperty, "SciCalc"));
"AutomationElement.RootElement" is the parent of all the currently opened windows and controls.
For improve the performance you can find the target window first and then scan the controls on the AutomationElement of the Target window.
For instance: You can find and create the target WPF window via "AutomationElement.FindFirst" or "AutomationElement.FromHandle" first and search you listbox on the target window.
The clean solution to your problem is to set Visibility="Hidden" to the UserControl instead of the TextBlock

WPF Dynamic Resource Lookup Behavior with FixedPage

Having the following very simple xaml:
<DocumentViewer Name="dv">
<FixedDocument Name="fd" Loaded="fd_loaded">
<FixedDocument.Resources>
<Style x:Key="TestStyle">
<Style.Setters>
<Setter Property="TextBlock.Foreground" Value="BlueViolet"/>
</Style.Setters>
</Style>
<SolidColorBrush x:Key="foregroundBrush" Color="Orange"/>
</FixedDocument.Resources>
<PageContent Name="pc">
<FixedPage Name="fp" Width="800" Height="600" Name="fp">
<TextBlock Name="tb" Style="{DynamicResource TestStyle}">
Lorem ipsum
</TextBlock>
<TextBlock Foreground="{DynamicResource foregroundBrush}" Margin="20">
Lorem ipsum
</TextBlock>
</FixedPage>
</PageContent>
</FixedDocument>
</DocumentViewer>
The use of Dynamic Resources (which I actually need in a more complex situation) here doesn't work. Using Static Resources colors the TextBlocks in the desired colors. Moving the Resources to the level of the FixedPage also does the trick. But I would like to have one generic resource dictionary on a top level element (because of runtime changes the user can make for colours, fonts, etc.). Placing the resources on Application level also does work. But it's not an option for good reasons.
Anybody have any clue why this doesn't work. Does it have something to do with the Logical Tree from the TextBlock upwards?
MSDN Resources Overview states that:
The lookup process checks for the requested key within the resource dictionary defined by the element that sets the property.
If the element defines a Style property, the Resources dictionary within the Style is checked.
If the element defines a Template property, the Resources dictionary within the FrameworkTemplate is checked.
The lookup process then traverses the logical tree upward, to the parent element and its resource dictionary. This continues until the root element is reached.
I also tried putting the Brush and the Style into the Resources of a (dummy) Style according to the above explanation of MSDN. But that didn't work either.
Really have the feeling that this can not be that complex, but most probably I oversee something. Any help is appreciated.
EDIT
When naming the TextBlock to "tb" and then using tb.FindResource("TestStyle") throws an exception. So that resource clearly can't be found. If I check out LogicalTreeHelper.GetParent(tb) and repeat that for the parents found I get the expected result: TextBlock > FixedPage > PageContent > FixedDocument ...
EDIT2
This works perfect. What's the difference with the XAML projected earlier?
<Window x:Class="WpfDynamicStyles2.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Grid>
<Grid.Resources>
<SolidColorBrush x:Key="resBrush" Color="Orange"></SolidColorBrush>
</Grid.Resources>
<StackPanel>
<Button>
<TextBlock Foreground="{DynamicResource resBrush}">Dummy text...</TextBlock>
</Button>
</StackPanel>
</Grid>
</Window>
EDIT3
private void fd_Loaded(object sender, RoutedEventArgs e)
{
Object obj = pc.TryFindResource("foregroundBrush");
obj = fp.TryFindResource("foregroundBrush");
obj = tb.TryFindResource("foregroundBrush");
}
The dynamic resource placed on the Foreground property of the textbox cannot be resolved (the actual resource is at the FixedDocument.Resources level). Also using the TryFindResource in code behind works from pc (PageContent) but from fp (FixedPage) and tb (TextBlock) it cannot resolve the resource (obj is null). When using a Static Resource in the XAML Markup everything works fine.
Well going the style way is a good design but not necessary. To make dynamic color based same named (keyed) brushes, we can use as dynamic color dictionaries (NOT brush dictionaries)
The solution can be as below ...
Create a single brush resource dictionary.
Refer the color in the brush with DynamicResource attribute.
Create multiple resource dictionaries with same keyed Color resource in each of them them.
Based on user's requirement, clear and add into Application.Current.resources.MergedDictionaries.
Example
Make a WPF project with a window that has following XAML....
<Window.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="Resources/Window11Resources.xaml"/>
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Window.Resources>
<DockPanel LastChildFill="True">
<ComboBox DockPanel.Dock="Top" VerticalAlignment="Top"
SelectionChanged="ComboBox_SelectionChanged"
SelectedIndex="0">
<ComboBox.ItemsSource>
<Collections:ArrayList>
<System:String>Orange</System:String>
<System:String>Red</System:String>
<System:String>Blue</System:String>
</Collections:ArrayList>
</ComboBox.ItemsSource>
</ComboBox>
<DocumentViewer>
<FixedDocument>
<PageContent>
<FixedPage Width="793.76" Height="1122.56">
<TextBlock
FontSize="30"
Foreground="{StaticResource LabelColorBrush}"
Text="Test"/>
</FixedPage>
</PageContent>
</FixedDocument>
</DocumentViewer>
If you observe the window doesnt need to use anything which is dynamic. All refernces can remain static. So LabelColorBrush can be found in dictionary Window11Resources.xaml
In Window11Resources.xaml dictionary add a dynamic color brush.
<SolidColorBrush x:Key="LabelColorBrush"
Color="{DynamicResource DynamicColor}"/>
Add following 3 color brush dictionaries in some folder from your project...
<!-- Name = OrangeColorResource.xaml -->
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Color x:Key="DynamicColor">Orange</Color>
</ResourceDictionary>
<!-- Name = BlueColorResource.xaml -->
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Color x:Key="DynamicColor">Blue</Color>
</ResourceDictionary>
<!-- Name = RedColorResource.xaml -->
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Color x:Key="DynamicColor">Red</Color>
</ResourceDictionary>
Note that the key remains the same i.e. DynamicColor.
Now clear and recreate color dictionaries in App.Resources. I have done that in the code behind of Window.xaml.cs
private void ComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
Application.Current.Resources.MergedDictionaries.Clear();
Application.Current.Resources.MergedDictionaries.Add(
new ResourceDictionary()
{
Source = new Uri("Resources\\"
+ ((ComboBox)sender).SelectedItem.ToString()
+ "ColorResource.xaml",
UriKind.RelativeOrAbsolute) });
}
Now as and when you select a color from the drop down, the dynamic color changes on the static resource brush. Note that there is no Style involved.
I hope this answers what you are asking.
By the way: the reason for this post had some more complex background. I really wanted to use a single resource dictionary whereby I could change colors, fonts, fontsizes, etc. dynamically during runtime (by an end-user). I am working on a tax form application. And the tax forms presented on screen for user input are resolving their resources at the Application level. So far so good.
But at the same time I want to present the user a print preview of the tax form where the same resourcedictionary (as object type, not as object instance) is used to define color schemes, fonts, fontsizes, etc. to be used for printing. Which can differ from the styling used for on screen user input. That's why I wanted to "attach" the resource dictionary on (for example) the FixedDocument level so that all pages in the document would refer to it. And when changing colors, fonts etc. all pages with common elements (TextBlocks, TextEditors, etc.) would respond to the change.
And then I became stuck...as described in this post.
For now I have a nice workaround by creating a specific instance of the resourcedictionary, storing it as a private variable and attach it to every individual page I put in the fixed document. It works and that already pleases me. But my programmers heart is still a little ached, because I would prefer to use a single instance of the resource dictionary and put it at some top level control so all controls in it can use it.
As a programmer we have to live with workarounds as well ;) If you have any further suggestions, I'm open to receive them. Thanx for your support thus far.
Greetz, Joep.
Also see: MSDN Forum Post about this issue

Split one big XAML in number of Sub-XAML files

In my WPF4 Desktop-based application there is a big block with sidebar menu that repeats in each window and takes about 70 lines of XAML. In order to improve code reuse, I would like to split XAML file in two files:
XAML-file that contains code for sidebar menu (≈70 lines)
Base XAML file that contains «include/reference» to XAML-file with sidebar menu code
As I understood, there are two ways to implement my problem:
Use ResourceDictionary
Use UserControl/CustomControl
My questions:
What is the difference between ResourceDictionary and UserControl? Could you give me examples where I have to use UserControl and where ResourceDictionary?
Could you give a full code example how to include/import content of one XAML-file to other?
P.S. Here is an example of code that I want to export to separate XAML-file:
<Border Style = "{StaticResource Body_SideBarMenu_Border_Settings}">
<StackPanel Style = "{StaticResource Body_SideBarMenu}">
<TextBlock Style = "{StaticResource Body_SideBarMenu_Title}"
Text = "{x:Static res:Resources.WinApp_SideBarMenu_Title}" />
<TextBlock x:Name = "SideBar_WinReports"
Style = "{StaticResource Body_SideBarMenu_Item}"
Text = "{x:Static res:Resources.DashListMarker}">
<Hyperlink KeyboardNavigation.TabIndex = "12"
Style = "{StaticResource Body_SideBarMenu_Item_Hyperlink}"
Click = "Call_WinReports_Click">
<TextBlock Text = "{x:Static res:Resources.WinApp_ModuleName_Reports}" />
</Hyperlink>
</TextBlock>
</StackPanel>
</Border>
ResourceDictionary is just a container for your styles/templates etc. So you really have a choice between using a style (and referencing it through a ResourceDictionary) or a UserControl.
In order to differentiate between the two, ask yourself a question: are you implementing just another look for some existing control, or you are implementing something really new, which is more than just a ListView (or a Border, or a ComboBox etc.)? In the former case, use a style; in the latter, create a new UserControl.
Specifically for your case, I would go for a UserControl.
Code example (although not full)
(Please note that a template for the following code can be inserted with VS's "add new UserControl")
Xaml:
<UserControl x:Class="SomeNamespace.SidebarMenu"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<UserControl.Resources> <!-- you can define local styles here -->
<Style x:Key="SidebarMenuTextblock" TargetType=TextBlock>
...
</Style>
</UserControl.Resources>
<Border Background=...>
<StackPanel>
<TextBlock
x:Name="Put_a_name_if_you_want_to_reference_this_item_in_code_behind"
Style="{StaticResource SidebarMenuTextblock}"
Text="{x:Static res:Resources.WinApp_SideBarMenu_Title}" />
...
</StackPanel>
</Border>
</UserControl>
.cs:
using System;
using System.Windows;
using System.Windows.Controls;
namespace SomeNamespace
{
public partial class SidebarMenu : UserControl
{
public NumericUpDown()
{
InitializeComponent();
}
...
// define here your properties etc,
}
}
Now, you can use the control like that:
<Window
x:Class="SomeOtherNamespace.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="clr-namespace:SomeNamespace">
<Grid>
<controls:SidebarMenu PropertyIfYouDefinedOne="SomeValue"/>
...
</Grid>
</Window>
If you can get your hands on Expression Studio, in Expression Blend, you can simply right click on any control and convert it to an user control. As easy as that.
User controls are good for splitting the XAML file. In essence, it is used to redefine the behavior of an existing control.
However, with User Controls, you can define whole WPF Layout Controls and convert them to an User Control, with the children content inside them. This is very helpful for a project spread across multiple developers, and can also be used to emulate the behavior of an MDI, which is kind of absent in WPF.

How to dynamically add MenuItems (with a header) to a WPF menu

[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!
I'll confess that I haven't dug quite as deep into your example as maybe I should, but whenever I see code-behind that's searching the visual tree, I think, could this be handled more explicitly in a view model?
It seems to me in this case that you could come up with a pretty straightforward view model - an object exposing Text, Image, Command, and Children properties, for instance - and then create a simple data template that for presenting it as a MenuItem. Then anything that needs to alter the contents of your menus manipulates this model.
Edit:
Having looked at what you're up to in more detail, and the two examples you've linked to in your blog post, I am banging my head against the desk. Both of those developers appear to be under the misapprehension that the way to set properties on the menu items that are being generated by the template is to search through the visual tree in the ContentPresenter.Load event after they're created. Not so. That's is what the ItemContainerStyle is for.
If you use that, it's quite straightforward to create dynamic menus of the type you're describing. You need a MenuItemViewModel class that has INotifyPropertyChanged implemented and exposes these public properties:
string Text
Uri ImageSource
ICommand Command
ObservableCollection<MenuItemViewModel> Children
Using this:
<Menu DockPanel.Dock="Top" ItemsSource="{DynamicResource Menu}"/>
where the ItemsSource is an ObservableCollection<MenuItemViewModel>, and using this template:
<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>
the menus in the window exactly represent what's in the collection, and are dynamically updated as items are added and removed, both to the top-level items and to the descendants.
There's no clambering about in the visual tree, no manual creation of objects, no code-behind (other than in the view model, and in whatever populates the collection in the first place).
I've built a pretty thoroughly worked example of this; you can download the project here.
Another possible approach could be having the Menu be a region and agree on a convention so all views added to that region have a ViewModel with a property named MenuHeader. That way, the region adapter can simply get the menu header from the View's Data Context, and set it to the item when adding it.
Something similar is done in Prism with views added to a Tab Region. You can read more here.
I hope this provides some useful guidance.
Thanks,
Damian

Resources