Converting WPF UserControls to Controls with a theme
I was working on this application and so I found it easy to create some controls as UserControl classes. However, I realised that if I wish to centralize the styling of the application or even move some of the controls in their own control library with a Themes folder, I would need to transform them into Control classes.
I found that there are two major problems that must be overcome:
- the named controls in a user control can be accessed as fields in the code behind; a theme style does not allow such direct access to the elements in the control template.
- the controls that expose an event may not expose an associated command. In a user control a simple code behind handler method can be attached to a child control event, but in a theme a command must be available in order to be bound.
Granted, when the first problem is solved, it is easy to attach events in the code of the control to the child elements, but this presents two very ugly problems: the template of the control will need to contain the child elements in question and the event will not be declared in the theme, so the behaviour would be fixed as well.
I will solve the handling of events as commands by using a solution from Samuel Jack. The article is pretty detailed, but to make the story short, one creates an attached property for each of the handled events by using a helper class:
public static class TextBoxBehaviour
{
public static readonly DependencyProperty TextChangedCommand =
EventBehaviourFactory.CreateCommandExecutionEventBehaviour(
TextBox.TextChangedEvent, "TextChangedCommand", typeof (TextBoxBehaviour));
public static void SetTextChangedCommand(DependencyObject o, ICommand value)
{
o.SetValue(TextChangedCommand, value);
}
public static ICommand GetTextChangedCommand(DependencyObject o)
{
return o.GetValue(TextChangedCommand) as ICommand;
}
}
then by using a very simple syntax on the control that fires the event:
<TextBox ff:TextBoxBehaviour.TextChangedCommand="{Binding TextChanged}"/>
The problem regarding access to the elements in the template is solved by reading the elements by name from the template. In some situations, like when one uses a control as a source for a data control (like using a TreeView as the first item in a ComboBox), the approach will have to be more complicated, but considering the element is stored in the template of the control, something like this replaces the work that InitializeComponent does inside a UserControl:
[TemplatePart(Name = "PART_textbox", Type = typeof (TextBox))]
public class MyThemedControl : Control, ITextControl
{
private TextBox textbox;
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
textbox = Template.FindName("PART_textbox", this) as TextBox;
}
...
The code is pretty straight forward: use the FrameworkTemplate.FindName method to find the elements in the OnApplyTemplate override and remember them as fields that you can access. The only weird part is the use of the TemplatePartAttribute, which is not mandatory for this to work, but is part of a pattern recommended by Microsoft. Possibly in the future tools will check for the existence of named elements in the templates and compare them against the ones declared in the control source.
The code of a demo project can be downloaded here.
Some other technologies I have used in the project:
- the RelayCommand class, to make it easier to defined ICommand objects from code without declaring a type for each.
- the AccessKeyScoper class that allows an IsDefault button to act locally in a panel.
Comments
Be the first to post a comment