65.9K
CodeProject 正在变化。 阅读更多。
Home

MyXaml 编写的 RSS 2.0 博客阅读器

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.57/5 (14投票s)

2004 年 4 月 14 日

9分钟阅读

viewsIcon

132203

downloadIcon

1

MyXaml 编写的 RSS 2.0 博客阅读器

目录

引言

我正在查看 Joe Marini 的 XAML 编写的博客阅读器,我当时想,为什么还要等 Longhorn 呢,我可以用 MyXaml 来做!但并不那么容易,因为我需要在 MyXaml 中实现一些尚不存在的功能,更糟糕的是,还需要实现一个拥有自己绘制列表框,才能实现那种漂亮的博客标题多行渲染。不过,修改所需的时间也不过一天左右,效果非常好。

Sample image

什么是 MyXaml

如果您还不熟悉 MyXaml,可以阅读我关于它的第一篇文章 这里,或者访问我的网站 http://www.myxaml.com/。基本上,MyXaml 是一个通用的类实例化器,能够初始化属性、将事件连接到事件处理程序、深入属性集合,并且可通过内联或代码隐藏进行自定义,与语言无关,运行时 JIT 汇编,所有这些都由 XML 标记驱动。这意味着使用 MyXaml

  • 您可以通过标记而不是代码来实例化表示层组件,例如窗体和控件。
  • 您可以初始化颜色、状态、标题和集合等属性。
  • 您可以编写内联代码,这些代码会与窗体一起汇编和实例化——处理事件、初始化数据集等。您可以使用 .NET 支持的任何语言来编写这些代码。
  • 您还可以与编译时代码集成。
  • 而且,没有任何理由说您只能用它来处理表示层组件。想通过标记实例化一个数据库连接吗?没问题。

RSS 阅读器

实现阅读器需要创建几个类来辅助 .NET。

首先,需要一个 XmlDataSource 类,它有一个自定义类型转换器,可以将 XPath 语句返回的节点列表转换为 DataTable

其次,需要一个 DataSource 类,它有一个自定义类型转换器,可以将 DataTable 转换为 System.Object。为什么?因为 ListBoxDataSource 属性接受 System.Object 类型,我们需要告诉解析器它可以专门将 MyXaml.Extensions.DataSource 实例转换为 System.Object 类型,该类型实际上返回 DataSource 类中包含的 DataTable 实例。我们这样做是因为我们无法直接实例化一个有意义的 DataTable 实例。

需要一个拥有自己绘制的 ListBox,因为原生列表框只显示每项一行,而我需要一个显示多行的控件——博客条目的标题、日期和 URL,并且还能实现那种酷炫的渐变背景填充。

最后,需要一个自定义的 Link 处理程序,因为 .NET 的 LinkLabel 工作方式并不那么简单。

XML 头部

首先,让我们看一下 XML 头部中的各个元素。

<?xml version="1.0" encoding="utf-8"?>
<MyXaml xmlns="System.Windows.Forms"
        xmlns:def="Definition">

第一行描述了 xml 标记版本和编码格式。这很直接。

第二行定义了根节点 (MyXaml),并声明使用了两个命名空间。第一个是默认命名空间(因为它没有前缀),分配给 System.Windows.Forms 命名空间。在 MyXaml 中,可以使用部分名称来标识程序集(这是一个相当新的功能)。我特意没有实现 Longhorn XAML 引用中你会找到的 CLR 命名空间到 xml 命名空间的映射,因为我觉得没有必要。

实例化窗体

MyXamlLoader 是一个自动搜索和加载具有 .myxaml 扩展名的文件的实用程序,它总是期望应用程序的窗体被标识为“AppMainForm”标签。

<Form Name='AppMainForm'
      ClientSize='800, 600' 
      StartPosition='CenterScreen' 
      Text='MyXaml RSS Reader Demonstration'>

正如你所见,Form 实例的四个属性正在被初始化。

一些辅助方法

点击 LinkLabel 不会自动调用浏览器。在博客阅读器中有两个地方可以点击链接——头部中的链接,以及每个博客条目中的链接。要调用浏览器,需要一些内联代码。

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

在 MyXaml 中,有“代码隐藏”的两种形式。一种是刚才演示的例子:直接嵌入到 xml 标记中的内联代码。另一种是引用一个单独的代码文件。除此之外,任何 MyXaml 需要连接的事件的目标都可以通过调用解析器来指定,或者通过向事件目标集合添加一个目标来指定。由于我们使用的是通用加载程序,并且没有使用其他程序集,因此唯一的选择是内联代码或单独的代码文件。

MyXaml 维护一个事件目标堆栈。每个 xml 元素都可以有自己的内联或单独的代码文件,在该代码中实例化的任何类都仅限于该元素及其子级。MyXaml 将自动实例化它在内联代码或代码文件中找到的所有具有无参数构造函数的类。只要您使用唯一的方法名作为事件处理程序,就不需要告诉 MyXaml 要连接到哪个实例——解析器会自动找出。如果您确实需要告诉 MyXaml 类实例,您可以在标记中用类名后跟一个点 ('.') 来前缀事件的方法名。

实例化全局 XmlDataSource

有趣的部分现在开始。如前所述,MyXaml 是一个通用实例化器。它不局限于特定的 CLR 对象,也不实现任何自定义解析(除了在必要时绕过一些 .NET 限制)。创建 MyXaml 兼容的程序集有一些基本规则,我在 这里 已经写过。

实际发生的第一个事情是实例化 XmlDataSource

<XmlDataSource
   def:Name="NewsData"
   Source="<a href="http://msdn.microsoft.com/rss.xml">http://msdn.microsoft.com/rss.xml</a>"
   XPath="rss/channel"/>

这里,Name 属性有一个“def:”前缀,这告诉 MyXaml 将对象添加到可以被其他地方引用的对象集合中。实现了延迟绑定,因为 MyXaml 首先实例化所有标签,深入到子节点,然后,当堆栈弹出时,为每个标签分配属性。

Source 属性指定了 xml 源,在本例中是硬编码到 MSDN 网站。

XPath 属性指定了基路径,它限定了稍后使用的所有其他子路径。请记住,此格式是针对 RSS 2.0 的。

实例化全局 DataSource

下一步是提取博客条目。这是通过创建一个 MyXaml.Extensions.DataSource 对象来实现的。

<DataSource def:Name="Items" Source="{NewsData; Path=item}"/>

这里,Source 属性指定了 XmlDataSource 对象(注意使用 {} 来引用全局对象集合中的对象)。可以向引用的对象传递额外的限定符,这通常会影响返回的数据。在本例中,“Path”字段为前面定义的 XPath 基路径提供了额外的限定符。

XmlDataSource 的实现需要弄清楚一些事情。首先,它必须实现一个自定义类型转换器,这是通过 TypeConverter 属性来实现的。

namespace MyXaml.Extensions
{
  [TypeConverter(typeof(XmlDataSourceTypeConverter))]
  public class XmlDataSource
  {
     ...

解析器会检查引用的对象是否可以转换为属性所期望的类型。在这种情况下,XmlDataSource 需要转换为 DataTable 类型,因为 MyXaml.Extensions.DataSource 类就是这样定义“Source”属性的。

namespace MyXaml.Extensions
{
  [TypeConverter(typeof(DataSourceTypeConverter))]
  public class DataSource
  {
    protected DataTable source;
    protected string field;

    public DataTable Source
    {
      get {return source;}
      set {source=value;}
    }
  ...

回到 XmlDataSource 类,类型转换器实现了两个方法:CanConvertToConvertTo

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;
}

正如你所见,转换器会根据目标类型(StringDataTable)执行不同的操作!

显示博客描述

一旦创建了 xml 数据源并得到了 DataTable,我们就可以开始显示博客条目了。通过利用 ListBox 的数据绑定功能,我们可以使用 .NET 在列表中选择某个项目时自动更新描述。此标记正是这样做的。

<Panel Dock="Fill" BackColor="White">
  <Controls>
    <TextBox Multiline="true" Dock="Fill" Font="MS Sans Serif, 12pt">
      <DataBindings>Text, Items, description</DataBindings>
    </TextBox>
  </Controls>
</Panel>

它实例化了一个包含 TextBoxPanelTextBox 的 Text 属性绑定到 Items 集合的“description”字段。为了实现这一点,DataSource 类需要一个类型转换器,以便在引用 DataSource 对象时返回包含的 DataTable

显示标题

同样,博客标题是直接从 XmlDataSource 解析的。因为我们只对单个值返回(String 对象)感兴趣,所以 XmlDataSource 的类型转换器期望 Path 限定符的结果查询返回单个节点。

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

这里,Text 被赋给了由指定的 Path 值限定的 XmlDataSource 对象,而将 XmlDataSource 转换为 String 的类型转换器则完成了实际工作。

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;
}

博客条目

最后,也是最有趣的部分,是拥有自己绘制的 ListBox

<StyledListBox Dock="Left" Width="390" BackColor="White"
    BorderStyle="Fixed3D" DataSource="{Items}" VisualStyle="Entries"
    Margin="10" DoubleClick="OnGotoBlogEntry"/>

同样,将 MyXaml.Extensions.DataSource 实例转换为 DataTable 实例的类型转换器被用到了,这次是在 ListBox</ODE>DataSource 属性中。

VisualStyle 属性是我实现 Longhorn 的 VisualTree 的一种简陋方式(在我看来,这是一种自定义解析的糟糕做法,破坏了标记解析器的通用性)。无论如何,此属性告诉拥有自己绘制的控件应该如何精确地绘制每个项目。“Entries”的定义与其他控件的定义方式相同。

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

请注意,这里的 Text 属性 **不** 使用 {} 来引用对象。如果使用了,当拥有自己绘制的 ListBox 实例化控件时,该引用就会被解析!相反,拥有自己绘制的 ListBox “知道” Text 属性将引用一个 DataTable,并会在内部解析该引用。为了保持一致性,语法保持不变,但没有 {}

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;
  }
}

正如我所提到的,这是 VisualTree 的一个简陋实现。 .NET 没有能力让控件在任何表面上自行渲染(这就是为什么我们需要 Longhorn!),因此 StyledListBox 的实现仅限于解析 TextBox 可视样式元素。当然,如果复选框等控件也被拥有自己绘制,它也可以扩展到这些类型。但它仍然相当复杂,因为它会测量每个项目所需的空间。

结论

就这样!整个阅读器都用 xml 标记实现,并且在内联声明了几个辅助函数。XmlDataSourceDataSourceStyledListBox 类虽然实现得比较基础,但它们是一些不错的通用扩展的开端。整个过程展示了使用 eXtensible Application Markup Language (XAML) 实现接口的灵活性,或者说 Xml Application Markup Language?

MyXaml 设计器

要查看更多有趣的示例(例如读取资源),请查看 MyXaml 设计器,可在 http://www.myxaml.com/ 下载(屏幕截图 此处)。不幸的是,源代码不可用,因为它使用了 Actipro Software 语法编辑器,该编辑器是按开发人员座位许可的。但 MyXaml 标记是可见的,它演示了资源以及您可以使用 MyXaml 做的一些其他很酷的事情。

许可证

本文未附加明确的许可证,但可能在文章文本或下载文件本身中包含使用条款。如有疑问,请通过下面的讨论区联系作者。

作者可能使用的许可证列表可以在此处找到。

© . All rights reserved.