Click Here to Install Silverlight*
United StatesChange|All Microsoft Sites
MSDN
The Beta Experience

Amazing XAML

 

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.

 

Downloads:

 

Contents:


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 ? 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 ? 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 ? ) :

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 bool 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, Visual Studio Team System, BizTalk Server and .NET 3.0 (aka WinFx).
He is also a Microsoft Regional Director.

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 .


© 2017 Microsoft Corporation. All rights reserved. Contact Us |Terms of Use |Trademarks |Privacy & Cookies
Microsoft