Save Key/Value Pairs in a File



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

By Sacha Barber | 26 Sep 2008 Part of The WPF Silverlight Zone. [pic]

How to create Styles in code/and magical Content.

Top of Form

• Download source - 29 KB

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# | ), 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:

• Content Weirdness

• Creating Styles/Templates in Code

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:

[pic] Collapse

We can see that this produces the following screenshot:

[pic]

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:

[pic]

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

[pic]

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:

[pic]

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 usesIAddChild behind the scenes, as we can see below:

[pic]

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

[pic]

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 aContent 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.

[pic]

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:

[pic] Collapse

And here is the related C# code:

[pic] Collapse

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 ponentModel;

using System.Windows.Markup;

using System.Collections.ObjectModel;

namespace WPF_Mysteries

{

///

/// 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.

///

[ContentProperty("SomeContent")]

public partial class ContentTestControl : UserControl

{

#region Public Properties

[DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]

public ObservableCollection SomeContent { get; set; }

#endregion

#region Ctor

public ContentTestControl()

{

InitializeComponent();

SomeContent = new ObservableCollection();

// 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 theContentTestControl.SomeContent property. The XAML parser knows what to do with these four controls, they are simply added to theContentTestControl.SomeContent property (which is after all a ObservableCollection, so should support Adds; it's a collection, is it not?).

[pic] Collapse

one

two

3

4

5

6

7

8

Which results in the following screenshot. It should, however, be noted that since my ContentTestControl UserControl inherits fromUserControl, 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. ButContentTestControl.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.

[pic]

It can be seen from this screenshot that the Binding that was set up in ContentTestControl (where ContentTestControl.xaml has the Bindingset as follows: {Binding SomeContent.Count}) shows a result of 4. This is interesting since ContentTestControl didn't use the IAddChildinterface 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:

[pic] Collapse

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

[pic] Collapse

//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:

[pic] Collapse

....

....

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.

[pic] Collapse

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:

[pic] Collapse

Where a Person object looks like the following code:

[pic] Collapse

using System;

using System.Collections.Generic;

using System.Linq;

using System.Text;

namespace WPF_Mysteries

{

///

/// A simple POCO, used for Binding in Window1

///

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 DataTemplateobject:

[pic] Collapse

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

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

[pic]

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:

[pic]

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

[pic]

Where the VisualTree property looks like this:

[pic]

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 ofFrameworkTemplate, 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 requiredDataTemplates VisualTree. In my example, this would be as follows:

[pic] Collapse

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:

[pic] Collapse

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:

[pic] Collapse

//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:

[pic]

It is more code when compared to the XAML.

Bottom of Form

................
................

In order to avoid copyright disputes, this page is only a partial summary.

Google Online Preview   Download