Amazing XAML
Peter Himschoot
U2U
Applies to:
- Visual Studio .NET 2005
- .NET 3.0
- Windows
Vista
(Beta 2, June CTP, ...)
- XAML (June CTP)
Summary: .NET 3.0, formerly known as
WinFx, uses XAML for describing and building object graphs. Windows Presentation
Foundation (aka Avalon), for example, uses XAML for building the next generation
user interfaces. In this article we will look at the XAML syntax (which is a lot
more than simply XML) by building a maze sample, such as can be found in old-fashioned
adventure games like Zork, etc... Please note that this article is not about Windows Presentation
Foundation, it is all about XAML and how you can use it for your own applications.
Some examples will use WPF when appropriate, but only to explain some of the concepts.
Why XAML?
Let’s first look at the advantage of using XAML (or simply XML) to describe your
objects. You can create objects directly in code, can’t you? By defining your objects
as XAML, it is a lot easier to build tools that can transform this XAML, for example
custom editors can be built that can edit, change, etc… your objects. The best example
I know is the Microsoft Interactive Designer for building user interfaces. This
tool can be used by designers (yes, people with talent for building nice user interfaces
J) and developers to build
state of the art user interfaces, with animation, data binding and such, without
writing a single line of code. That is the power of XAML!
The Maze object with XAML
Instead of using Windows Presentation Foundation to explain XAML (I might start
to explain more on WPF than on XAML), we are going to build a custom object hierarchy
that we are going to use with XAML.
This way we can look at the details of building XAML enabled objects, how you can
take advantage of the advanced features of this language and how you can extend
it for your own use. We are going to build a little maze (hence A MAZING
XAML
J)
with rooms and items, so let’s start by creating a new library project which we
will call XAML.Mazes. Rename the class1.cs file to Maze.cs and build the following
class:
namespace
XAML.Maze {
public class
Maze {
private string
_name;
public string
Name {
get { return
_name; }
set { _name = value;
}
}
}
}
Build this project. We will later use this object in XAML, but first let’s build
an application to load our maze into memory.
Loading XAML into your application
Add a console application to the solution and call it XAML.TheMaze. Add the following
references to the project: WindowsBase, PresentationCore, PresentationFramework,
XAML.Mazes, and add a couple of using statements:
using
System.Windows;
using
System.Windows.Markup;
using
XAML.Mazes;
using
System.IO;
Implement your Main method as follows:
private
const string myMaze = "../../myMaze.xaml";
static
public Maze LoadMaze( string fromFile
) {
FileStream xamlFile
=
new FileStream ( fromFile,
FileMode.Open, FileAccess.Read );
Maze theMaze = (Maze) XamlReader.Load ( xamlFile );
return theMaze;
}
static
void Main( ) {
Maze myMaze =
Program.LoadMaze ( Program.myMaze );
Console.ReadLine ( );
}
This code used the System.Windows.Markup.XamlReader class to load a XAML file called
myMaze.xaml from disk. So let’s build a XAML file for our Maze object. Add a new
XML file to your console project called myMaze.xaml (adding a new xaml file in visual
studio will invoke the Cider built-in XAML editor, which will take a while, so be
patient):
<u:Maze
xmlns:u="clr-namespace:XAML.Mazes;assembly=XAML.Mazes"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Name="The Amazing XAML Maze"
>
</u:Maze>
XAML namespaces
Time to explain this XAML file a little; first of all we use a root object called
Maze. But because XAML doesn’t know our Maze class we have to tell the XamlReader
where it can find the class definition. We do this by declaring
a xml-namespace:
xmlns:u="clr-namespace:XAML.Mazes;assembly=XAML.Mazes"
This is a special syntax used in XAML to map the u prefix to the XAML.Mazes CLR
namespace and the XAML.Mazes assembly. The other namespace declaration points to
a number of specific XAML extensions which we will discuss later… Because the Maze
element uses the u prefix, the XamlReader knows now that is needs to load the XAML.Mazes
assembly and look for the XAML.Mazes.Maze class. It creates a new instance of our
Maze object and sets the Name property to the “The Amazing XAML Maze” string.
Stepping through this application with the Visual Studio Debugger will show you
that indeed a Maze object gets created with the Name property set.
Object properties
XAML creates your objects by calling the default constructor, and then uses each
xml attribute to set the property on the object with the same name. That is why
our Maze instance has the Name property value correctly set. Actually, the XAML
that we are using is equivalent to this code:
Maze myMaze
= new Maze ( );
myMaze.Name =
"The Amazing XAML Maze";
Adding Rooms to the Maze
Every maze needs a couple of rooms so you at least can get lost. So let’s add a
new Room class to the XAML.Mazes project:
public class Room
{
private string _name;
public string Name
{
get { return _name; }
set { _name = value; }
}
private string _description;
public string Description
{
get { return _description; }
set { _description = value; }
}
}
Make sure the Room class is public.
Now let’s add a couple of rooms to the XAML maze file:
<u:Maze
xmlns:u="clr-namespace:XAML.Mazes;assembly=XAML.Mazes"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Name="The Amazing XAML Maze"
>
<u:Room
Name="room_1"
Description="The
first room" />
<u:Room
Name="room_2"
Description="The
second room" />
</u:Maze>
Running our application will however result in an “Cannot
add content to an object of type 'XAML.Mazes.Maze'.” exception. That is because
XAML doesn’t know where to add our room objects to the maze object.
Property-Element syntax
Could we have used another attribute on the Maze class to have XAML add the room
objects to the maze? Of course not, first of all because a room is itself a complex
object, and also because we have multiple rooms. That is why XAML supports the property-element
syntax, where we can use a <Class.Property>kind of notation. But first let’s
add a Rooms property to the Maze:
public
RoomCollection
_rooms
= new RoomCollection();
public
RoomCollection
Rooms
{
get { return _rooms; }
}
For this we also need a collection of Rooms, so add a new RoomCollection class to
the XAML.Mazes project:
public
class RoomCollection :
List<Room>
{
}
Now we change our XAML file to look like this:
<u:Maze
xmlns:u="clr-namespace:XAML.Mazes;assembly=XAML.Mazes"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Name="The Amazing XAML Maze"
>
<u:Maze.Rooms>
<u:Room
Name="room_1" Description="The first room" />
<u:Room
Name="room_2" Description="The second room" />
</u:Maze.Rooms>
</u:Maze>
This works because the XamlReader recognizes the <u:Maze.Rooms>
syntax, and our RoomCollection class implements the ICollection interface. Our Room
objects get built as normal, and are then added to the Rooms property by calling
the ICollection.Add method. XAML allows for another technique, which we will elaborate
later on in this article.
Our XAML file does the same as this code fragment:
Maze myMaze
= new Maze ( );
myMaze.Name =
"The Amazing XAML Maze";
ICollection rooms = myMaze.Rooms;
Room room1 = new Room();
room1.Name = "room_1";
room1.Description = "The first room";
Rooms.Add( room1 );
Room room2 = new Room();
room2.Name = "room_2";
room2.Description = "The
second room";
Rooms.Add( room2
);
Using Multiple Namespaces in XAML
Let’s clean up our solution for a moment. First of all I want my rooms to be in
a different namespace than the Maze class itself (who know how many different room
classes we might end up at the end of the project?). So let’s move the Room and
Rooms classes to the XAML.Mazes.Rooms namespace:
namespace
XAML.Mazes.Rooms
and add a new using statement to the Maze class because
we use the RoomCollection class there:
using
XAML.Mazes.Rooms;
Running the application now results in a “The tag 'Room' does not exist in XML namespace
'clr-namespace:XAML.Mazes;assembly=XAML.Mazes'” exception.
How can we solve this?
XAML actually allows you to use multiple CLR namespaces mapped to the same XML namespace.
This requires a couple of steps. First add the following CLR attributes to the XAML.Mazes
project, for example in the Mazes class (you will need to reference the WindowsBase,
PresentationCore, and PresentationFramework assemblies again for this to compile):
...
using
System.Windows.Markup;
[assembly: XmlnsDefinition("http://www.u2u.be/amazingxaml",
"XAML.Mazes")]
[assembly: XmlnsDefinition("http://www.u2u.be/amazingxaml",
"XAML.Mazes.Rooms")]
[assembly: XmlnsPrefix("http://www.u2u.be/amazingxaml",
"u")]
namespace
XAML.Mazes
{
...
We also need to specify a mapping in the XAML file:
<?xml version="1.0"
encoding="utf-8" ?>
<?Mapping
XmlNamespace="http://www.u2u.be/amazingxaml"
ClrNamespace="XAML.Mazes"
AssemblyName="XAML.Mazes"?>
<u:Maze
xmlns:u="http://www.u2u.be/amazingxaml"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Name="The Amazing XAML Maze"
>
<u:Maze.Rooms>
<u:Room Name="room_1"
Description="The first room" />
<u:Room Name="room_2"
Description="The second room" />
</u:Maze.Rooms>
</u:Maze>
This mapping tells the XAML parser to load our XAML.Mazes assembly. The XmlnsDefinition
attributes then are used to identify all classes from the XAML.Mazes and XAML.Mazes.Rooms
assemblies, so now we can use them in our XAML file.
Actually, we can now lose the u: prefixes in our XAML file:
<?xml version="1.0"
encoding="utf-8" ?>
<?Mapping
XmlNamespace="http://www.u2u.be/amazingxaml"
ClrNamespace="XAML.Mazes"
AssemblyName="XAML.Mazes"?>
<Maze
xmlns="http://www.u2u.be/amazingxaml"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Name="The Amazing XAML Maze"
>
<Maze.Rooms>
<Room Name="room_1"
Description="The first room" />
<Room Name="room_2"
Description="The second room" />
</Maze.Rooms>
</Maze>
Resources
Every maze has a couple of items you need to open doors, solve puzzles, etc... So
let’s add a couple of items to the maze. Because these items move around, and might
not even be available right away, we need another place to store them. Instead of
using a normal collection of items, we will use the standard XAML resources for
storing the items. WPF used resources to supply its objects with an easy way for
finding commonly reused objects, such as styles and data-objects. Resources are
stored as a key-value collection using the ResourceDictionary class, so let’s add
this to our Maze class as a public property:
private
ResourceDictionary
_resources
= new ResourceDictionary();
public
ResourceDictionary
Resources
{
get { return _resources; }
set { _resources = value; }
}
Of course we also need an item class, to create a new Item class in the XAML.Mazes.Items
namespace:
namespace
XAML.Mazes.Items
{
public class Item
{
private string
_description;
public string
Description
{
get { return
_description; }
set { _description =
value; }
}
private double
_weight;
public double
Weight
{
get { return
_weight; }
set { _weight = value;
}
}
}
}
And we need to add an extra XmlnsDefinition attribute to the beginning of the Item
class:
[assembly:
XmlnsDefinition("http://www.u2u.be/amazingxaml",
"XAML.Mazes.Items")]
So now we are ready to add an item to our maze using XAML:
<Maze
xmlns="http://www.u2u.be/amazingxaml"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Name="The Amazing XAML Maze"
>
<Maze.Resources>
<Item x:Key="theKey" Description="A long, rusty key"
Weight="55"/>
</Maze.Resources>
...
</Maze>
A couple of thing to note about this. First of all, resources
are stores as key-value pairs, with the value being the object. But where do we
get the key? This is what the x:Key=”…” is used for.
The x:Key is a standard feature of XAML (hence the x:
prefix) and is used to send the key value to the XAML parser so it can add it to
the ResourceDictionary instance.
Type Converters
The Weight property is a double, but in XAML the value is a string. How does XAML
convert the string to a double? Well, it looks if the target type (or target property)
has a TypeConverter. TypeConverters are a standard way for converting one type to
another, and the .NET runtime has supplied us with TypeConverters for standard built-in
types. That is why XAML can convert strings to integers, doubles, etc…
We can use this system to our advantage. Some rooms have items already in them,
and we’re going to use a TypeConverter so we can easily list the items in the room.
Start by adding a new ItemNames property to the Room class:
using
System.ComponentModel;
using
XAML.Mazes;
...
private
static string[]
noItemNames = new string[]
{ };
private
string[] _itemNames = null;
[TypeConverter
( typeof ( StringArrayConverter
) )]
public
string[] ItemNames {
get {
if( _itemNames ==
null )
return noItemNames;
else
return
_itemNames;
}
set { _itemNames =
value; }
}
Please note that the ItemNames property is an array of strings, and we’re going
to use a type converter to convert a comma-separated list of item-names into an
array of strings. We’re using the TypeConverterAttribute to specify the TypeConverter
XAML needs to call to do the conversion. Add a StringArrayConverter class to your
XAML.Mazes project:
using
System.ComponentModel;
using
System.Globalization;
namespace
XAML.Mazes
{
public class
StringArrayConverter : TypeConverter
{
public override
bool CanConvertFrom(
ITypeDescriptorContext context, Type sourceType)
{
if (sourceType ==
typeof(string))
return true;
else
return base.CanConvertFrom(context,
sourceType);
}
public override
object ConvertFrom(
ITypeDescriptorContext context,
CultureInfo
culture, object value)
{
return (value as
string).Split(',');
}
}
}
(TypeConverters are nicely documented in the .NET documentation, so we’re not going
to discuss this.)
Now we can add some items to our rooms:
<Maze
xmlns="http://www.u2u.be/amazingxaml"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Name="The Amazing XAML Maze"
>
<Maze.Resources>
<Item x:Key="theKey" Description="A long, rusty key"
Weight="55"/>
<Item x:Key="oldKnife" Description="An old, antique knife"
Weight="65"/>
<Item x:Key="oldFork" Description="An old, antique fork"
Weight="65"/>
</Maze.Resources>
<Maze.Rooms>
<Room Name="room_1" Description="The first room"
ItemNames="theKey"/>
<Room Name="room_2" Description="The second room"
ItemNames="oldKnife, oldFork"/>
</Maze.Rooms>
</Maze>
To actually use the items in the room we need to add an Items property
that will lookup the items using the maze’s resources (add this code
to the Room class):
using
XAML.Mazes.Items;
using
System.Diagnostics;
...
private
Item[] _items = null;
public
Item[] Items {
get {
if( _items == null
) {
_items = LookupItemNames ( );
}
return _items;
}
}
private
Item[] LookupItemNames( ) {
List<Item>
items = new List<Item>( );
foreach( string
itemName in ItemNames ) {
Item it = this.Maze.Resources[itemName.Trim()]
as Item;
if( it != null
)
items.Add ( it );
else
Debug.WriteLine
( "Item with name {0} was not found",
itemName );
}
return items.ToArray ( );
}
This code does require access to the room’s maze, so let’s add support for that
(add code to the Maze class):
static
Maze _current;
public
static Maze Current
{ get { return
_current; } }
public
Maze( ) {
_current = this;
}
Markup Extensions
Now let’s add exits to our rooms, start by adding a new Exit class to the XAML.Mazes
project:
using
XAML.Mazes;
using
XAML.Mazes.Rooms;
using
System.Windows.Markup;
[assembly: XmlnsDefinition("http://www.u2u.be/amazingxaml",
"XAML.Mazes.Exits")]
namespace
XAML.Mazes.Exits
{
public enum
Direction
{
North,
East,
West,
South
};
public class
Exit
{
private Direction
_direction;
public Direction
Direction
{
get { return
_direction; }
set { _direction =
value; }
}
private Room
_room;
public Room
Room
{
get { return
_room; }
set { _room = value;
}
}
}
}
And of course an ExitCollection class:
public
class
ExitCollection
:
List<Exit>{
}
Now let’s add an Exits property to the Room class:
using
XAML.Mazes.Exits;
private
ExitCollection _exits = new
ExitCollection();
public
ExitCollection Exits
{
get { return
_exits; }
}
And now add the Exits to the XAML file:
<Room Name="room_1"
Description="The first room"
ItemNames="theKey">
<Room.Exits>
<Exit Direction="North"
Room="???" />
<Exit Direction="East"
Room="???" />
</Room.Exits>
</Room>
Specifying the direction is easy, XAML will automatically convert the string to
our enumeration, however to specify the room we need something special. We cannot
use a room element because this might result in an endless XML loop; look at room1,
the north exits actually ends up in the same room!
We are going to use a Markup Extension, this is a special class that will get called
by XAML during parsing, asking the extension to return the value by calling its
ProvideValue method. Using a markup extension is easy in XAML, simple specify the
name of the extension between curly braces ({ <Extension>
…}. WPF uses this for example to lookup a resource using the {x:Resource}
syntax. We’re going to build our own extension, so let’s start by adding a new GoTo
class to our XAML.Mazes project (who would have thought you would ever use a GoTo
in XAML
J):
using
System.Windows.Markup;
using
XAML.Mazes.Rooms;
...
[MarkupExtensionReturnType(typeof(Room))]
public
class GoTo : MarkupExtension
{
internal
class DelayRoomLookup : Room
{
public DelayRoomLookup(string
name) :
base( name ) { }
}
private string
_roomName;
private string
RoomName { get { return
_roomName; } }
public GoTo(string
roomName)
{
_roomName = roomName;
}
public override
object ProvideValue(IServiceProvider
serviceProvider)
{
Room otherRoom = Maze.Current[RoomName];
if (otherRoom != null)
return otherRoom;
else
return new
DelayRoomLookup(RoomName);
}
}
All markup extensions need to derive from the MarkupExtension base class, and override
the ProvideValue method. Initializing a markup extension is normally done with the
constructor, which can take a string argument. We are also being friendly to XAML
by explaining that our extension returns a Room object from the ProvideValue method
using the MarkupExtensionReturnTypeAttribute. The ProvideValue method returns the
room to go to by doing a lookup by room name.
To complete this extension we need a read-only indexer on the Maze class for retrieving
rooms by name:
public
Room
this[string roomName]
{
get
{
return Rooms.Find(delegate(Room room)
{
return room.Name == roomName;
});
}
}
And of course we have to add a little support for the DelayRoomLookup class in the
Exit class, so change the Exit’s Room property:
public
Room Room
{
get {
if (_room is
GoTo.DelayRoomLookup)
{
GoTo.DelayRoomLookup
lookup = _room as
GoTo.DelayRoomLookup;
_room = Maze.Current[lookup.Name];
}
return _room;
}
set { _room = value; }
}
And to complete this functionality add a GoTo method in the Room class:
public
Room GoTo(Direction
direction)
{
Exit exit = Exits.Find(delegate(Exit
e)
{ return (e.Direction == direction); });
if (exit != null)
return exit.Room;
else
return this;
}
You can actually use more than one string to initialize the GoTo extension. Let’s
say we want some of our exits to be available only when the player carries an item:
public GoTo(string roomName,
string itemName)
{
RoomName
= roomName;
RequiredItemName
= itemName;
}
You use it in the XAML file as follows:
<Exit
Direction="North"
Room="{GoTo room_1, theKey}" />
Or you use named properties:
private string _roomName;
public string RoomName
{
get { return _roomName;
}
protected set { _roomName
= value; }
}
private string _requiredItedName;
public string RequiredItemName
{
get { return _requiredItedName;
}
protected set { _requiredItedName
= value; }
}
Which you then set in the XAML file:
<Exit
Direction="North"
Room="{GoTo RoomName=room_1, RequiredItemName=theKey}" />
Using ContentPropertyAttribute
Some of our items have very long descriptions, and in this case the XAML element
for an item becomes very long and difficult to read. Can we solve this? Yes, by
applying the ContentPropertyAttribute to our class we can use the inner text of
the element to use as our description:
[ContentProperty("Description")]
public
class Item
{ ... }
Our XAML item now looks like this (add it to the item collection):
<Item
x:Key="mustyBook"
Weight="100">
An
old musty book called -- Programming Windows 3.1 --
</Item>
We can actually use this same technique to get rid of the <Maze.Rooms>property-element!
[ContentProperty("Rooms")]
public
class Maze
{ ... }
Remove the <Maze.Rooms>and </Maze.Rooms>tags from the XAML file:
<Maze
xmlns="http://www.u2u.be/amazingxaml"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Name="The Amazing XAML Maze"
>
<Maze.Resources>
...
</Maze.Resources>
<Room
Name="room_1"
Description="The
first room" ItemNames="theKey">
<Room.Exits>
<Exit Direction="North"
Room="{GoTo RoomName=room_1,
RequiredItemName=theKey}" />
<Exit
Direction="East"
Room="{GoTo room_2}" />
</Room.Exits>
</Room>
<Room
Name="room_2"
Description="The
second room" ItemNames="oldKnife, oldFork"/>
</Maze>
You might want to try the same for the Room.Exits!
Attached Properties
This leaves one more thing we need to discuss about XAML. In WPF there are all kinds
of containers for your controls, for example there is a Canvas control that allows
you to position child controls anywhere you want. There is also a Grid control that
will do the layout for you. The problem is each control needs to specify where it
wants to be put in the containing control. Of course we don’t want to have to add
properties to our controls for each possible container. XAML solves
this using attached properties. These allow a control to tell any containing
control where it wants to position itself. For example:
<Window
x:Class="AttachedPropertySample.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="AttachedPropertySample"
Height="300" Width="300"
>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<Button Grid.Row="1"
Grid.Column="1">Click me!</Button>
</Grid>
</Window>
This example will place the button in the lower right corner of a 2x2 grid. It’s
actually the Grid class that will remember the button’s position, making the button
class independent of the Grid class.
We’ll use an attached property to set the player’s start room, so let’s add an new player class to the XAML.Mazes project:
public class
Player
{
static
Room _startRoom;
}
And of course give the Maze a player property:
private Player _player;
public Player Player
{
get { return _player;
}
set { _player = value;
}
}
Set one of the rooms as the start using the attached property syntax:
<Room
Player.Start="true"
Name="room_1"
...
Attached properties are implemented by adding a static Get<PROPERTY> and Set<Property>
method:
public class
Player
{
public static
void SetStart(Room room, bool
isStart)
{
if (room == null)
throw new
ArgumentException("Invalid room");
_startRoom = room;
}
public static
Room
GetStart(Room
room)
{
return
_startRoom;
}
...
}
The Set method takes two arguments, the element where the attached property is being
used (our Room class) and the value of the property (a Boolean in our case). Our
implementation simply remembers the room passed as the starting room (ignoring the
Boolean argument).
Room for Improvement
We can make our XAML solution even better, by getting rid of the Mapping processing
instruction. Remove the <? Mapping …> processing instruction from the XAML
file, and add the following code to the LoadMaze method in the console application:
static public
Maze LoadMaze( string fromFile ) {
FileStream xamlFile
= new FileStream ( fromFile,
FileMode.Open, FileAccess.Read
);
ParserContext
parserContext = new ParserContext();
XamlTypeMapper
mapper = new XamlTypeMapper(new string[] { "XAML.Mazes" });
parserContext.XamlTypeMapper=mapper;
Maze theMaze =
(Maze)XamlReader.Load(xamlFile,
parserContext);
return theMaze;
}
The XamlTypeMapper instructs the XamlReader to load the XAML.Mazes assembly and
process the XmlnsDefinitions, which identifies our Maze classes.
XAML Cookbook
Just as an easy reference, let’s list a couple of common things to do for XAML:
Q: You want to use a couple of classes in
a XAML file, and all of them are in the same CLR namespace?
A: Add a XML namespace declaration to the element looking like this (UPPERCASE are
place-holders):
xmlns:PREFIX="clr-namespace:NAMESPACE.CLASS;assembly=ASSEMBLYNAME"
Now you can use your objects using the <prefix:class>xml
element syntax.
Q: You need to use classes from multiple CRL namespaces in your XAML file?
A: Use the XmlnsDefinition attribute to map your namespaces to the same xml namespace,
and then use the
<?Mapping
XmlNamespace="…"
ClrNamespace="…" AssemblyName="…"?>
XML processing instruction. Or use the XamlTypeMapper.
Q: You want to have a shorter
syntax for creating objects?
A: Use a custom TypeConverter,
which creates your object by parsing some string.
Q: You want to add multiple
objects to your class, and have an easy syntax?
A: Use the ContentPropertyAttribute.
But beware, this will only work for one of your properties, the others need to use
the Property-Element syntax.
Q: Objects need a way to
talk to other objects which should be independent of your class?
A: Use the Attached Property
syntax. This way any object can talk to you, but you have to handle them.
Q: You need an easy way
to specify a graph of objects, and build tools for easy editing?
A: Use XAML!
About the Author
Peter Himschoot is
an architect and trainer for U2U, specializing in .NET,
Team System, BizTalk and .NET 3.0 (aka WinFx).
He is also a Microsoft Regional Director (http://www.microsoft.com/belux/nl/msdn/community/regionaldir.mspx
).
You can reach him at peter@u2u.net.
Or read his blog: http://blog.u2u.info/DottextWeb/peter/
Favorite T-shirt: >> Code is poetry! <<

U2U Training and Consultancy Services is a Microsoft .NET competence center located
in
Belgium
, to learn more please visit www.u2u.be
.