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


