Click here to Skip to main content
Click here to Skip to main content

WPF: How to create Styles in code/and magical Content

, 26 Sep 2008 CPOL
Rate this:
Please Sign up or sign in to vote.
How to create Styles in code/and magical Content.

Introduction

Over the past year, I have been playing and working with WPF, and have published a few articles (though not as good as Josh Smith's / Karl Shifflett's) on WPF. As such, there have been a few questions/comments/queries posted in the forums attached to these articles. One of the most common questions/puzzlements I see is that people seem to be honestly lost by the differences between doing something in XAML opposed to doing something in code (C# | VB.NET), and how the heck Content works. I recall one of CodeProject's greatest authors, Marc Clifton, being particularly frustrated about Content, and Marc has actually created his own declarative XML markup: MyXaml. When someone like that is getting frustrated, it must be confusing.

This article will attempt to outline several areas that people seem to struggle with the most (based on the questions I have been asked in my own articles).

Contents

Here is what I will be covering in this article:

Prerequisites

In order to get the most out of this article, I would suggest you download the free .NET Disassembler, Reflector, which is freely available using the new RedGate download page. RedGate recently took over development of this product from Lutz Roeder. Thanks Lutz, it is, and always has been, a great product.

Content Weirdness

If we consider the following section of XAML:

<Window x:Class="WPF_Mysteries.Window2"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="Window2" Height="300" Width="300">
    <StackPanel Orientation="Vertical">
        <Button Background="Aqua" Margin="10" Width="Auto" Content="1" />
        <Button Background="Green" Margin="10" Width="Auto" Content="2" />
        <Button Background="Pink" Margin="10" Width="Auto" Content="3" />
    </StackPanel>
</Window>

We can see that this produces the following screenshot:

But how does this work? Is it magic? Well, actually no. If we go and look up a StackPanel in Reflector, and have a look at its definition:

This doesn't tell us too much, but we can see that this inherits from Panel, which in turn looks like the following:

What is very interesting here is an attribute, namely the ContentPropertyAttribute, which here is shown as being Children. Also worth a mention is the interface IAddChild. MSDN states the following: IAddChild provides a means to parse elements which permit child elements or text. The main use of IAddChild is to support FrameworkElementFactory.

For purposes of establishing or defining a content property or content model, IAddChild is obsolete. Apply the ContentPropertyAttribute to a custom class instead.

OK, so Panel has both a ContentPropertyAttribute and implements IAddChild. If we look at the Children property, which is a GET only property, as shown below:

We can see that we can use this to obtain a UIElementCollection which is what is used to add child UIElements to. Panel actually also uses IAddChild behind the scenes, as we can see below:

We can see this is where the real work of adding objects to the internal Children property of Panel happens.

These are the only methods within the IAddChild interface.

That is how some of the standard System.Windows.Controls that support children (such as Grid/StackPanel/Canvas etc.) deal with Content. However, if the control is supposed to have a single piece of Content, you will find that it still implements the IAddChild interface and has a Content property, much the same as described above. This is shown below for the System.Windows.Controls.Label control, which in turn inherits from System.Windows.Controls.ContentControl.

In order to demonstrate ContentPropertyAttribute further, let's soldier on and try and get to the bottom of the magical Content property. I have a small UserControl that I have declared as follows:

<UserControl x:Class="WPF_Mysteries.ContentTestControl"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Height="auto" Width="auto">
    <StackPanel Orientation="Vertical">
        <Label Content="There are currently"/>
        <Label Content="{Binding SomeContent.Count}"/>
        <Label Content="elements, on the SomeContent property"/>
    </StackPanel>
</UserControl>

And here is the related C# code:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.ComponentModel;
using System.Windows.Markup;
using System.Collections.ObjectModel;

namespace WPF_Mysteries
{
    /// <summary>
    /// This demonstrates how to use the ContentPropertyAttribute, which is 
    /// how some of the native Controls such as Grid/Canvas/StackPanel etc etc
    /// know what to do with the content that is added to them in XAML.
    /// Basically the ContentPropertyAttribute, tells the XAML parser, 
    /// what code behind property should be used when the XAML parser 
    /// finds some content.
    /// </summary>
    [ContentProperty("SomeContent")]
    public partial class ContentTestControl : UserControl
    {
        #region Public Properties
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
        public ObservableCollection<UIElement> SomeContent { get; set; }
        #endregion

        #region Ctor
        public ContentTestControl()
        {
            InitializeComponent();
            SomeContent = new ObservableCollection<UIElement>();
            // Allows binding to it's own properties
            this.DataContext = this;
        }
        #endregion
    }
}

I then use this ContentTestControl control within a XAML Window as follows. Notice that I am actually adding four controls here, but I do not tell it what property I am using to add these controls to. This is done via the magic of ContentPropertyAttribute, which is pointing to the ContentTestControl.SomeContent property. The XAML parser knows what to do with these four controls, they are simply added to the ContentTestControl.SomeContent property (which is after all a ObservableCollection<UIElement>, so should support Adds; it's a collection, is it not?).

<local:ContentTestControl x:Name="testControl">
    <StackPanel Orientation="Vertical">
        <Label>one</Label>
        <Label>two</Label>
    </StackPanel>
    <StackPanel Orientation="Vertical">
        <Label>3</Label>
        <Label>4</Label>
    </StackPanel>
    <StackPanel Orientation="Vertical">
        <Label>5</Label>
        <Label>6</Label>
    </StackPanel>
    <StackPanel Orientation="Vertical">
        <Label>7</Label>
        <Label>8</Label>
    </StackPanel>
</local:ContentTestControl>

Which results in the following screenshot. It should, however, be noted that since my ContentTestControl UserControl inherits from UserControl, it is UserControl that actually adds the elements as actual UI elements, which is why we are seeing different UIElements within the screenshot than you might have thought. You may have been expecting to see the four * StackPanels shown above. But ContentTestControl.SomeContent is really just a property that is holding the values specified by ContentPropertyAttribute; it doesn't necessarily do anything with these values. In the standard System.Windows.Controls controls, the Content property probably would affect the UI as well.

It can be seen from this screenshot that the Binding that was set up in ContentTestControl (where ContentTestControl.xaml has the Binding set as follows: {Binding SomeContent.Count}) shows a result of 4. This is interesting since ContentTestControl didn't use the IAddChild interface at all, but still worked just fine; this is all thanks to the ContentPropertyAttribute usage on this class.

Creating Styles/Templates in Code

One thing that seems to crop up over and over again is people really don't know how to create Styles or templates in code. Obviously, XAML is better suited to doing this than code, so why the hell would you want to do this in code anyway? Well, consider a system which is quite dynamic, and may be driven by meta driven objects, where what objects and properties you may be using are not known at design time. I am actually working on a system like this right now, where everything (most screens) are driven by metadata, where the metadata contains crucial information about the objects the metadata is associated with.

The system is quite dynamic, so statically declared Styles or templates just don't cut the mustard, so we have to create them in code. I am not saying this is normal, but I have been asked how to do this enough times to warrant a small rant on the subject. Consider the following section a small rant on how to create Template/Styles in code.

Creating Styles in Code

Let's start with a simple example. Suppose I have the following Style, declared in XAML, which is applied to ListBoxItems:

<ListBox.ItemContainerStyle>
    <!-- And how about a nice simple Style for a ListBoxItem -->
    <Style TargetType="ListBoxItem">
        <Setter Property="TextElement.FontSize" Value="14"/>
        <Style.Triggers>
            <Trigger Property="IsMouseOver" Value="true">
                <Setter Property="Foreground" Value="Black" />
                <Setter Property="Background">
                    <Setter.Value>
                        <LinearGradientBrush StartPoint="0,0" EndPoint="0,1">

                            <LinearGradientBrush.GradientStops>
                                <GradientStop Color="#0E4791" Offset="0"/>
                                <GradientStop Color="#468DE2" Offset="1"/>
                            </LinearGradientBrush.GradientStops>
                        </LinearGradientBrush>
                    </Setter.Value>
                </Setter>
                <Setter Property="Cursor" Value="Hand"/>
            </Trigger>
        </Style.Triggers>
    </Style>
</ListBox.ItemContainerStyle>

I think this is pretty self-explanatory. But how about the code equivalent?

//And here is the C# code to achieve the above
Style styleListBoxItem = new Style(typeof(ListBoxItem));
styleListBoxItem.Setters.Add(new Setter
{
    Property=TextElement.FontSizeProperty,
    Value=14.0
});

//Trigger
Trigger triggerIsMouseOver  = 
    new Trigger { Property=ListBoxItem.IsMouseOverProperty, Value=true };

triggerIsMouseOver.Setters.Add(
  new Setter(ListBoxItem.ForegroundProperty, Brushes.Black));

GradientStopCollection gradientStopsLinearBrush = new GradientStopCollection();
gradientStopsLinearBrush.Add(
    new GradientStop((Color)ColorConverter.ConvertFromString("#0E4791"), 0.0));
gradientStopsLinearBrush.Add(
    new GradientStop((Color)ColorConverter.ConvertFromString("#468DE2"), 1.0));

LinearGradientBrush backgroundLinearBrush = 
    new LinearGradientBrush(gradientStopsLinearBrush)
{
    StartPoint = new Point(0,0),
    EndPoint = new Point(0,1)
};

//Trigger setters
triggerIsMouseOver.Setters.Add(
    new Setter(ListBoxItem.BackgroundProperty, backgroundLinearBrush));
triggerIsMouseOver.Setters.Add(
    new Setter(ListBoxItem.CursorProperty, Cursors.Hand));
styleListBoxItem.Triggers.Add(triggerIsMouseOver);

As you can see, for Styles, there is pretty much a 1 to 1 mapping to the XAML code, it's not that bad really. The only thing worth a mention is the part where we have a XAML Setter, which looks like the following:

<ListBox.ItemContainerStyle>
    <!-- And how about a nice simple Style for a ListBoxItem -->
    <Style TargetType="ListBoxItem">
    ....
                <Setter Property="Cursor" Value="Hand"/>
    ....
    </Style>
</ListBox.ItemContainerStyle>

This may not have been obvious to you, but the Cursor property here actually refers to the ListBoxItem, so when we do this in code, we must make sure to use the ListBoxItem.Cursor Dependency Property. This is the case with all Setter properties, unless they state another fully qualified property such as TextElement.FotSize.

triggerIsMouseOver.Setters.Add(
    new Setter(ListBoxItem.CursorProperty, Cursors.Hand));
styleListBoxItem.Triggers.Add(triggerIsMouseOver);

Other than that, I think Styles are pretty easy to do in code.

Creating Templates in Code

Unfortunately, Templates are slightly harder to create in code than Styles. But they are still achievable. Let's consider the following DataTemplate that I have declared in XAML to represent a simple Person object:

<!-- Lets have a DataTemplate for a Person-->
<DataTemplate DataType="{x:Type local:Person}">

    <StackPanel x:Name="spOuter" 
            Orientation="Horizontal" Margin="10">
        <Path Name="pathSelected" Fill="Orange" 
            Stretch="Fill" Stroke="Orange" Width="15" 
            Height="20" Data="M0,0 L 0,10 L 5,5" 
            Visibility="Hidden"/>

        <StackPanel x:Name="spInner" Orientation="Horizontal">
            <Label Content="{Binding FirstName}" Foreground="Black"/>
            <Ellipse Fill="Black" Height="5" Width="5" 
                     HorizontalAlignment="Center" 
                     VerticalAlignment="Center"/>
            <Label Content="{Binding LastName}" Foreground="Black"/>
        </StackPanel>
        
    </StackPanel>
    
    <DataTemplate.Triggers>
        <DataTrigger 
               Binding="{Binding RelativeSource={RelativeSource Mode=FindAncestor, 
                        AncestorType={x:Type ListBoxItem}, AncestorLevel=1}, 
                        Path=IsSelected}" 
               Value="True">
            <Setter TargetName="pathSelected" 
                Property="Visibility" Value="Visible"  />
        </DataTrigger>
    </DataTemplate.Triggers>
</DataTemplate>

Where a Person object looks like the following code:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace WPF_Mysteries
{
    /// <summary>
    /// A simple POCO, used for Binding in Window1
    /// </summary>
    public class Person
    {
        public string FirstName { get; set; }
        public string LastName { get; set; }
    }
}

Based on what we know about WPF/XAML and what we have just seen about Styles, you would think that there was a 1 to 1 mapping for DataTemplates as well. But there isn't.

I think the best way to dissect this DataTemplate is piece by piece. So let's grab a section of it at a time, and then at the end, I'll show the whole code section for the code created DataTemplate that does the same as the XAML version. Let's start with the actual creation of a DataTemplate object:

DataTemplate dataTemplate = new DataTemplate(typeof(Person));

That's pretty easy, right? How about getting something in the DataTemplate? Mmm, well, here is the code:

Is this what you were expecting? Probably not. So what the heck is going on here? Let's start at the end to understand the beginning. We can see there is a VisualTree being set to a strange looking object of Type FrameworkElementFactory. What the @$*^!!!

To understand this, let's use our favourite tool, Reflector, and get to the bottom of this. If we start with the DataTemplate:

Mmmm, no VisualTree property here (even though we can see that it depends on the VisualTree property), but we can see this inherits from FrameworkTemplate, so let's continue to look at that.

Where the VisualTree property looks like this:

Aha, it's becoming a little clearer. So we have a VisualTree property that we need to supply a FrameworkElementFactory to. Cool. But what are these FrameworkElementFactory things? This class is a deprecated way to programmatically create templates, which are subclasses of FrameworkTemplate, such as ControlTemplate or DataTemplate. MSDN actually states "The recommended way to programmatically create a template is to load XAML from a string or a memory stream using the Load method of the XamlReader class." But when you have to do it in code, this is your only option.

So carrying on, it's really just a question of creating as many of these FrameworkElementFactory objects that you need to represent your required DataTemplates VisualTree. In my example, this would be as follows:

<StackPanel x:Name="spOuter">
    <Path Name="pathSelected"/>
    <StackPanel x:Name="spInner" >
        <Label />
        <Ellipse />
        <Label />
    </StackPanel>
</StackPanel>

For which there is a a bunch of code-behind created FrameworkElementFactory objects, and they are wired up as required with all the relevant properties/relationships set in code. Finally, the top level FrameworkElementFactory is set as the DataTemplates VisualTree property. So that's how the VisualTree stuff works. But what about Triggers?

Luckily, Triggers are pretty easy to do in code. Here is how:

DataTrigger dataTrigger = new DataTrigger();
dataTrigger.Binding = new Binding {
    Path = new PropertyPath(ListBoxItem.IsSelectedProperty),
    RelativeSource = 
        new RelativeSource(RelativeSourceMode.FindAncestor, 
        typeof(ListBoxItem), 1)
};
dataTrigger.Value = true;
dataTrigger.Setters.Add(
    new Setter(FrameworkElement.VisibilityProperty, 
        Visibility.Visible, "pathSelected"));

dataTemplate.Triggers.Add(dataTrigger);

Putting all this together, we end up with a code-behind DataTemplate that looks like this:

//And here is the C# code to achieve the above
DataTemplate dataTemplate = new DataTemplate(typeof(Person));

FrameworkElementFactory spOuterFactory = 
    new FrameworkElementFactory(typeof(StackPanel));
spOuterFactory.SetValue(
    StackPanel.OrientationProperty, Orientation.Horizontal);
spOuterFactory.SetValue(
    StackPanel.MarginProperty, new Thickness(10));

#region Path
FrameworkElementFactory pathSelectedFactory = 
    new FrameworkElementFactory(typeof(Path), "pathSelected");

pathSelectedFactory.SetValue(Path.FillProperty, Brushes.Orange);
pathSelectedFactory.SetValue(Path.StretchProperty, Stretch.Fill); 
pathSelectedFactory.SetValue(Path.StrokeProperty, Brushes.Orange);
pathSelectedFactory.SetValue(Path.WidthProperty, 15.0);
pathSelectedFactory.SetValue(Path.HeightProperty, 20.0);
pathSelectedFactory.SetValue(Path.VisibilityProperty, Visibility.Hidden);

PathGeometry pathGeometry = new PathGeometry();
pathGeometry.Figures = new PathFigureCollection();
PathFigure pathFigure = new PathFigure();
pathFigure.StartPoint = new Point(0, 0);
pathFigure.Segments = new PathSegmentCollection();
pathFigure.Segments.Add(new LineSegment() { Point = new Point(0, 10) });
pathFigure.Segments.Add(new LineSegment() { Point = new Point(5, 5) });
pathGeometry.Figures.Add(pathFigure);


pathSelectedFactory.SetValue(Path.DataProperty, pathGeometry);
spOuterFactory.AppendChild(pathSelectedFactory);
#endregion

#region Inner StackPanel
FrameworkElementFactory spInnerFactory = 
         new FrameworkElementFactory(typeof(StackPanel));
spInnerFactory.SetValue(StackPanel.OrientationProperty, 
                        Orientation.Horizontal);

//FirstName
FrameworkElementFactory labelFNFactory = 
             new FrameworkElementFactory(typeof(Label));
Binding bindingFirstName = new Binding();
bindingFirstName.Path = new PropertyPath("FirstName");
labelFNFactory.SetBinding(Label.ContentProperty, bindingFirstName);
labelFNFactory.SetValue(Label.ForegroundProperty, Brushes.Black);
spInnerFactory.AppendChild(labelFNFactory);


//Ellipse
FrameworkElementFactory ellipseFactory = 
         new FrameworkElementFactory(typeof(Ellipse));
ellipseFactory.SetValue(Ellipse.FillProperty, Brushes.Black);
ellipseFactory.SetValue(Ellipse.HeightProperty, 5.0);
ellipseFactory.SetValue(Ellipse.WidthProperty, 5.0);
ellipseFactory.SetValue(Ellipse.HorizontalAlignmentProperty, 
                        HorizontalAlignment.Center);
ellipseFactory.SetValue(Ellipse.VerticalAlignmentProperty, 
                        VerticalAlignment.Center);
spInnerFactory.AppendChild(ellipseFactory);


//LastName
FrameworkElementFactory labelLNFactory = 
                new FrameworkElementFactory(typeof(Label));
Binding bindingLastName = new Binding();
bindingLastName.Path = new PropertyPath("LastName");
labelLNFactory.SetBinding(Label.ContentProperty, bindingLastName);
labelLNFactory.SetValue(Label.ForegroundProperty, Brushes.Black);
spInnerFactory.AppendChild(labelLNFactory);

//Add to outer StackPanel
spOuterFactory.AppendChild(spInnerFactory);
#endregion

#region DataTrigger
DataTrigger dataTrigger = new DataTrigger();
dataTrigger.Binding = new Binding {
    Path = new PropertyPath(ListBoxItem.IsSelectedProperty),
    RelativeSource = 
        new RelativeSource(RelativeSourceMode.FindAncestor, 
        typeof(ListBoxItem), 1)
};
dataTrigger.Value = true;
dataTrigger.Setters.Add(
    new Setter(FrameworkElement.VisibilityProperty, 
        Visibility.Visible, "pathSelected"));

dataTemplate.Triggers.Add(dataTrigger);
#endregion

dataTemplate.VisualTree = spOuterFactory;
return dataTemplate;

When I run the attached app, the code-behind version does exactly the same as the XAML defined version:

It is more code when compared to the XAML.

That's it

That's all I wanted to say this time, I hope it helps some of you. Could I just ask, if you liked this article, please vote for it.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Sacha Barber
Software Developer (Senior)
United Kingdom United Kingdom
I currently hold the following qualifications (amongst others, I also studied Music Technology and Electronics, for my sins)
 
- MSc (Passed with distinctions), in Information Technology for E-Commerce
- BSc Hons (1st class) in Computer Science & Artificial Intelligence
 
Both of these at Sussex University UK.
 
Award(s)

I am lucky enough to have won a few awards for Zany Crazy code articles over the years

  • Microsoft C# MVP 2014
  • Codeproject MVP 2014
  • Microsoft C# MVP 2013
  • Codeproject MVP 2013
  • Microsoft C# MVP 2012
  • Codeproject MVP 2012
  • Microsoft C# MVP 2011
  • Codeproject MVP 2011
  • Microsoft C# MVP 2010
  • Codeproject MVP 2010
  • Microsoft C# MVP 2009
  • Codeproject MVP 2009
  • Microsoft C# MVP 2008
  • Codeproject MVP 2008
  • And numerous codeproject awards which you can see over at my blog

Comments and Discussions

 
GeneralNice Explanation PinmemberCoder.8622-May-13 12:59 
QuestionI love you man!!! Pinmemberlimbejeff2-Oct-12 5:18 
AnswerRe: I love you man!!! PinmvpSacha Barber2-Oct-12 5:41 
Generalnice! Pinmemberkwindly18-Mar-12 0:06 
GeneralRe: nice! PinmvpSacha Barber18-Mar-12 10:10 
GeneralMy vote of 5 PinmemberPerry Bruins14-Apr-11 19:49 
GeneralMy vote of 5 PinmemberMigdalin30-Dec-10 3:49 
GeneralMy vote of 5 Pinmemberblahrtdfiuh2-Aug-10 1:44 
GeneralImplicit style in code PinmemberUninformed24-Jul-10 7:42 
GeneralRe: Implicit style in code PinmvpSacha Barber25-Jul-10 0:18 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Terms of Use | Mobile
Web04 | 2.8.141223.1 | Last Updated 26 Sep 2008
Article Copyright 2008 by Sacha Barber
Everything else Copyright © CodeProject, 1999-2014
Layout: fixed | fluid