By Joe Stegman
Download the sample
Introduction
This is a sample of an extensible mechanic to add a markup model on top of an existing. net Framework object model. this sample's parsing rules can be summarized as "XML elements map. net Framework types and XML attributes map to type properties (or events )". this sample has des a markup parser that dynamically generates an object instance tree from an XML file. the markup format provided des constructs for the following:
- XML namespace to. NET Framework namespace Mapping
- Object instancing
- Object identification and references
- Property sets
- Instance and Static Method Invocation
- Event wire-ups
- Assembly references
Disclause
This is only a sample and will not be part of the next version of Windows Forms. in addition, this sample was written against. net Framework Version 1.1 and has not been tested against other versions.
Basic sample
The following sample shows the XML syntax used to declare a simple Windows Forms form with a single child label control.
<? XML version = "1.0" encoding = "UTF-8"?>
<? Mapping xmlns = "http://www.microsoft.com/2003/WindowsForms"
Namespace = "system. Windows. forms; system. Drawing"?>
<Wfml xmlns = "http://www.microsoft.com/2003/WindowsForms"
Xmlns: wfml = "http://www.microsoft.com/2003/WFML">
<Form wfml: Root = "true" text = "basic sample" size = "300,200">
<Label text = "Hello World" autosize = "true" location = "10, 20"/>
<Method. Show/>
</Form>
</Wfml>
The Windows Forms XML Parser sample will generate an instance tree (form) from the above XML. This assumes the above XML is contained in a file named "Basic. xml ".
Markupparser parser = new markupparser ();
Object form = parser. parse ("basic. xml ");
This will dynamically generate the following form:
Dissecting the basic sample
The basic sample is composed of an XML declaration, a Windows Forms parser specific processing instruction, a root tag, the instance declarations and an end tag. These sections are described below:
XML Declaration
<? XML version = "1.0" encoding = "UTF-8"?>
This is a standard XML declaration that describes the XML version and encoding. This line is at the top of the most XML files and is not specific to this sample.
Sample Processing Instruction
<? Mapping xmlns = "http://www.microsoft.com/2003/WindowsForms"
Namespace = "system. Windows. forms; system. Drawing"?>
This line defines a mapping between an XML namespace and. net Framework namespaces. this line declares that any elements defined in the XML namespace "The http://www.microsoft.com/2003/WindowsForms" will map to types in. net Framework namespaces "system. windows. forms or system. drawing ". see the markup reference section for more information on the mapping Processing Instruction.
Root tag
<Wfml xmlns = "http://www.microsoft.com/2003/WindowsForms"
Xmlns: wfml = "http://www.microsoft.com/2003/WFML">
This line defines the default XML namespace for the sample file ("http://www.microsoft.com/2003/WindowsForms") and defines "wfml" as a prefix for elements and attributes in the "http://www.microsoft.com/2003/WFML" namespace.
Instance declarations
<Form wfml: Root = "true" text = "basic sample" size = "300,200">
<Label text = "Hello World" autosize = "true" location = "10, 20"/>
<Method. Show/>
</Form>
When processing this XML, the parser will create an instance of type form using the default form constructor and set its text property to "basic sample" and its size property set to "300,200 ". the parser will create a "label" and add it to the form's controls collection (see below for details) and set the label's text property to "Hello world ", set the label's autosize property to "true" and set the label's location property to "10, 20 ". finally, the parser will call the method "show" on the form instance.
XML namespace to. NET Framework Mappings
The sample parser creates an instance of the system. windows. forms. form type as a result of parsing the <form> tag. the. net Framework Version 1.1 Except des at least two different "form" types: one in system. windows. forms and one in system. web. UI. mobilecontrols. the parser selected the Form Type in system. windows. forms based on the evaluation of the XML namespace. net Framework namespace mappings setup using the "mapping" Processing Instruction (described abve ). in this example, the <form> element is in the default XML namespace. the default XML namespace is "http://www.microsoft.com/2003/WindowsForms" as specified in the root "wfml" tag:
<Wfml xmlns = "http://www.microsoft.com/2003/WindowsForms "...
The "mapping" Processing Instruction mapped this XML namespace to the system. Windows. Forms. NET Framework namespace:
<? Mapping xmlns = "http://www.microsoft.com/2003/WindowsForms"
Namespace = "system. Windows. forms; system. Drawing"?>
The parser used this mapping to select the form type in system. Windows. Forms rather than the form type in "system. Web. UI. mobilecontrols ".
Collection Heuristics
When the parser encounters a child element, the parser will instantiate the child element based on the mapping rules defined above and then add the instanced element to a collection on the parent (or directly to the parent if the parent is a collection ). by default, the parser looks for a "controls" collection (which is the only thing Windows Forms specific about the parser) but can add children to a different collection using explicit "dot" notation. explicit notation takes the form:
<Menuitem text = "new">
<Property. menuitems>
<Menuitem text = "window" shortcut = "ctrln"/>
<Menuitem text = "-"/>
<Menuitem text = "message"/>
<Menuitem text = "Post"/>
<Menuitem text = "Contact"/>
<Menuitem text = "Internet call"/>
</Property. menuitems>
</Menuitem>
In the example above, the Child menuitems are explicitly added to the "menuitems" collection on the parent "menuitem ". the "property. propertyname "notation is described in more detail in the markup reference below.
Markup reference
The sample parser uses a small set of attributes, elements and processing instructions to control the parsing process. the attributes and elements live in the XML namespace "http://www.microsoft.com/2003/WFML" and typically have a namespace prefix of "wfml ".
Wfml: Id attribute
The ID attribute is used to uniquely name an instance of an element. this ID can be used to reference the instance elsewhere in the markup. in addition, the parser provides an API to query for an instance based on ID (see the parser reference for details ). in the example below, the form is given the name "form1 ".
<? XML version = "1.0" encoding = "UTF-8"?>
<? Mapping xmlns = "http://www.microsoft.com/2003/WindowsForms"
Namespace = "system. Windows. forms; system. Drawing"?>
<Wfml xmlns = "http://www.microsoft.com/2003/WindowsForms"
Xmlns: wfml = "http://www.microsoft.com/2003/WFML">
<Form wfml: Id = "form1" text = "basic sample" size = "300,200">
<Label text = "Hello World" autosize = "true" location = "10, 20"/>
<Method. Show/>
</Form>
</Wfml>
The parser "find" method can be used to retrieve the "form1" instance.
Markupparser parser = new markupparser ();
Parser. parse ("basic. xml ");
Form form1 = (form) parser. Find ("form1 ");
Wfml: Root attribute
The root attribute identifies the instance that is returned from the "parse" method on the parser. there can only be one element with a root attribute. in the example below, "form1" is the "root" element.
<? XML version = "1.0" encoding = "UTF-8"?>
<? Mapping xmlns = "http://www.microsoft.com/2003/WindowsForms"
Namespace = "system. Windows. forms; system. Drawing"?>
<Wfml xmlns = "http://www.microsoft.com/2003/WindowsForms"
Xmlns: wfml = "http://www.microsoft.com/2003/WFML">
<Form wfml: Id = "form1" wfml: Root = "true" text = "basic sample" size = "300,200">
<Label text = "Hello World" autosize = "true" location = "10, 20"/>
<Method. Show/>
</Form>
</Wfml>
The parser "parse" method will return the "form1" instance:
Markupparser parser = new markupparser ();
Form form1 = (form) parser. parse ("basic. xml ");
Wfml: argument attribute
When instancing types, the parser uses the type's default constructor. there are some types such as bitmap that do not have a default constructor. the argument attribute is used to specify a single argument to pass to a non-default constructor. in the example below, the parser will call the bitmap (string) constructor with an argument of "C:/image.jpg ".
<Form wfml: Root = "true" wfml: Id = "form1" name = "form1" text = "Set background">
<Property. backgroundimage>
<Bitmap wfml: argument = "C: // image.jpg"/>
</Property. backgroundimage>
<Method. Show/>
</Form>
Note that it is not possible to invoke constructors will more than one argument.
Property Element
The general rule of parser is XML elements map to types and XML attributes map to properties. In XML, attributes take the form:
<Element attribute = "string value"/>
The parser is able to deal inclutively with strings for non-string properties by using typeconverters (system. componentmodel. typeconverter ). for example, the backcolor property on form is of type "system. drawing. color "but can be set using a string value:
<Form backcolor = "green"/>
In the example above, the backcolor property is converted from "green" to a "system. drawing. color "using a typeconverter. the typecoverter model works well when the property value can be represented as a string (e.g. color) but does not provide a good solution when the property type is more complex (e.g. image ). the parser uses a "dot "(.) property notation to address more complex property sets. "dot" notation takes the form "property. propertyname ". the following example shows how to set the backgroundimage on a form:
<Form wfml: Root = "true" wfml: Id = "form1" name = "form1" text = "Set background">
<Property. backgroundimage>
<Bitmap wfml: argument = "C: // image.jpg"/>
</Property. backgroundimage>
<Method. Show/>
</Form>
In the example above, the backgroundimage property on the parent form is set to the result of the "bitmap" element evaluation.
Method Element
Similar to the "dot" Property notation, the parser supports a "dot" method notation. when the parser sees an element of the form "method. methodname ", it will invoke" methodname "on the parent element instance. in the following example, when the parser Encounters "<method. show/> ", it will invoke the" show "method on the" form1 "instance.
<Form wfml: Root = "true" wfml: Id = "form1" name = "form1" text = "Set background">
<Property. backgroundimage>
<Bitmap wfml: argument = "C: // image.jpg"/>
</Property. backgroundimage>
<Method. Show/>
</Form>
The parser can call both instance and static methods but cannot call methods with more than one argument. The following example shows how to invoke a static method will a single argument:
<Form wfml: Root = "true" wfml: Id = "form1" name = "form1" text = "Set background">
<Property. backgroundimage>
<Method. fromfile static = "system. Drawing. Image">
C: // image.jpg
</Method. fromfile>
</Property. backgroundimage>
<Method. Show/>
</Form>
This callthe static method "system. Drawing. image. fromfile" with an argument of "C:/image.jpg". The form's backgroundimage is set to the method return value.
Wfml: var Element
The parser has limited support for creating and referencing variables. Any element with a wfml: Id attribute can be referenced as a variable using the wfml: var element. For example:
<Form wfml: Id = "form1" name = "form1" text = "simple"/>
<Wfml: var name = "form1">
<Method. Show/>
</Wfml: var>
In the example above, the "wfml: Var" element references the variable named "form1" and the "show" method is invoked on the "form1" instance.
Variable prefix
Attribute values that begin with "$" are treated as variable references.
<Wfml: var wfml: Id = "image">
<Method. fromfile static = "system. Drawing. Image">
C: // image.jpg
</Method. fromfile>
</Wfml: var>
<Form backgroundimage = "$ image" name = "form1" text = "Set background">
<Method. Show/>
</Form>
Mapping Processing Instruction
The "mapping" XML Processing Instruction is used to map XML Namespaces. net Framework namespaces. there may be one or more mapping processing instructions in an XML file. the general form of this instruction is:
<? Mapping xmlns = "xmlnamespace" namespace = ". Net namespaces"?>
The. NET Framework namespace attribute contains a list of one or more namespaces separated by a semi-colon. For example:
<? Mapping xmlns = "mynamespace"
Namespace = "system. Windows. forms; system. Drawing"?>
When instancing types, the parser will map XML elements in the namespace specified by "xmlns" to. NET Framework types in the namespaces specified in the "namespace" attribute.
Assembly Processing Instruction
The "assembly" Processing Instruction adds an Assembly reference to the parser. the "assembly" Processing Instruction is analogous to adding references in Visual Studio or the/reference C # compiler switch. the following example shows how to add a reference to the v1.1. system. data assembly.
<? Assembly name = "system. Data" version = "1.0.5000.0" Culture = "neutral"
Publickeytoken = "b77a5c561934e089"?>
Markup parser reference
The parser dynamically generates an instance tree from a XML file. The parser API is described below.
Parse Method
The parse method takes either a string argument representing a file or an xmltextreader. the parse method returns the instance of the element containing the "wfml: Root" attribute or the instance of the first element if "wfml: Root" is not specified.
Addvariable Method
The parser "addvariable" method adds a named instance to the parser's internal variable context. "addvariable" is used for event handler wire-up as well as to create instances of types too complex for Parser's parsing rules. the following example will generate a form and wire-up the form's load event:
<Form wfml: Root = "true" load = "me. formloadhandler" text = "load sample"/>
The parser will recognize "LOAD" as an event and look for a variable named "me" and wire-up the form's load event to "Me's" formloadhandler. the variable "me" is added to the parser Variable list by using "addvariable ":
Private void button#click (Object sender, system. eventargs E)
{
Markupparser parser = new markupparser ();
// Add "this"
Parser. addvariable ("me", this );
// Parse
Object OBJ = parser. parse ("sample. xml ");
}
Private void formloadhandler (Object sender, eventargs E)
{
MessageBox. Show ("form load ");
}
Find Method
The find method is used to retrieve a named (wfml: ID) Instance value from the parser variable context. for example, the following sample defines a form named "form1" with a button named "button1 ":
<Form wfml: Root = "true" wfml: Id = "form1" text = "find sample">
<Button wfml: Id = "button1"/>
</Form>
The find method can be used to retrieve the Instance value of "button1 ":
Private void button#click (Object sender, system. eventargs E)
{
Markupparser parser = new markupparser ();
// Parse (returns form1)
Object OBJ = parser. parse ("sample. xml"); // get button1 instance
Button button1 = parser. Find ("button1") as button;
}
Reset Method
The Parser's variable context (Variable list) is shared within SS multiple instances of the Parser (static list). The Parser's reset method will clear the shared variable context.
Status event
The Parser's status event provides line by line parsing state information. Note that this event is useful only in debugging scenarios.