Click here to Skip to main content
15,879,535 members
Articles / Web Development / ASP.NET

Writing XAML Friendly Assemblies

Rate me:
Please Sign up or sign in to vote.
3.80/5 (30 votes)
21 Mar 20048 min read 78.6K   39   9
How to write assemblies so that they will work with the various XAML parsers that are starting to emerge.

Contents

What's New

March 22, 2004: Added the seventh golden rule (seven golden rules is a much better number than 6)--Tag and Name properties, and an interface to help you remember!

Introduction

This is my entry in the Code Project's Design And Strategy competition for March. It's very simple, and I hope you enjoy it!

What Is XAML

XAML is Longhorn's XML Application Markup Language (not to be confused with the "Transaction Authority Markup Language"). The concept of using markup to describe the user interface certainly isn't new. The XUL people have been doing it for years. Of course, now that Microsoft is jumping on the band wagon, there's a lot of ooh-ing and aah-ing and a lot of moaning too. If you read some of the articles on the Microsoft website that start off with "why is this better?", you'll quickly discover that even Microsoft is having a hard time saying why it's better. But I digress.

XAML is a markup syntax that describes the user interface. But it's actually a lot more. It's a generic class instantiator. You can use XAML to instantiate classes (create an object that is an instance of a class), assign values to the properties that the class exposes, and wire up events to your application code. So, it actually does a lot more than just create a UI. XAML also allows you to define code in-line with the markup.

Will I Be Editing XML Now

No, of course not, unless you want to (I'm one of those sick people that likes to). The whole markup will be hidden behind a designer so most people won't even know it's there.

So Why Use It

Because Microsoft is designing a "standard" (we've heard that before) syntax for describing user interface controls. The next Visual Studio will serialize your GUIs out to XAML. Now you can have a deserialization engine (a parser) that can reconstruct the GUI in anything--a Window's client, a web page, a mobile device, and so on.

There are some other interesting advantages as well with the idea of generating the user interface at runtime (if you want to take the performance hit):

  • you can change the UI at runtime
  • you can write a simple designer that lets your customer change the UI to within reason
  • it's easy to implement styles and customize those styles
  • it makes re-use a breeze--just copy&paste the markup
  • it provides a nice clean separation between the user interface itself and the presentation layer
  • it's easy to extend your UI with features such as vector graphics, SVG, and even non-UI things like state machines

About the Presentation Layer

I hope I'm not mincing words here. If I were to define the UI as the "surface" on which controls are drawn (like buttons and comboboxes, etc.), then the typical PL definition is the thing that manages the UI and the interface between the PL and the business layer. Managing the UI means handling events, doing type conversion, etc. A lot of times, the PL is "polluted" with UI code--just look at what the Visual Studio designer emits--you have UI code generation mixed in with PL event handling. And even worse, the designer lets you mix in DataSet, DataTable, DataView, SqlConnection, and other non-UI components, creating a snarling mess of UI, PL, and business logic.

So, XAML is a good thing, because it separates out the UI from the PL, and let's the PL do its job better.

Who are the Players

Well, there's me, of course, with the MyXaml open source project. There's also Xamlon, WFML, Mobiform, and Laszlo. Those are the XAML players of note, I would say.

How Does It Work

Image 1

All the XAML parsers essentially work the same at the core:

  • 1 & 2: They use a special namespace mapping construct to map from the assembly namespace to the XML namespace.
  • 3: They use the XML prefixes to figure out the namespace in which the class resides, so it can be instantiated.
  • 4 & 5: They use XML attributes to assign values to the properties of the class or its base classes.
  • 5: They use a type converter to help convert from the XML string to the type that the property expects (leveraging on several type converters built into .NET).
  • 6: They wire up events to class instance.

That, in a nutshell, is it!

Writing A XAML Friendly Assembly

So, now that you know the basics, let's talk about some simple rules to follow so that you can succeed at writing a XAML friendly assembly.

Namespaces

XML allows you a single default namespace which allows you to reference classes in the tag without a prefix. Everything else needs a namespace, which is going to make the markup really ugly if you have a lot of different namespaces. So choose your namespaces carefully. As far as MyXaml is concerned, I'll probably implement something that walks through all the namespaces of an assembly in an attempt to instantiate a class, but that's time consuming, and as far as I know, it's not XAML compliant.

For example, in MyXaml, the namespace is extracted from the XmlNode NamespaceURI property (MyXaml uses a slightly different syntax for namespace mapping, but I'll change that soon):

C#
public object CreateControl(object parent, XmlNode element,
                            object eventTarget)
{
  string controlName=element.LocalName;
  string nameSpace=element.NamespaceURI;
  object ctrl=InstantiateControl(nameSpace, controlName);
  ...
}

Classes

Classes must have default constructors. The parser doesn't know what parameters the constructor is going to need, so you have to provide a default constructor. In MyXaml, classes are instantiated like this:

C#
protected object InstantiateControl(string nameSpace, string name)
{
  // construct the control based on the namespace information
  string qualifiedName=StringHelpers.LeftOf(nameSpace, ',')+"."+name;
  if (StringHelpers.RightOf(nameSpace, ',') != String.Empty)
  {
    qualifiedName=qualifiedName+","+StringHelpers.RightOf(nameSpace, ',');
  }
  object ctrl=InstantiateControl(qualifiedName);
  return ctrl;
}

protected object InstantiateControl(string qualifiedName)
{
  object ctrl=null;
  Type t=Type.GetType(qualifiedName);
  if (t != null)
  {
    ctrl=Activator.CreateInstance(t);
    ...
  }
}

Given the namespace, there's a little bit of massaging to combine the namespace with the class name in order to create a fully qualified name. The parser then attempts to obtain the type and instantiate the class. No parameters are passed to the constructor. (BTW, the word "Control" is going to be deprecated soon).

Properties

Write property setters, at a minimum, for all your publicly settable fields. The exception to this is collections. The basic concept is very simple: given the object just instantiated and XML attribute name, attempt to set the property to the XML attribute value. In MyXaml, you'll find some code like this:

C#
PropertyInfo pi=obj.GetType().GetProperty(propertyName);
...
// Thanks to Leppie for showing me the TypeConverter class
TypeConverter tc = TypeDescriptor.GetConverter(pi.PropertyType);
if (val is String)
{
  if (tc != null && tc.CanConvertFrom(typeof(string)))
  {
    // changed from ConvertFromString, as per CPian tditiecher, to support
    // different culture formats
    object objConv = tc.ConvertFromInvariantString((string)val); 

   // The Form.MainMenu property returns true for CanConvertFrom, but null
   // when the conversion takes place!
   if (objConv != null)
   {
     try
     {
       pi.SetValue(obj, objConv, null);
       ...
     }
     ...
    }
  }
}

Type Converters

For custom types, write a type converter that converts a string to your internal property type. Notice in the above code that the type converter is always used. MyXaml can handle custom converts within its own namespace, and uses a couple, and I'll eventually extend MyXaml to fire an event so that you can handle cases where you don't want to implement a type converter but would rather handle the conversion differently.

Collections

Collections should be derived from an IList interface. Collections can be read-only (make sure you instantiate an empty collection in your constructor!). In MyXaml, if the parent object is of type IList, it automatically adds the child object to the collection:

C#
if (obj is IList)
{
  string nameSpace=node.NamespaceURI;
  string qualifiedName=StringHelpers.LeftOf(nameSpace, ',')+"."+propertyName+
     ", "+StringHelpers.RightOf(nameSpace, ',');
  object ctrl=InstantiateControl(qualifiedName);
  if (ctrl != null)
  {
    ((IList)obj).Add(ctrl);
    isProperty=true;
  }
}

Events

If you're implementing a UI element, consider writing an event for every action that your control handles from the .NET framework. If your class is not a UI element (or even if it is), consider implementing an event whenever a property setter is called, even if the property value doesn't change. You will make lots of friends by taking the time to really think through what event your users are going to be interested in.

In MyXaml, I've implemented a non-standard approach to wiring up events. Typically, the event is handled at the application level or by the instantiated object. I've chosen (from user requests) to walk up the entire object hierarchy in an attempt to wire up the event, starting from the currently instantiated object, through all in-line and code-behind code, up to the application instance itself (as long as one was passed to the parser). The code for the parser looks like this:

C#
protected bool SetEvent(object obj, string propertyName, string val,
                        object eventTarget)
{
  // if it's not a property, see if it's an event
  EventInfo ei=GetEventInfo(obj, propertyName);
  bool isEvent=ei != null;
  if (isEvent)
  {
    try
    {
      string methodName=val;
      Delegate dlgt;
      eventTarget=SearchForEventTarget(ei, ref methodName, out dlgt);
      ei.AddEventHandler(obj, dlgt);
      }
    catch(Exception e)
    {
      ...
    }
  }
  else
  {
    // the property is not an event.
    ...
  }
  return isEvent;
}

The actual search function is a bit too long to put here. Regardless of whether the user is using MyXaml or Xamlon or another parser, it is important that you, the implementer, provide the appropriate events.

Tags and Names

As I'm writing some XAML for a demonstration MDI application, I'm realizing the importance of tags and names. As I wrote in my blog: "XAML makes your applications very event-centric. Events are the primary mechanism for communicating outside things to the inside (your application). Your events operate on things that they can get access to via the parser." But what this means is that your event handlers are going to need help acquiring information relevant to the object that fired the event. Tags are where this information can be conveniently stored, and it provides a poor-man's way of communicating useful information between objects and between the declarative markup and the application code. Names are useful when you need to search through a collection for a particular instance (or look up an instance by its name).

For example, the MenuItem class is missing both the Tag property and the Name property. This is really inconvenient, to the point where using .NET's MenuItem class is pretty much useless. For the MDI demonstration (still working on it), what I've done is taken Chris Beckett's Menu object and extended it with some useful properties to make it XAML-friendly. You might even want to use the following interface to help remember this golden rule:

C#
interface IXaml
{
  string Name
  {
    get;
    set;
  }

  object Tag
  {
    get;
    set;
  }
}

Wrapping It Up

So those are the seven golden rules to a XAML friendly assembly:

  1. not too many namespaces
  2. public, default constructors
  3. properties for everything you want to expose to the outside world
  4. type converters when .NET can't handle the conversion itself
  5. collections derived from IList and instantiated at construction time if read-only
  6. implement events for UI actions and property setters, where appropriate
  7. every class should provide a Tag property

License

This article has no explicit license attached to it, but may contain usage terms in the article text or the download files themselves. If in doubt, please contact the author via the discussion board below. A list of licenses authors might use can be found here.


Written By
Architect Interacx
United States United States
Blog: https://marcclifton.wordpress.com/
Home Page: http://www.marcclifton.com
Research: http://www.higherorderprogramming.com/
GitHub: https://github.com/cliftonm

All my life I have been passionate about architecture / software design, as this is the cornerstone to a maintainable and extensible application. As such, I have enjoyed exploring some crazy ideas and discovering that they are not so crazy after all. I also love writing about my ideas and seeing the community response. As a consultant, I've enjoyed working in a wide range of industries such as aerospace, boatyard management, remote sensing, emergency services / data management, and casino operations. I've done a variety of pro-bono work non-profit organizations related to nature conservancy, drug recovery and women's health.

Comments and Discussions

 
Generalnothign to do with longhorn/vista Pin
aL38915-Apr-08 5:31
aL38915-Apr-08 5:31 
GeneralRe: nothign to do with longhorn/vista Pin
Marc Clifton5-Apr-08 5:36
mvaMarc Clifton5-Apr-08 5:36 
QuestionNeed more understanding.. Pin
Punprom Kasemsant8-Feb-07 4:56
Punprom Kasemsant8-Feb-07 4:56 
QuestionExisting controls Pin
Federico Ribeiro21-Jul-06 3:36
Federico Ribeiro21-Jul-06 3:36 
GeneralWeb Forms Pin
Anonymous3-Oct-05 0:23
Anonymous3-Oct-05 0:23 
GeneralUnderstanding... Pin
monrobot1323-Mar-04 20:45
monrobot1323-Mar-04 20:45 
GeneralRe: Understanding... Pin
Marc Clifton24-Mar-04 1:13
mvaMarc Clifton24-Mar-04 1:13 
GeneralReuse... Pin
Daniel Turini22-Mar-04 7:20
Daniel Turini22-Mar-04 7:20 
GeneralRe: Reuse... Pin
Marc Clifton22-Mar-04 7:24
mvaMarc Clifton22-Mar-04 7:24 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.