Monday 5 May 2014

XAML learning

XAML Learning


XAML is fundamental to Windows Phone 8 development, without understanding it you can not develop WP8 applications. If you’re like I was, and have little understanding of XAML, below are some great places to learn what XAML is, how/why it’s used, and it’s syntax

XAML can be confusing - especially if you think it is a markup language like HTML - it isn't.
XAML is a, mostly declarative, object instantiation language – that is it’s a way of describing using XML what objects should be created and how they should be initialised before your program starts running. If you keep this in mind then XAML is becomes a lot more transparent and easier to understand.

Its use in conjunction with WPF is just one of its many possible applications and indeed it has started to appear in other places – Windows Workflow for example. To explain exactly what XAML is this article works with custom classes that have nothing to do with WPF so that we can find out about XAML in a completely general context.

Declaritive instantiation

We could start with a non-WPF project to prove how general XAML is we but this would waste a lot of time adding references and “usings”. So let’s start with a simple WPF Application. You don’t need to modify any of the generated code but you do need to add a simple custom class with which to try out XAML:
public class MyClass
{
 public MyClass()
 {
 }
}
All that is necessary for a class to be instantiable by XAML is that it has a parameterless constructor and it can’t be a nested class. It can have other constructors but these play no part in its working with XAML. Notice that as structs have a default parameterless constructor provided automatically by the system you can instantiate structs in XAML.
Now that we have our minimal class we can write some XAML to create an instance of it. However first there has to be some way of making the link between the XAML document and the class definition. This is achieved by importing the class’s namespace into XAML.
You can import the namespace of any assembly and use namespaces to indicate exactly which class you are referring to. In this case we need to import the namespace of the assembly that the XAML file is part of, i.e. the current project. So moving to the XAML editor we need to add a single line to the <Window> tag:

<Window x:Class="WpfApplication1.Window1"
  xmlns="http://schemas.microsoft.com/
  winfx/2006/xaml/presentation"
  xmlns:x="http://schemas.microsoft.com/
  winfx/2006/xaml"
  xmlns:m="clr-namespace:WpfApplication1"
  Title="Window1" Height="300"
  Width="300" Loaded="Window_Loaded">
The “clr-namespace” is a special token which is interpreted to mean “get the namespace from the named CLR runtime”. In general you might also need an “assembly=” token to supply the location of the assembly but in this case it’s assumed to be the current project.
Following this any name prefixed by m: is taken from the namespace of the current project.
The next thing we have to do is to get rid of the <Grid> tags as we cannot nest a general class within a grid – it needs a class that can be displayed. To create the instance of our class all we have to enter is:
 <m:MyClass>
 </m:MyClass>
</Window>
The project should now run without errors. If you do see any errors then it will be due to loss of synchronisation between namespaces – simply run the project again. The need to keep namespaces and other generated files in sync is one of the problems of splitting instantiation from the runtime.

Naming objects

So we have a working program that creates an instance of MyClass but this does us very little good as the instance is dynamic and goes out of scope as soon as the main form is loaded!
To keep the instance in scope, and to allow us to work with it within the C# code, we need to give the instance a name. Standard WPF components are generally given a name via the NAME property but a more direct mechanism for custom classes is to use the facilities provided by the XAML “x” namespace. The “x:Name” attribute can be used to set the name used by XAML to create the instance:
<m:MyClass x:Name="MyObject1">
</m:MyClass>
With this change you can now run the program and, with the help of a breakpoint and the debugger, you can confirm that there it really does create MyObject1.
If you would like to see the generated C# code that does the instantiation then load the file called Window1.g.cs - click on the Show All files icon in the Solution Explorer and its in the obj\Debug directory - where you will discover that what happens boils down to:
internal WpfApplication1.
               MyClass MyObject1;
followed a few lines later by:
this.MyObject1 = ((WpfApplication1.
                    MyClass)(target));
Not exactly the way most of us would do it, but it serves a higher purpose!
As already mentioned, WPF classes inherit a Name property which can be used in place of x:Name. If a class doesn’t have a Name property then use x:Name, if it does use it, but don’t give your class a Name property unless it sets the name of the new object.
To assign a name property to a class you simply use the RuntimeNameProperty Attribute to designate a property that will store the name of the instance. For example:
[RuntimeNameProperty("MyName")]
public class MyClass
{
 public MyClass(){}
 public string MyName
 {
  get { return _MyName; }
  set { _MyName= value; }
 }
 private string _MyName= string.Empty;
}
However this only works if the class belongs to another assembly and not the one actively being edited in Visual Studio say. Notice also that the XAML compiler will create an instance with the name you specify and store the name in the property in case you need to make use of it.

Implementing a type converter

As an alternative to using nested property syntax to set reference properties, we can opt to handle the conversion from the initialiser string to the object properties ourselves.
Surprisingly this isn’t difficult. For example, suppose we want to allow MyProperty2 to be set by a string. All we have to do is tag the type that the type converter will apply to with:

[TypeConverter(typeof(
      MyDataClassTypeConverter))]
public class MyDataClass
{
 
The rest of the class is unaltered. Now we have to implement the MyDataClassTypeConverter and to do this we need to add:
using System.ComponentModel;
The type converter class has to inherit from TypeConverter and it first implements CanConvertFrom which simply returns true or false depending on whether or not the type converter can do the requested conversion:

public class
MyDataClassTypeConverter:TypeConverter
{
 public override bool
 CanConvertFrom(ITypeDescriptorContext
                       context, Type t)
 {
  if (t == typeof(String)) return true;
  return false;
}
 
The second method we need actually does the type conversion:

 public override objectConvertFrom(
   ITypeDescriptorContext context,
   System.Globalization.CultureInfo 
                     culture,object val)
 {
  MyDataClass temp=new MyDataClass();
  temp.MyProperty2= Convert.ToInt32(
                          (string)val);
  return temp;
 }
}
Notice that it creates a new instance of the class that it performs type conversion for and then processes the string to initialise the properties of the MyDataClass instance. Notice also that the Type converter creates and then initialises the object to be stored in the property using the information supplied by the initialisation string.
Clearly a real example would be more complex and would parse the string to extract multiple items of initialisation information.
With the type converter in place we can create an instance of MyClass using:

<m:MyClass x:Name="MyObject"
     MyProperty="23" MyClassProp="35" >
</m:MyClass>

Beyond initialisation

Suppose you want to make use of the objects created by the XAML in your code. The attribute:
x:Class="WpfApplication1.Window1"
defines the partial class that provides the “code behind” support for the XAML. Within this class all of the objects you have created are within the namespace and accessible.
For example you can write, without any other modifications:

private void Window_Loaded(
       object sender,RoutedEventArgs e)
{
 MessageBox.Show(
     MyObject1.MyProperty.ToString());
}
 
You can similarly write code that manipulates any object or property initialised by XAML.

Where next?

It is clear that XAML is a very general and extensible object instantiation language. If you follow the example in this article I can promise you lots of difficulties caused by synchronisation – the project organisation used in this article was adopted to make the example simple and thus falls over in the real world. The problem is that XAML doesn’t expect the assemblies that contain the classes it is instantiating to change during development. This is easily cured by splitting out the class definitions into another assembly.

In the final analysis you have to ask yourself if XAML is worth it. Is it really so much better to instantiate objects declaratively with all of the type conversion and namespace problems this brings about? Even if you decide it isn’t, armed with the knowledge of the “bigger picture” the way XAML works with both WPF and Silverlight should be much easier to understand. You might even want to create custom designers for your own sub-space of XAML.

No comments:

Post a Comment