WPF Customization Continuum
When Microsoft designed WPF they also thought about customization continuum which was not present in Windows Forms. To some extent it was present in Windows Forms but it required writing code and take control of the rendering of the control or else we had to write a custom control altogether. MS referred to this problem as the owner draw cliff.
WPF Controls are called as lookless controls and the templates can be used to change the appearance of a button to circle with one line of xaml.So a WPF controls has properties, events, commands, etc for it but it does not know how to display itself. So we display a button in multiple formats using just a line of xaml and define the control’s Template.
Control Template
A Control Template describes what goes into the visual tree to represent a template. As you can see in the code below that the button template contains nothing but ellipse. Also the buttin still has the functionality of a button like the events but it looks like an ellipse.
Template Binding
The most important thing that a template do is connect the properties of a template to the properties of a control. As we can see in the code below we have used TemplateBinding. Now whatever background property I set on the button will be passed to ellipse for the background.
But if you change this Template Binding to ordinary Data Binding then the background stops working. The reason is that this binding does not have a DataSource so it tries to use the data context of its target element. But the Ellipse has no DataContext. So we would think that it will get the DataContext from the ControlTemplate which in turn will get its DataContext from its Target Element which in this case is the Button. But Button does not have a data context and nor does the page. And hence the DataContext is null and that is why nothing is happening.
We can point the DataContext or source to a control. We can also use a property called RelativeSource. RelativeSource looks up the binding by looking up the visual tree. We can do it in two ways:
- We can specify the type of control to look for
- Or we can specify the control
In this case we have specified the RelativeSOurce to be the TemplatedParent. The TemplatedParent means whichever template this object was instantiated for. This works but ots crumbersome to use, so we have the simpler TemplateBinding itself.
Another reason for the existence of TemplateBinding is that even though DataBinding is powerful it is heavy weight because of Value conversion, two way binding and validation.
Triggers
Like Data Templates Control Templates can have Triggers. They support Event Triggers and Data Triggers as well as Property Trigger. The difference between a Data Trigger and a Property Trigger is the same as Data Binding and Property Binding. While Data Binding monitors the property of the Data Source, a Property Trigger monitors the changes in the Control’s Template property change.
Generally the way these triggers work is that there is a named element inside the Controls Template and then the triggers specify the name of that element as the target. It helps us have effects on the parts of the controls that do not directly connect to the public properties of the control.
Contract between Controls and Templates
Controls depend on templates to provide certain visual features and templates depend on controls to provide behaviors. So there is contract between the controls and its templates and this contract varies in complexity. For a Button the contract is very simple. The two things that a Button asks its template to provide is
- Bubbling of the mouse events. This is mandatory as if this is not provided then the button will not able to raise the mouse events and hence it will not function as a button. But this implemented by default so nothing needs to be done there.
- Provide a place for content place holder. This one is optional as all buttons might not need content.
A complex contract example can be a scrollbar. As a Scrollbar contains a dragable thumb area whose size is proportional to the area to be scrolled. Also its movements should be constrained within the bounds of the clickable arrow buttons. So scrollbar does is define a control called Track in Windows.Controls.Primitives namespace whose only job is to define the dragable region of the scrollbar and sliders. The Track imposes structure and has 3 properties to hold the 3 parts of the track.
- The Thumb – Mandatory
- Up and Down Repeat Buttons – Optional. It is a control in the primitive namespace and has an additional functionality of repeating itself when it is kept pressed down.
The Specialized control named Thumb is also part of the primitive namespace. It is a Content control that provides events to control the drag operations. WPF uses it not only in the scrolllbars but also the resize control at the bottom right of the resizable window. This can be used by developers if a dragable control is needed in the user interface.
The scrollbar needs the lineup and linedown commands to be implemented but it does not care if it’s a hyperlink or a repeat button. It will just move up or move down by one line.
Also the empty region of the scrollbar which might seem like the track area is the place where the PageUp and PageDown commands are implemented for buttons.
Contract kinds
The Contract kinds can be of types below.
- Named Parts – The control mearly requires the template to contain certain names. A ProgressBar is an example of a Named Parts Control. We can see the customized template of the ProgressBar below. The two of the controls in the progress bar template have named and these are names that the control will look for. The documentation tells us to define the PART_Track which tells the control the area to sweep. And the PART_Indicator that the control will resize when the value property changes and adjust the indicators width to show the progress. If we do not provide these named parts then the progress bar will not complain. The nature of the progress bar is that if you want it to do the resizing then you provide the named parts.
- Common Idioms – The Standard ways in which the controls work
- ContentControl – This is one of the common idioms for the controls. This is used by a button. The template is required to provide the ContentPresenter to provide the content.
- HeaderdContentControl – This is like the ContentControl. It has 2 ContentControls. One defines the Content and one defining the Content for the header of the control.
- ItemsControl – Items control are a bit complex and it is a bit unusual that they provide template for each individual control. A problem with WPF template model model is that it is an all or none proposition. If we want to make a template which is slightly different from the default template we need to replace the whole template. But ItemControl provide more fine grain templating by providing three important properties.
- ItemsPanel
- ItemsPanelTemplate – It is the 3rd type of template provided by WPF. Let’s see it in action. Add the following code to the Window.
- ItemsPanel
However while defining the ItemsPanelTemplate we have turned off the virtualizing feature and this will degrade the performance of the ListBox when it has lot of items. But if we use VirtualizingStackPanel we will still be able to enjoy speedy performance. However this VirtualizingStackPanel is the only virtualization aware control. We can also write our own Virtualizing panel.
- ItemsTemplate
- DataTemplate – This helps us customize the appearance of the indivisual elements without having to customize the whole control.
- Template (Whole Control)
- ControlTemplate– This will replace the appearance of the whole control. Below is the ListBox using ControlTemplate. It requires only two things.
- If we want scrolling then we need to provide a scrollviewer. The control template will look for this as it will need the scrollviewer to collaborate with the virtualization. If it doesn’t find then it will turn off the scrolling.
- We need to provide the placeholder for the item. We need to use an Itemspresenter in the template. We can also place a VirtualizingStackPanel and set the IsItemsHost property to true. By doing this the ItemsPanel Template is completely ignored and Panel in the ControlTemplate is used. But if wants the flexibility of using different layouts then we can use the ItemsPresenter in the ItemsTemplate.
- ControlTemplate– This will replace the appearance of the whole control. Below is the ListBox using ControlTemplate. It requires only two things.
- HeaderedItemsControls – Combines ItemsControl Functionality with ContentControl features. We can use templates with the mixture of both styles.As you can see in the code below we have a StsckPanel whose IsItemsHost property is set to true. And then we have the ContentProvider that will provide the TreeViewItem header property. Also TreeView ItemsTemplates need to provide a toggle button to provide the functionality of a toggle button to expand and collapse the node. We have used a CheckBox here and we have used DataBinding inside it as opposed to ItemsBinding because ItemsBinding supports only one way binding.
- Control specific Custom Contracts
Templates are Factories
Template is a slightly different object than the user interface elements normally deal with. We place the same button at two different places in the user interface then it will complain as one UI element can have only one parent. But we can use the same template multiple times without any error. The reson is that Templates are factories and everytime they are used WPF creates a new copy. This applies to both DataTemplate as well as ControlTemplate. The base class for templates i.e. FrameworkTemplate provides a method to generate a copy of the template named LoadContent. This means the mapping of XAML and code is not quite as straight forward as other markups. The XAML enters a different mode when working with a template. So instead of generating an Ellipse in the example shown below, it actually generates a Factory to generate an ellipse with the specified properties.
So it creates a ControlTemplate Object and its content becomes a Tree of FrameworkElmentFactory. Each element in the template becomes a new factory object and this factory structure corresponds to the xaml tree structure. And this tree of factories becomes the template property for the visual tree.
Logical and Visual Tree
So we have now seen enough to know how the control expands from a single logical nod to a set of nodes in the visual logical tree. When the Visual Tree is loaded the Control reads its own Template property and then calls LoadContent on that causing the FrameworkElement factory tree to generate a new instance on behalf of the button. The control then attaches this to the visual tree by using the helper class named Visual Tree Helper and that’s how a control gets to appear.
Any questions, Comments or feedback are most welcome.
The Code for this post can be found here.