Why WPF Rock... (The content model)
For the last week I was back in WinForms land... There is nothing making you appreciate the new stuff in WPF more than living without it for a week!!!
Let me start by giving you some background... I had to make a fairly simple data logger for a client... the only problem is that I only had WinForms to use... I constantly found myself wondering why the hell thy don't have a content model!!!
So, what is this content model and why should I care? In WinForms you have a button... very simple control with a specific purpose... Allow people to press it and perform a action based on this click! As you would expect, the button has a text property. This represents the text on the buttons face... Ok, now I need a picture inside the button... Ok, so lets add a image property... That will work... but what if I need to add a circle to this button... or even better, another button (To create a split button)... now the wheels start coming off!!! Yes, some of this is possible in WinForms, but it just feels like a afterthought! WPF is designed with this type of scenarios in mind! This is were the content model start coming in...
Any control derived from ContentControl has a property called Content. Content is of the type object which implies that the content can be of any type... This is huge... You can potentially place anything inside a ContentControl! So, lets look at a few examples, lets create a button
This just creates a very basic button, lets start slow:
<Button Content="Hallo World!" />
|
As expected, this creates a button with a string inside "Hallo World!". We will come back to this example because their are more underneath the surface... Have a look at the following button
<Button>
Hallo World
</Button>
|
Ok, so this creates exactly the same results as the previous button! What can we deduct from this? We added the content here as if it was the child of the button... how does it know that it is content and not a child? The button's source code possible looks something like this:
[ContentProperty("Content")]
public class Button : ButtonBase
{
...
}
|
Ok, so what's next... Let's try adding a visual element as my content
<Button>
<Ellipse Width="20" Height="20" Fill="Black" />
</Button>
|
This is nice... now I can add shapes to my button... or can I? We have now hit one of the "limitations" of ContentControl... Content can only be a single object! This is easy to bypass... just add a panel and you object as children. Lets try adding 2 circles
<Button>
<StackPanel>
<Ellipse Width="20" Height="20" Fill="Black" />
<Ellipse Width="20" Height="20" Fill="Black" />
</StackPanel>
</Button>
|
So now we can add endless objects inside a ContentControl... (Remember that this is not "free" and their is a performance hit if you add to many controls). Lets look at the string example again. We added a object of type string as my content... how did it know how to display it? We will look at the rules of how this gets resolved in more detail later... but for now, all I will say is that if it is not a visual element, then it calls the ToString() of this object to render it!
Lets make it a little more complicated, here is my button
<Button x:Name="MyButton" />
|
All I did is give my button a name... the next step will happen in the code behind... I created a very simple Person class
public class Person
{
public string Name { get; set; }
public string Surname { get; set; }
}
|
And then add the following in the Loaded event
Person me = new Person() { Name = "Rudi", Surname = "Grobler" };
MyButton.Content = me;
|
What will be displayed now? Same rules still apply... ToString() returned WpfApplication1.Person. Ok, lets override the ToString()
public override string ToString()
{
return Name + " " + Surname;
}
|
Now my button shows "Rudi Grobler". Great, so if it is not a visual element, then it uses the ToString() of the object!
For what ever reason, I want a circle in between the name and surname (Try this with WinForms?).
<Button x:Name="MyButton">
<Button.ContentTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Name}" />
<Ellipse Width="15" Height="15" Fill="Black" />
<TextBlock Text="{Binding Surname}" />
</StackPanel>
</DataTemplate>
</Button.ContentTemplate>
</Button>
|
WOW!!!
Lets break this down... Now the ToString() gets ignored, why? DataTemplate!!!
What is a DataTemplate?
A data template is a piece of UI that you'd like to apply to a arbitrary .NET object when it is rendered.
Ok, what have we learned about the ContentControl so far?
1) If the ContentControl has a DataTemplate defined, use it
2) If the content is a visual element (derived from UIElement), render it using UIElement.OnRender()!
3) DataTemplate with matching DataType found (up the visual tree), use it
4) Use the objects ToString()
ContentControl is amazing...
So, what else does a ContentControl have? It has a property called HasContent... Why does it have a property called HasContent, if I can just as easily check by using Content == null?
This is the WPF way... This allows easily checking for content from XAML... Now you can animate based on if a control has content, etc?
What if my Content is a collection of .NET managed objects? Have a look at the ItemsControl... Dr WPF has a excellent series covering the ItemsControl in detail (or has he put it, from A to Z)
ItemsControl - 'A' is for Abundance
ItemsControl - 'B' is for Bet You Can't Find Them All
ItemsControl - 'C' is for Collection
ItemsControl- 'D' is for DataTemplate
ItemsControl- 'P' is for Panel
ItemsControl- 'I' is for Item Container
ItemsControl- 'R' is for Rob has a Customer
If you need to see the power of the content model, read 'R' is for rob has a Customer and check out the split button he created!