Wednesday, May 29, 2013

WPF: TemplateBinding with ControlTemplate

Today I'll try to write bit on TemplateBinding and how to use it inside a ControlTemplate. TemplateBinding is a type of binding used mainly for template scenarios. Here I am not going to write more on it's theoretical aspect as what is TemplateBinding, when to use, blah blah blah, as lot of content is readily available on net.  So, let's start quickly onto coding part:

First of all, let's create a new project using WPF template and place a button in it as below:
Now, what I am going to do is, I am going to replace this content template for this button. So, in order to do this, open up the Button tag and add Button.Template markup tag with a new ControlTemplate as:












Now as soon as you will add ControlTemplate tag, you will notice that the content of the button is gone and button is shown as a transparent rectangle. This is happened because here I told WPF to replace the default ControlTemplate with the one, which I defined. But at this point, our ControlTemplate is blank, so there is no visualization and we can see only a transparent rectangle.

Now go ahead and customize our ControlTemplate by putting Ellipse inside it as:








Now we can see that, we get a visualization for a button in form of ellipse. At this point of time, it works OK, but there are scenarios where this struct breaks down.

For example, Let's increase the height of button, from 35 to 105 as:








In above image, you will notice that button height is increased but the ellipse size is still the same, which is a bad UI design. And the reason this is happening is, inside a ControlTemplate, the height of an ellipse is hard coding. So, no matter, whatever height is set at parent (i.e. Button), it will not get inherited to child control (i.e. Ellipse).

So, now we have to fix this problem by using a special type of binding called TemplateBinding inside ControlTemplate. So, instead of hard coding the height, we will use TemplateBinding as shown below:









By setting the TargetType property of ControlTemplate, we are telling Ellipse that, any property of  Button can be set to ellipse. Now, whatever the height of button will be, it will automatically be the height of ellipse also. Isn't it interesting?

Moving forward, let's do something more interesting with Fill property of ellipse.














In above snippet, I am trying to set the Fill property of an ellipse using TemplateBinding. But now problem here is, a button doesn't have a Fill property. So, there is no one-to-one mapping for Fill property. Now, what to do ?

No need to worry that much because button does has a Background property as:









In above image, you might have notice that as soon as ellipse's Fill property is set to Background, ellipse becomes transparent as button's background. Now if we set button's Background property to Red, the same will be applied to ellipse too.












So, one can understand that how much magic we can do with TemplateBinding.
Now, let's work little bit on code cleanup.

ControlTemplate inside resource dictionary:
For better code readability, we will move out our ControlTemplate code and put it inside a resource dictionary as:











So, now we can see as earlier that whatever visual property for button is set, all get applied to ellipse also.

Hope this article was useful and gave you the basic idea on how to use TemplateBinding.


4 comments:

  1. Thanks for the quick start :)

    ReplyDelete
  2. This is pretty interesting, but i got one doubt.

    How can we implement the events in control Template? I tried normally, but it is not working for the Button control.

    ReplyDelete
    Replies
    1. Hello Harish,
      The template is applied at runtime, and so elements contained within it it are part of the visual tree. Your class, and therefore class’ constructor is executed within the logical tree, so you needed to attach your event handler after the template had been applied. You can do this by overriding OnApplyTemplate in your class, obtain the template that’s being applied, and then you have access to the named button’s events, like this:

      public override void OnApplyTemplate()
      {
      DependencyObject d = GetTemplateChild("PART_KeyboardPopupButton");
      if (d != null)
      {
      (d as Button).Click += new RoutedEventHandler(KeyboardPopupButton_Click);
      }
      base.OnApplyTemplate();
      }

      Delete
    2. Hi Shweta,

      Thanks for your quick reply.

      But unfortunately this is not working for me.

      Here is my Code:

      Grid.Resources
      ControlTemplate x:Key="controlTemplate" TargetType="{x:Type Thumb}"
      Grid x:Name="PreviewGrid"
      Grid.RowDefinitions
      RowDefinition/
      RowDefinition/
      /Grid.RowDefinitions
      Border BorderBrush="Black" BorderThickness="2" Grid.Row="0" Background="#303030"
      Grid x:Name="firstGrid" Background="#303030" Width="{TemplateBinding Property=Width}" Height="30"
      Button x:Name="btnZoomIn" Click="btnZoomIn_Click" HorizontalAlignment="Right" VerticalAlignment="Center" Width="30" Margin="0,0,10,0"
      Height="30" Content="+" FontSize="18" FontWeight="Bold" Visibility="Hidden"/
      Label x:Name="lblZoomPercentage" Visibility="Hidden" Content="100%" Foreground="White" HorizontalAlignment="Center" VerticalAlignment="Center" FontWeight="Bold"/
      Button x:Name="btnZoomOut" Click="btnZoomOut_Click" HorizontalAlignment="Left" VerticalAlignment="Center" Width="30" Margin="10,0,0,0"
      Height="30" Content="-" FontSize="18" FontWeight="Bold" Visibility="Hidden" /
      Slider Grid.Row="0" x:Name="zoomSlider" Width="100" HorizontalAlignment="Center" VerticalAlignment="Center" Minimum="5" Maximum="50" Value="10"
      IsMoveToPointEnabled="True" Margin="5" Grid.IsSharedSizeScope="True"
      ValueChanged="zoomSlider_ValueChanged" PreviewMouseDown="zoomSlider_PreviewMouseDown" PreviewMouseUp="zoomSlider_PreviewMouseDown"/
      /Grid
      /Border
      Canvas Grid.Row="1"
      Border BorderBrush="Black" BorderThickness="2"
      Rectangle Width="160" Height="128"
      Rectangle.Fill
      VisualBrush x:Name="visualBrush" Visual="{Binding MyInkCanvas}"/
      /Rectangle.Fill
      /Rectangle
      /Border
      /Canvas
      /Grid
      ControlTemplate.Triggers
      Trigger Property="IsEnabled" Value="True"/
      /ControlTemplate.Triggers
      /ControlTemplate
      /Grid.Resources


      Please help me on this. I just want to use the click events of btnZoomIn and btnZoomOut

      Thanks,
      Harish.

      Delete