|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Announcements
Want a new Job?
Chapters
Services
Feature Zones
|
ContentsIntroductionI 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:
What Is MyXamlIf 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:
The RSS ReaderImplementing the reader required creating a few classes to help .NET along. First, a Second, a An owner draw And finally, a custom The Xml HeaderFirst, 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 Instantiating The FormThe 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 Some Helper MethodsClicking 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. Instantiating A Global XmlDataSourceThe 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 def:Name="NewsData" Source="http://msdn.microsoft.com/rss.xml">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. Instantiating A Global DataSourceThe next step is to extract the blog entries. This is accomplished by
creating a <DataSource def:Name="Items" Source="{NewsData; Path=item}"/> Here, the Source attribute specifies the 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
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 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 Displaying The Blog DescriptionOnce the xml data source has been created and a <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 Displaying The CaptionSimilarly, the blog caption is parsed directly from the
<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 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 EntriesThe final and most interesting part is the owner draw
<StyledListBox Dock="Left" Width="390" BackColor="White" BorderStyle="Fixed3D" DataSource="{Items}" VisualStyle="Entries" Margin="10" DoubleClick="OnGotoBlogEntry"/> Again, the type converter that takes a
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
ConclusionThat's it! The entire reader is implemented in xml markup with a couple
helper functions declared in-line. The MyXaml DesignerTo 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.
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||