White Papers: Writing XAML-Friendly Assemblies
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):
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:
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 "e;Control"e; is going to be deprecated soon).
Properties
Write property setters, at a minimum, for all you 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:
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:
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:
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, then implementer, provide the appropriate events.
Wrapping It Up
So those are the six golden rules to a XAML friendly assembly:
- not too many namespaces
- public, default constructors
- properties for everything you want to expose to the outside world
- type converters when .NET can't handle the conversion itself
- collections derived from IList and instantiated at construction time if read-only
- implement events for UI actions and property setters, where appropriate
©2004 - 2008 MyXaml. All Rights Reserved.
|