(c) 2005 Marc Clifton All Rights Reserved.
I was taking a look at Joe Marini's Blog
Reader Built in XAML, and I thought, why wait for Longhorn, I can do that
with MyXaml! But it wasn't that easy, as I needed to implement some
features in MyXaml that didn't yet exist, and even worse, implement an owner
draw list box so I could get that nice multi-line rendering of blog
titles. Still, it didn't take more than a day so to make the necessary
modifications, and the results are very nice:

If you aren't already familiar with MyXaml, you can visit my website http://www.myxaml.com/. Basically,
MyXaml is a general class instantiator capable of initializing properties,
wiring up events to event handlers, and drilling down into property collections,
customizable with inline or code-behind, language non-specific, JIT assembling
at runtime, all driven by xml markup. This means that with MyXaml:
- You can instantiate presentation layer components such as forms and
controls through markup rather than code.
- You can initialize properties like color, state, captions, and
collections.
- You can write in-line code that is assembled and instantiated along with
the form--handle events, initialize data sets, and so forth. You can write
this code in whatever language is supported by .NET
- You can tie in to compile-time code also.
- And, nothing says you are limited to doing this with presentation layer
components. Want to instantiate a connection to a database specified using
markup? No problem.
Implementing the reader required creating a few classes to help .NET
along.
First, a XmlDataSource class with a custom type converter to
convert a node list returned from an XPath statement to a DataTable
is needed.
Second, a DataSource class with a custom type converter to
convert a DataTable to a System.Object is
needed. Why? Because the DataSource property of a
ListBox takes a System.Object type, and we need to
tell the parser that it can specifically convert a
MyXaml.Extensions.DataSource instance to a
System.Object type, which actually returns the
DataTable instance contained in the DataSource
class. And we do that because we can't directly instantiate a meaningful
DataTable instance.
An owner draw ListBox is required because natively a list box
only displays one line per item, and I needed something that displayed multiple
lines--the caption, date, and URL of the blog entry, plus doing that cool
gradient background fill.
And finally, a custom Link handler because the way .NET's
LinkLabel works is not exactly simple.
First, let's look at the elements of the Xml header:
<?xml version="1.0" encoding="utf-8"?>
<MyXaml xmlns="System.Windows.Forms"
xmlns:def="Definition">
The first line describes the xml markup version and encoding format.
That's straightforward enough.
The second line defines the root node (MyXaml), and establishes that two
namespaces are being used. The first is the default namespace (as there is
no prefix), and is assigned to the System.Windows.Forms
namespace. In MyXaml, an assembly can be identified using a partial name
(this is a fairly new feature). I specifically chose not to implement the
CLR namespace to xml namespace mapping that you'll find in the Longhorn XAML
references because I just didn't think it was necessary.
The MyXamlLoader, a utility for automatically searching and loading files
with the .myxaml extension, always expects the application's form to be
identified with the "AppMainForm" tag:
<Form Name='AppMainForm'
ClientSize='800, 600'
StartPosition='CenterScreen'
Text='MyXaml RSS Reader Demonstration'>
As you can see, four properties of the Form instance are being
initialized.
Clicking on a LinkLabel does not automatically invoke the browser.
There are two places that a link can be clicked in the blog reader--the link in
the header, and the link for each blog entry. To invoke the browser, a
little inline code is required:
<def:Code language='C#'>
<reference assembly="System.Windows.Forms.dll"/>
<reference assembly="myxaml.dll"/>
<![CDATA[
using System;
using System.Windows.Forms;
using MyXaml;
using MyXaml.Extensions;
class Helpers
{
public void OnLinkClicked(object sender, LinkLabelLinkClickedEventArgs e)
{
LinkLabel ll=(LinkLabel)sender;
int idx=ll.Links.IndexOf(e.Link);
ll.Links[idx].Visited=true;
System.Diagnostics.Process.Start((string)e.Link.LinkData);
}
public void OnGotoBlogEntry(object sender, EventArgs e)
{
StyledListBox slb=(StyledListBox)sender;
string url=slb.SubItem(2);
System.Diagnostics.Process.Start(url);
}
}
]]>
</def:Code>
In MyXaml, there are two forms of "code-behind". One is the example
just illustrated: in-line code built directly into the xml markup. The
second is a reference to a separate code file. In addition to this, the
event target of any events that MyXaml needs to wire-up can be specified when
invoking the parser or by adding a target to the event target collection.
Since we're using the generic loader, and there's no other assemblies being
used, the only option is inline or a separate code file.
MyXaml maintains a stack of event targets. Each xml element can have
it's own inline or separate code file, and any class instantiated in that code
are local to that element and its children. MyXaml will automatically
instantiate all classes with a parameter-less constructor that it finds in the
in-line or code file. As long as you use method names for the event
handlers that are unique, you don't need to tell MyXaml to which instance to
wire up the event--the parser figures it out automatically. If you do need
to tell MyXaml the class instance, you can prefix the event's method name with
the class name followed by a dot ('.') in the markup.
The fun part begins now. As mentioned earlier, MyXaml is a general
purpose instantiator. It isn't restricted to certain CLR objects and it
doesn't implement any custom parsing (except to get around some .NET limitations
where necessary). There are a few basic rules to creating a
MyXaml-friendly assembly, which I've written about here.
The first thing that really happens is that the XmlDataSource is
instantiated:
<XmlDataSource
def:Name="NewsData"
Source="http://msdn.microsoft.com/rss.xml"
XPath="rss/channel"/>
Here, the Name attribute has a "def:" prefix, which tells MyXaml to add the
object to an object collection that can be referenced elsewhere. Late
binding is implemented because MyXaml first instantiates all tags, drilling down
into child nodes, then, as the stack is popped, the attributes for each tag are
assigned.
The Source attribute specifies the xml source, in this case hardcoded to the
msdn website.
The XPath attribute specifies the base path with qualifies all other
sub-paths used later on. Remember, this format is for RSS 2.0.
The next step is to extract the blog entries. This is accomplished by
creating a MyXaml.Extensions.DataSource object:
<DataSource def:Name="Items" Source="{NewsData; Path=item}"/>
Here, the Source attribute specifies the XmlDataSource object
(note the use of the {} to reference objects in the global object
collection). Additional qualifiers can be passed to the referenced object
which typically affects the data that is being returned. In this
particular case, the "Path" field provides additional qualifiers to the XPath
base path defined earlier.
The implementation for the XmlDataSource needs to figure out a view
things. First off, it has to implement a custom type converter, which is
accomplished using a TypeConverter attribute:
namespace MyXaml.Extensions
{
[TypeConverter(typeof(XmlDataSourceTypeConverter))]
public class XmlDataSource
{
...
The parser checks to see if the referenced object can be converted to the
type expected by the attribute. In this case, the
XmlDataSource needs to be converted to a DataTable type, because
this is how the MyXaml.Extensions.DataSource class defines the
"Source" property:
namespace MyXaml.Extensions
{
[TypeConverter(typeof(DataSourceTypeConverter))]
public class DataSource
{
protected DataTable source;
protected string field;
public DataTable Source
{
get {return source;}
set {source=value;}
}
...
Going back to the XmlDataSource class, the type converter
implements two methods: CanConvertTo and
ConvertTo.
public class XmlDataSourceTypeConverter : TypeConverter
{
public override bool CanConvertTo(ITypeDescriptorContext context, Type t)
{
if (t.FullName=="System.String") return true;
if (t.FullName=="System.Data.DataTable") return true;
return false;
}
public override object ConvertTo(ITypeDescriptorContext context,
System.Globalization.CultureInfo culture, object value,
Type destinationType)
{
object ret=null;
XmlDataSource xds=(XmlDataSource)value;
if (destinationType.FullName=="System.Data.DataTable")
{
ret=xds.LoadDataTable();
}
else
{
ret=xds.ToString();
}
return ret;
}
As you can, depending on the target type (a String or a
DataTable), the converter does something different!
Once the xml data source has been created and a DataTable is
available, we can start working with displaying the blog entries. By
leveraging the ListBox data binding feature, we can use .NET to
automatically update the description whenever an item is selected in the
list. This markup does just that:
<Panel Dock="Fill" BackColor="White">
<Controls>
<TextBox Multiline="true" Dock="Fill" Font="MS Sans Serif, 12pt">
<DataBindings>Text, Items, description</DataBindings>
</TextBox>
</Controls>
</Panel>
It instantiates a Panel containing a TextBox.
The TextBox's Text property is bound to the "description" field of
the Items collection. In order to accomplish this, the
DataSource class needs a type converter so that the contained
DataTable is returned when the DataSource object is
being referenced.
Similarly, the blog caption is parsed directly from the
XmlDataSource. Because we're interested in a single value
return (String objects), the type converter for
XmlDataSource expects that the Path qualifier results in a query
that returns a single node.
<Panel Dock="Top" Height="100" BackColor="LightGray">
<Controls>
<Label Location="0, 0" Size="800, 40"
Font="MS Sans Serif, 24pt, style=Bold"
Text="{NewsData; Path=title}"/>
<Label Location="0, 40" Size="800, 40"
Font="MS Sans Serif, 12pt"
Text="{NewsData; Path=description}"/>
<LinkLabel Location="0, 80" Size="800, 20"
Font="MS Sans Serif, 10pt"
Text="{NewsData; Path=link}" LinkClicked="OnLinkClicked">
<Links>
<Link LinkData="{NewsData; Path=link}"/>
</Links>
</LinkLabel>
</Controls>
</Panel>
Here, the Text is being assigned to the
XmlDataSource object qualified by the designated Path values, and
the type converter that converts the XmlDataSource to
String does the real work:
public override string ToString()
{
string ret=String.Empty;
XmlNodeList nodes;
try
{
nodes=document.SelectNodes(xpath+"/"+path);
if (nodes.Count==1)
{
ret=nodes[0].InnerText;
}
}
catch(Exception e)
{
Trace.WriteLine(e.Message);
}
return ret;
}
The final and most interesting part is the owner draw
ListBox.
<StyledListBox Dock="Left" Width="390" BackColor="White"
BorderStyle="Fixed3D" DataSource="{Items}" VisualStyle="Entries"
Margin="10" DoubleClick="OnGotoBlogEntry"/>
Again, the type converter that takes a
MyXaml.Extensions.DataSource instance and converts it to a
DataTable instance is being utilized, this time in the
</ODE>DataSource attribute of the ListBox.
The VisualStyle attribute is my poor man's way of implementing Longhorn's
VisualTree (which, in my opinion, is an abomination of custom parsing that
breaks the generic nature of the markup parser). In any case, this
attribute tells the owner draw control how exactly it should draw each
item. The "Entries" are defined just like any other controls:
<Panel Name="Entries">
<Controls>
<TextBox Font="MS Sans Serif, 12pt"
Text="Items; Field=title" WordWrap="true"/>
<TextBox Font="MS Sans Serif, 9pt"
Text="Items; Field=pubDate" ForeColor="Gray" WordWrap="true"/>
<TextBox Font="MS Sans Serif, 9pt"
Text="Items; Field=link" WordWrap="false"/>
</Controls>
</Panel>
Note that the Text attribute here does not use the {} to reference an
object. If it did, the reference would be resolved when the owner drawn
ListBox instantiates the controls! Rather, the owner drawn ListBox "knows"
that the Text property is going to be referencing a DataTable and resolves the
reference internally. The syntax stays the same for consistency, but
without the {}.
protected void GetText(Graphics gr, Control ctrl, int index, out string text,
out int height)
{
text=ctrl.Text;
height=0;
bool wordWrap=false;
wordWrap=((TextBox)ctrl).WordWrap;
string dataSourceName=StringHelpers.LeftOf(text, ';').Trim();
string field=StringHelpers.RightOf(text, '=').Trim();
Parser parser=Parser.CurrentInstance;
object ds=parser.GetReference(dataSourceName);
DataTable dt=null;
TypeConverter targetTypeConverter=TypeDescriptor.GetConverter(ds.GetType());
if (targetTypeConverter.CanConvertTo(typeof(DataTable)))
{
dt=(DataTable)targetTypeConverter.ConvertTo(ds, typeof(DataTable));
}
if (dt != null)
{
text=(string)dt.Rows[index][field];
SizeF sf;
if (wordWrap)
{
sf=gr.MeasureString(text, ctrl.Font, Width-20);
}
else
{
sf=gr.MeasureString(text, ctrl.Font);
}
height=(int)sf.Height+1;
}
}
As I mentioned, this is a poor man's implementation of a VisualTree.
.NET doesn't have the ability for controls to render themselves on any surface
(this is why we need Longhorn!) and therefore the implementation of the
StyledListBox is limited to parsing TextBox visual
style elements. Of course, it could be extended to other types, such as
checkboxes, etc., if these were owner drawn as well. But still, it's
pretty sophisticated, in that it measures the amount of space that each item
requires.
That's it! The entire reader is implemented in xml markup with a couple
helper functions declared in-line. The XmlDataSource,
DataSource, and StyledListBox classes, while
rudimentary in their implementation, are the beginnings of some nice generic
extensions. The whole thing shows the flexibility of implementing
interfaces with an eXtensible Application Markup Language (XAML), or was that
Xml Application Markup Language?
To see some more interesting examples (such as reading resources), check out
the MyXaml Designer, downloadable at http://www.myxaml.com/ (screen shots here).
Unfortunately, the source is not available because it's written using the Actipro Software
syntax editor, which is licensed per developer seat. But the MyXaml markup
is viewable and demonstrates Resources and a couple other nifty things you can
do with MyXaml.