Advanced WPF Part 1 of 5: Reusable UI in Windows Presentation Foundation

 

 

In this section we will have a look at how we can reuse resources, templates, xamls, custom elements and custom controls.

Resource System

The first way of making UI reusable is using the Resource System provided by WPF. But this approach cannot be used at all the places because it is not supported for UI elements. We can define UI elements in the resource but if we use it more than one place we will get an error as the resource system will refer to the same underline object. So this approach will be useful where we can reuse the objects itself. So this approach is perfect for Freezeable as the types which derive from freezable are shareable. For example if we have a logo which needs to be shown at multiple places then we can define it as a resource and reuse it.

*A freezable object has two states – frozen and unfrozen. When an object is frozen, it cannot be modified and it cannot even fires events but the unfrozen state, it works same as a normal object.

There is a trick which works in compiled xaml scenarios can be used to define reusable framework elements. We use the compiled XAML’s deferred loading to give each resource reference a new copy. As in compiled xaml the Resource Dictionaries are loaded on demand. This won’t work if this xaml is parsed at runtime.

Let’s see this in action. Add the following code to your window. The resource that we see here has x:Shared to false. When you are typing this xaml you will not find this appearing in intellisense . The reason is because Microsoft has hidden it and it might go away in the future versions. Using this False value will help us share the component because it specifies that the object should not be shared and each element using this should have its own copy. Normally what happens when we ask for a resource what WPF does is it creates an instance of it and keeps it in handy and when some other objects asks for it WPF provides the same object reference. But as we have turned off the sharing of the resource WPF will not keep the reference of the object in memory and when another object asks for this reference then WPF creates a new one and give it to the object. This won’t work for parsing xaml.

 

WPF Resource System

WPF Resource System

When we run this we will see that we have two buttons using the same resource. It would just work for simple static resources.

Templates

Templates are designed as a mechanism to reuse the trees of object that include user interface elements or behaviors. The event handlers that we define in the templates will be hooked to the object each time it is instantiated. Templates are usually declared as resources and it is a shared object. A template is just a factory and each time we use it creates a new copy and gives it the object. Templates are usually defined to work in a particular context like the ControlTemplate works to define the look of a particular control and a Data Template will work to display the data in the specified format. To use the template in a particular format we need to use the raw template. Let’s see this in action. Add the following code to your WPF window and you will see that we have specified a ControlTemplate in the Window.Resources. We have instantiated the ControlTemplate with the help of the Control Base class. As Control is the base for all Controls and it introduces the concept of Templates in the Controls and it’s not abstract. A control does not have any intrinsic behavior so we have used it as a way to host the template. It is not the perfect way of using the template though. As you can see we have specified that the controls should not act as a focus control. If we do not turn off the Focusable then each of these controls will have focus twice.

 

WPF Templates

WPF Templates

Another way of fixing this double focus issue is by using a DataTemplate and a ContentPresenter. Add the code as shown below to a Winow xaml.

 

WPF Templates

WPF Templates

The best place to use data templates is when the situation demands it such as DataBinding or ControlCustomization.

Xaml File Reuse

We can also reuse the xaml by separating it out to a different file and we can use Application.LoadComponent to load the compiled xaml resources. The Application.LoadComponent creates a new object by reparsing the xaml or baml every time it is called. After loading the component we need to add it to the UI tree. The root of the xaml file can be any type and we can package any sub tree this way.

We can also use a Frame or a NavigationWindow to load the xaml. It’s really straight forward to load   the xaml this way. All we need to do is point Frame directly to xaml file in the project and it will load a copy of the page. We can only point these to a xaml file whose root is a Page.

Xaml with Code Behind

In our application we can combine any xaml file with a code behind file. All we need to do is to provide the x:Class attribute which will define the code-behind class and we can use any WPF type as the root.

Let’s see this in action. Let’s create a reusable Grid with some behavior. Add the following xaml to a Page.

 

WPF Xaml + Code Behind

WPF Xaml + Code Behind

Now add the following code to the code behind of the GridComponent.

 

WPF Xaml + Code Behind

WPF Xaml + Code Behind

Now let’s try use this GridComponent which has a Button Click behavior,

 

WPF Xaml + Code Behind

WPF Xaml + Code Behind

Anytime we are defining classes with code behind, we are just creating partial classes and we can instantiate them with C# new operator or we can use xaml as shown above.

UserControl

UserControl is name of the reusable UI component which is built from other components. As we saw in the last section that we derived from a Grid to create a custom control, generally in WPF we derive from UserControl. You should know that UserControl does not do a lot. We can take an approach similar to user control with any UI element. The main reason of the existence of UserControl is to have a recognizable base class. The only problem with using Grid as the base class instead of the UserControl is that the .NET programmers will expect the Grid base class to provide the same functionality a Grid does and that might be really messy because the Grid will break the rule that the derived class provides the same functionality as the base class. As a matter of fact the UserControl also breaks the rule of derived class. The UserControl derives form Control but still it does not provide the functionality for a Template because there is no way to hook up the look of the UserControl with the code behind. User Controls are not lookless controls. UserControl is derived from ContentControl. It does that because whatever control you put inside UserControl xaml it displays that. This means if we set the content of the UserControl then the complete content of the UserControl will be replaced. So we should not try to set its template or Content.

Limitation – A class only gets to use xaml once in its inheritance chain. Although we can inherit from UserControl, we cannot use the xaml because the UserControl base class has already used it and same is true if the class derives from Window or Page. A class gets to use xaml with code behind only once and the base class has already done that then we cannot do it again. To understand this you will need to understand how Application.LoadComponent works. The way it hooks up xaml with code-behind is incompatible with inheritance. There are two things that need to be hooked up at xaml loading time.

  • Named elements must be placed in their corresponding fields.
  • Event handlers that specified its attributes must be attached.

Application.LoadComponent does this by using the interface IComponentConnector. The code behind class implements this. So when the derived class tries to implement the IComponentConnector then its implementation replaces the one that was done by the base class.

Runtime XAML Parsing

We can generate and parse xaml at runtime. For example we have an application that returns xml documents then we might have to run these through xslt which will generate the xaml and we would parse and run this xaml. This parsing can be done through the xaml reader class. We can do this by passing the xaml string or by passing xamlReader object and it will return an object with the root as the xaml tree and that tree will be defined as the xaml passed. The restriction in this mode is that events will work. Also we will need to call the FrameworkElement.FindNamed helper method to find any named components.

Custom Elements

Here are a few base classes that do not involve xaml:

  • Decorator – It is the base class for all the utility elements that contain a single child and provides some sort of service. The exact functionality of the decorators varies from a decorator to decorator. The border decorator provides the border around the child element whereas a viewbox applies a scale transform on the child so that the child takes all the space. A decorator and content control both host a child. The difference between them is that a Content Contol has some interactive behavior whereas a decorator does not.
  • Adorner – Adorners are visual elements that are attached to some other elements that always appear on top. As we can see in the image below that one of the grids is selected and all the resize, rotate, etc options available are all adorners. These adorners will be visible even when the object’s z-index in low and its not visible on the screen.

 

WPF Adorner

WPF Adorner

But an adorner decorator is the one which decides the bound of the adorner. So even when the element is sent back then also adorner is visible so you would think that an adorner is designed to be on the top at all times but when a panel comes above it as shown in the image below the adorner does not come above it. This is done with the help of an adorner decorator in the visual tree. S here an adorner decorator is defined as the parent of the design surface so that no adorner comes above it.

 

WPF Adorner

WPF Adorner

  • Panel – Panel is the base class for the WPF layout strategy type i.e. Grid, StackPanel,etc. If we want to write a custom layout we need to derive from Panel.
  • Shape – This is the base class to use when building any custom shape like a rectangle or ellipse. It provides Fill, Geometry cache, etc properties.
  • FrameworkElement – This is the base class to use when we are building a Code based user interface element and there is no other base class that suits our needs.

Custom Controls – This used when our UI elements has some distinctive behavior which is not provided by the controls available.

API Considerations

While building a custom control or a custom feature we need to consider the following things.

  • Properties – These are useful for components that are reusable from xaml as this is the API that does not require code and the functionality of the control will be accessible simply by setting properties. To use properties we need to use the WPF’s dependency property system so that the property supports Triggers, Animation, styling, data binding, notifications, etc.  So there might be properties that do not do a lot in code but are controlled by the templates.

 

  • Events – These allow code to be run when things happen in your code. These also allow triggers to be hooked up into xaml. Defining a custom event is pretty similar to defining a property. We need to register the event and store in a public static read only field.

 

We provide event accessor. The AddHandler and RemoveHandler methods are provided by the FrameworkElement base class.

 

We need to raise the event by creating a RoutedEventArgs and passing it into the FrameworkElement ReiseEvent method. RaiseEvent requires the event to be of type RoutedEventsArgs or derived from that.

 

We can add a class level event handler for the built in events.

  • Commands– These are really useful in controlling the behavior of the elements as any operations that can invoked by commands can be driven by xaml. We can hook up controls like menu item or a button with commands. Commands are really straight forward. We just need to create a Command of type RoutedUICommand and provide a name, display name and owning type. Commands are made available as public static read only fields.

 

In order for a command to do something we need to register a command Binding. As we want each and every instance of the class to handle the command we should use a class level command binding instead of the instance level binding as the class level binding is slightly more efficient. To register the command we need to pass the static command name and command owner details to the CommandManager.RegiterClass method.

 

  • Control Template – While writing a custom control we need to decide how its template will work. We should define the control by defining the idioms for the control liked the named parts. This tells what the control expects from its template. We can list parts we require by specifying the TemplatePart attribute. We need to declare the name of the part and the type it should be compatible with. Let’s see  this in action. Add a custom control to the project as shown in the image below.

 

WPF Custom Control

WPF Custom Control

Now let’s use the NamedParts idioms to specify what we want in the template.

 

WPF Custom Control

WPF Custom Control

The most important thing to do while working with templates is to override the OnApplyTemplate method as WPF will call this each time the control is given the template and this might happen multiple times. The first thing that we do here is check if the tenplate is not null and then we proceed to find the named part in this particular control. Then we cast it to the types of Shape. If the template named part is not null then we hook up a mouse down event handler and in this event handler we set the target fill property to green.

 

WPF Custom Control

WPF Custom Control

Now, Let’s use this custom control in our application

 

WPF Custom Control

WPF Custom Control

On MouseDown event the fill changes to green. So our handler works for the templates.

Themes

Most controls would want to have a default template. We generally do this by providing the compiled xaml resource by putting them in the themes folder. We should also provide generic.xaml  to provide the resources that might be needed when the resources specified by the user are not available. Also the assembly must declare the presence of these themes by using the Assembly Info attribute. This tells whether theme resources are present and whether generic resources are present. As we can see in the image below WPF added this information when we added the custom control.

 

WPF Themes

WPF Themes

WPF also added a Generic.xaml when we added the custom control and this contains a style for the custom control that we added.

 

WPF Themes

WPF Themes

Let’s add one more instance of the custom control without overriding the template and we will see something like below. The functionality of both of them is same but the appearance is different.

 

WPF Themes

WPF Themes

Designer Integration

We can integrate with design environments such as Visual Studio or Expression Blend. We can move all the general purpose design time code to a separate dll and we can follow the naming convention by adding .Design at the end of the class name. Or we can place the designer specific code in the dll with extensions .Expression.Design or .VisualStudio.Design in the end and if we install these dll so that they appear in the add reference panel then the designers weill be able to locate them.

We can put design time components in the toolbox for any design time environment. Visual Studio provide option to specify the Custom adorners or items in context menu or view or modify Object tree or property grid.

If we want use the design time dlls then the design time dll must include a implementation for IRegisterMetadata.

 

Any questions, comments or feedback are most welcome.

The code for this post can be found here.

1 comment

  1. Thanks for this great post!
    It’s rarely to see detailed and carefully-explanation wpf tutorial like this one.

Leave a Reply

Your email address will not be published. Required fields are marked *