Modifying a ControlTemplate in a Style at runtime (programmatically)
My problem this time was modifying a ControlTemplate at runtime. Let me explain:
I have a WPF Button which I create programmatically and I set a pre-defined Style to it.
This style defines a ControlTemplate with a VisualTree as follows:
Button btn = new Button(); btn.Content = "Category"; btn.Style = this.Resources["catButtonStyle"] as Style; btn.Click += Button_Click; myGrid.Children.Add(btn);
This style defines a ControlTemplate with a VisualTree as follows:
ControlTemplate/DockPanel/Border/Grid/TextBlock/ContextMenu
The ContextMenu contains two MenuItems but I wanted to add a third MenuItem to be displayed as a Contextual menu at runtime.
Here is my Style:
<Style x:Key="catButtonStyle" TargetType="Button"> <Setter Property="OverridesDefaultStyle" Value="True" /> <Setter Property="Margin" Value="2" /> <Setter Property="FontFamily" Value="Verdana" /> <Setter Property="FontSize" Value="11px" /> <Setter Property="FontWeight" Value="Bold" /> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="Button"> <DockPanel Name="CategoryDockPanel"> <Border Name="border" BorderThickness="1" Padding="4,2" BorderBrush="DarkGray" CornerRadius="3" Background="{TemplateBinding Background}"> <Grid Name="CategoryGrid"> <TextBlock Text="My button TextBlock" Name="block"> <!-- Context Menus --> <TextBlock.ContextMenu> <ContextMenu> <MenuItem Header="Properties" /> <MenuItem Header="Remove" /> </ContextMenu> </TextBlock.ContextMenu> </TextBlock> </Grid> </Border> </DockPanel> <ControlTemplate.Triggers> <Trigger Property="IsMouseOver" Value="True"> <Setter TargetName="border" Property="BorderBrush" Value="#FF4788c8" /> <Setter Property="Foreground" Value="#FF4788c8" /> </Trigger> </ControlTemplate.Triggers> </ControlTemplate> </Setter.Value> </Setter> </Style>
So the main logic I had to follow to that was the following:
- Find the target Control inside of my ControlTemplate
I struggled a little bit with this step because I was not able to Find my target control inside of my ControlTemplate. Note that it is very important to call the ApplyTemplate() method BEFORE trying to find a Control in a ControlTemplate otherwise you will always get null back. This step I was missing and gave me a headache.
Once you ApplyTemplate, there are basically two ways of finding controls:
a) By Control Name: If you choose this route you need to give a Name to the immediate child of your ControlTemplate, in my case was <DockPanel Name="CategoryControlPanel". This link gives a good explanation on how you can Find Controls in a ControlTemplate: How to: Find ControlTemplate-Generated Elements
b) By Control Type. If you don't want to give a Name to your ControlTemplate Controls then you can use a helper method to find VisualChildren, like this: Find all controls in WPF Window by type - Manipulate the content of my target control. This step is trivial
Here is how my code looks like:
MenuItem mi = new MenuItem(); mi.Header = "Add Mapping"; //Important: ApplyTemplate First so our ControlTemplate controls are available btn.ApplyTemplate(); //Find by Name DockPanel dp = btn.Template.FindName("CategoryDockPanel",btn) as DockPanel; //Or Find target control directly by Type var tmp = btn.FindVisualChildren<TextBlock>().ToList(); if(dp != null) { //Get a list of TextBlock childrens in the Visual Tree var tb_list = dp.FindVisualChildren<TextBlock>(); if(tb_list != null && tb_list.Count() > 0) { //Get the first TextBlock TextBlock tb = tb_list.First(); if (tb != null && tb.ContextMenu != null) { //Manipulate Context Menus tb.ContextMenu.Items.Add(mi); } } }
Notice that using the "FindVisualChildren" helper method is a quicker way of finding a control by type. But that will depend on your visual tree. There may be situations where you have multiple controls of the same type and you want to access a specific one. Here is where finding by name could be a good option.
That's it for now. Hope you find this useful.
Adolfo
That's it for now. Hope you find this useful.
Adolfo