Code Location
The code for this blog post can be downloaded from NonWPFDataTemplatesTests.zip.
Introduction
Here, I continue a series of blog posts regarding implementing WPF concepts outside of WPF.
WPF is built around a hierarchy of visual objects, the so called Visual Tree. Any WPF control can provide an example of such hierarchy - e.g. a WPF window might contain a grid panel which in turn contains a number of visual objects, e.g., buttons and list boxes which in turn contain some text, etc.
The object hierarchy is usually built in XAML with the help of numerous techniques such as bindings, Control Templates and Data Templates. Data Templates with the help of bindings allow to translate some non-visual objects into visual objects with the hierarchy defined by XAML. These resulting visual objects usually mimic the behaviors of the original non-visual objects – e.g. Changing a property on a non-visual object would result in the corresponding visual property change, adding or removing an object to or from an observable collection would result in a corresponding item being added or removed from e.g. a ListView
.
This blog entry will show how to use concepts similar to WPF Data Templates outside of WPF and apply them for creating in XAML purely non-visual objects.
I’d like to stipulate that the utility of the concepts introduced here might not be quite obvious yet. The usage of non-visual data templates will be explained in the future blog posts.
Also, I would like to express gratitude to the WPF re-implementation attempt called Avalonia. It would have taken me much longer to understand the inner workings of XAML functionality without this project and also I am using a modified version of several of its classes: AvalonXamlReader
, ContentLoader
and TemplateContent
in order to create the non-visual Data Templates.
Data Hierarchy Code Description
Data Hierarchy
First of all, let me describe the non-visual hierarchy that we want to build.
The WPF’s visual hierarchy is based on the natural containment of visual objects. As was stated above, a window might contain a panel – a panel further might contain other panels and controls, etc.
The non-visual hierarchy that we deal with is Property Map and the classes that represent that hierarchy are contained under NP.Paradigms.DataHierarchy
project. In a sense, it mimics the data hierarchy within JSON code: there are Fields, Property Maps, and Collections.
A Field would only contain a value of type object.
A Property Map corresponds to a C# object - it has a number of named properties, each has a value. The values can be plain Fields, other Property Maps or Collections. The name of a property is unique within the map object. I implemented such maps as a Dictionary
with string
keys corresponding to the name of the property and values – the property value. In JSON, such object would be shown within curly brackets:
{ name1 : value1, name2 : value2 }
Collection is a collection of objects (usually of similar structure) that can be either Property Maps or Fields. In JSON, collections are denoted by square brackets:
[ value1, value2 ]
Clearly, our Data Hierarchy can be used to represent a very wide range of models – in particular, anything that can be represented by JSON or C# classes and collections can be represented by the Data Hierarchy.
Classes Describing the Data Hierarchy
As was mentioned above, all the classes describing our Data Hierarchy model are located under NP.Paradigms.DataHierarchy
project.
All the Data Hierarchy classes implement IPresentationTreeNode
interface. This interface has Parent
property that points from the current object to its parent within the Data Hierarchy. IPresentationTreeNode
also extends IDataContextContainer
interface that defines DataContext
property that all the members of the hierarchy should implement. Just like in WPF, the DataContext
property contains the data object from which the member of the hierarchy gets its values via binding.
There is also IPresentationTreeNodeWithChildren<T>
interface that extends IDataContextContainer
and in addition also defines a collection Children
.
FieldPresenter
class corresponds to a single value Data Hierarchy node. Its property TheValue
contains the corresponding value. Its subclass TextFieldPresenter
also allows formatting the values for text presentation.
CollectionPresenter
defines a collection within Data Hierarchy by its property Children
.
PropertyMapPresenter
represents the Property Map nodes within Data Hierarchy. It implements IDictionary
so that one can use the indexer notations (square brackets) to define the name-value mappings, e.g.: myMap["Name"] = value
Note that classes that implement Data Hierarchy nodes fire PropertyChanged
events when their properties change. Also Children
property of the CollectionPresenter
is implemented as ObservableCollection
and correspondingly fired CollectionChanged
event when the items are changed within the collection (removed, added or moved). This is necessary so that the bindings that have the Data Hierarchy nodes as source would work.
Classes for XAML Composition using Data Templates
Classes used for XAML composition are also defined under NP.Paradigms.DataHierarchy
project.
DHierarchyContentControl
and DHierarchyItemsControl
correspond to the WPF’s ContentControl
and ItemsControl
classes.
DHierarchyContentControl
defines properties Content
and ContentTemplate
. ContentTemplate
defines the sub-hierarchy of the node while Content
property allows to switch the data context of its sub-nodes.
DHierarchyItemsControl
defines properties ItemsSource
and ItemTemplate
. ItemsSource
should be bound to a collection of objects. ItemTemplate
specified the structure of each of the sub-nodes within DHierarchyItemsControl
‘s Children
collection. The resulting Children
collection behaves similar to the ItemsSource
collection if an item is added or removed from ItemsSource
the corresponding item will be added or removed from the Children
collection.
There are a number of classes for reading and processing XAML data templates. Class DHierarchyDataTemplate
is used for storing the Data Hierarchy data templates within DHierarchyContentControl
and DHierarchyItemsControl
. DHierarchyTemplateContent
together with DHierarchyContentLoader
and AvalonXamlReader
control loading the data templates from XAML.
Usage Code Description
Here, we describe how the Data Hierarchy code with data template capabilities can be used.
The mock up data is contained under MockUpObjects
. Here, we consider only two classes: Organization
and Person
. Both are very simple Organization
contains its name and a collection of people. Person
simply contains first and last name of the person.
The main project in NonVisualDataTemplates
. It defines an organization as its resource:
<MockUps:Organization x:Key="TheWonkaChocolateFactoryOrganization" />
Around the organization object, it defines the Data Hierarchy data template. While on top of the Data Hierarchy data template, it defines a visual representation (just in order to be able to see that it works).
Data Hierarchy representation is defined by the following templates:
<hierarchy:DHierarchyDataTemplate x:Key="EmployeeNonVisualDataTemplate">
<hierarchy:PropertyMapPresenter>
<hierarchy:FieldPresenter x:Key="FirstName"
TheValue="{winparadigms:Bind SourcePath=FirstName,
SourceMode=DataContext}"/>
<hierarchy:FieldPresenter x:Key="LastName"
TheValue="{winparadigms:Bind SourcePath=LastName,
SourceMode=DataContext}"/>
</hierarchy:PropertyMapPresenter>
</hierarchy:DHierarchyDataTemplate>
This template define the Data Hierarchy representation of a person (employee
):
<hierarchy:DHierarchyDataTemplate x:Key="TheOrganizationTemplate">
<hierarchy:PropertyMapPresenter>
<hierarchy:FieldPresenter x:Key="OrgName"
TheValue="{winparadigms:Bind SourcePath=OrgName,
SourceMode=DataContext}"/>
<hierarchy:DHierarchyItemsControl x:Key="People"
ItemsSource="{winparadigms:Bind SourcePath=People,
SourceMode=DataContext}"
ItemTemplate="{StaticResource EmployeeNonVisualDataTemplate}"/>
</hierarchy:PropertyMapPresenter>
</hierarchy:DHierarchyDataTemplate>
This template defines the whole organization with multiple employees.
DHierarchyContentControl
‘marries’ the organization template and the organization data:
<hierarchy:DHierarchyContentControl ContentTemplate="{StaticResource TheOrganizationTemplate}"
Content="{StaticResource TheWonkaChocolateFactoryOrganization}"
x:Key="TheDHierarchyContentControl"/>
In order to visualize the results, a plain visual WPF hierarchy is built around the DHierarchyContentControl
:
<Grid Grid.Row="0"
DataContext="{winparadigms:Bind Source={StaticResource TheDHierarchyContentControl}}">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<TextBlock Text="{winparadigms:Bind SourcePath=Child.-OrgName-.TheValue, SourceMode=DataContext}"
Margin="10,0,0,0"/>
<Border Grid.Row="1"
BorderBrush="Black"
BorderThickness="2"
Margin="5">
<ItemsControl ItemsSource="
{winparadigms:Bind SourcePath=Child.-People-.Children, SourceMode=DataContext}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<TextBlock Text="
{winparadigms:Bind SourcePath=Child.-FirstName-.TheValue, SourceMode=DataContext}" />
<TextBlock Text="
{winparadigms:Bind SourcePath=Child.-LastName-.TheValue, SourceMode=DataContext}"
Grid.Column="1"
Margin="20,0,0,0"/>
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Border>
</Grid>
Note that we are using Bind
markup extension instead of Binding
. This extension has been described in some of the previous blog entries.
Also note that ‘-
’ character around a path link specifies that this path link is actually a key inside a Dictionary
for the Bind
markup extension. Also SourceMode=DataContext
means that we are binding to the DataContext
property (by default Bind
extension binds to the object on which it is defined, not to its DataContext
).
There are two buttons at the bottom of the application that allow to modify the original Organization
object. The very simple for modifying the original Organization
is located in MainWindow.xaml.cs code behind file. You can add an employee
or you can replace its People
collection with another collection. These changes will cause changes in Data Hierarchy representation and those in turn will cause the visual representation to change.
After running the application (and pressing a button to add an employee
), you’ll see the following picture:
Conclusion
We’ve shown implementing XAML data templates for non-WPF (and even non-Visual) objects.