Using the Windows Forms
XML Parser Sample
by Joe Stegman
Download the sample
Table of Contents
Introduction
Basic Sample
Markup Reference
Markup Parser Reference
Introduction
This is a sample of an extensible mechanism 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 to .NET
Framework types and XML attributes map to Type properties (or events)”.
This sample includes a markup parser that dynamically generates an object
instance tree from an XML file. The markup
format includes 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
This is only
a sample and will not be part of the next version of Windows Forms. In addition,
this sample was written against the .NET Framework version 1.1 and has not been
tested against other versions.
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 “sample.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 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 “http://www.microsoft.com/2003/WindowsForms”
will map to types in the .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 includes 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 to .NET Framework Namespace mappings
setup using the “mapping” processing instruction (described above).
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.
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 effectively 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 calls
the 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 to .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"?>
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
button1_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
button1_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 across 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.