Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

An RSS 2.0 Blog Reader Written In MyXaml

0.00/5 (No votes)
13 Apr 2004 1  
An RSS 2.0 Blog Reader Written in MyXaml

Contents

Introduction

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 or so to make the necessary modifications, and the results are very nice:

Sample image

What is MyXaml

If you aren't already familiar with MyXaml, you can read my original article on it here, or 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.

The RSS Reader

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.

The XML Header

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.

Instantiating the Form

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.

Some Helper Methods

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 are 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 its 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.

Instantiating a Global XmlDataSource

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="<a href="http://msdn.microsoft.com/rss.xml">http://msdn.microsoft.com/rss.xml</a>"
   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.

Instantiating a Global DataSource

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!

Displaying the Blog Description

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.

Displaying the Caption

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 Blog Entries

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.

Conclusion

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?

MyXaml Designer

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.

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.

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