与Blog相比,RSS的知名度确实低很多。但是RSS已经越来越被人们所熟知,尽管它还没一个正式的中文名称。许多分析人士已经认识到RSS将要对互联网内容的浏览方法产生巨大影响。
RSS也称为聚合内容,它是一种描述和同步网站内容的格式,是目前使用最广泛的XML应用。RSS搭建了信息迅速传播的一个技术平台,使得每个人都成为潜在的信息提供者。发布一个RSS文件后,这个RSS Feed中包含的信息就能直接被其他站点调用,而且由于这些数据都是标准的XML格式,所以也能在其他的终端和服务中使用。目前,可以获取RSS的软件很多,譬如Outlook想必大家已经再熟悉不过了。
本次C#实践我们就来创建一个完全属于自己版本的RSS阅读器,网友可以利用这个框架和机制来扩展和完善它。

RSS一般具有固定的格式,但是由于历史和版本的原因,RSS的XML文件格式具有好几种。主要有: 1、RSS 1.0 2、RSS 2.0 3、Atom 0.3 Feed 4、Blogroll (OPML)
我们就以 IT168 的RSS 2.0文件格式作为参考来进行程序演示。
创建RSS阅读器的第一步我就是要获取远程RSS聚合内容,一般都是一个以.xml为扩展名的文件。由于获取远程数据比较耗时,为了不让程序失去响应我们继续利用BackgroundWorker异步获取远程数据,代码如下:
private void button1_Click(object sender, EventArgs e)
{
hasError = false;
textBox1.Enabled = listBox1.Enabled = button1.Enabled = false;
backgroundWorker1.RunWorkerAsync(textBox1.Text);
}
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
string rsslink = e.Argument.ToString();
DataSet ds = new DataSet();
try
{
ds.ReadXml(rsslink);
rssData = ds.Tables[2];
}
catch
{
hasError = true;
}
}
private void backgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
if (!hasError)
{
listBox1.Items.Clear();
for (int i = 0; i < rssData.Rows.Count; i++)
{
listBox1.Items.Add(i);
}
}
else
{
listBox1.Items.Clear();
listBox1.Items.Add("error");
}
listBox1.Enabled = true;
textBox1.Enabled = true;
button1.Enabled = true;
}
我们注意到DataSet对象的ReadXml方法,它将 XML 架构和数据读入 DataSet。这样获得的DataSet数据会包含多个表,以IT168的RSS为例,http://rss.it168.com/txt/16.xml被读入到DataSet中会有3个Table:rss、channel和item,我们所感兴趣的内容就全部包含在item表中,这就是目前RSS格式的一种,我们可以读取rss头部关于RSS版本的信息来判断RSS格式,具体实现我们在这里省略。
获取了RSS内容后将其保存在rssData中备用。尤其是item表中title、description和link字段是我们最关心的,它保存了每一条RSS新闻的标题、描述和对应的内容链接。下一步就是将内容显示出来。在这里我们为了更加美观阅读器,我们使用ListBox并对其进行自定义重绘,将RSS中的每一项内容按照不同的字体样式和大小以及内容的多少,重新绘制ListBox的Item并将其绘制出来。代码如下:
private void listBox1_DrawItem(object sender, DrawItemEventArgs e)
{
Graphics gfx = e.Graphics;
if ((listBox1.Items.Count == 1) && (listBox1.Items[0].ToString().Contains("error")))
{
gfx.DrawString(errortext, titleFont, Brushes.SteelBlue, e.Bounds.X + leftMargines, e.Bounds.Y + topMargines);
return;
}
if (listBox1.Items.Count > 0)
{
int number = e.Index + 1;
string strtitle = "";
string strdescription = "";
strtitle = rssData.Rows[e.Index]["title"].ToString();
strdescription = rssData.Rows[e.Index]["description"].ToString();
if ((e.State & DrawItemState.Selected) == DrawItemState.Selected)
{
//Graphics gfx = e.Graphics;
Pen pen = new Pen(Brushes.SteelBlue, 0.4f);
gfx.DrawRectangle(pen, e.Bounds.X + 4, e.Bounds.Y + 4, e.Bounds.Width - 8, e.Bounds.Height - 8);
gfx.FillRectangle(Brushes.LightSteelBlue, e.Bounds.X + 5, e.Bounds.Y + 5, e.Bounds.Width - 9, e.Bounds.Height - 9);
gfx.DrawString(number.ToString() + ".", titleFont, Brushes.Black, e.Bounds.X + leftMargines, e.Bounds.Y + topMargines);
SizeF f1 = gfx.MeasureString(number.ToString() + ".", titleFont);
gfx.DrawString(strtitle, titleFont, Brushes.SteelBlue, e.Bounds.X + leftMargines + f1.Width + 8, e.Bounds.Y + topMargines);
SizeF f11 = gfx.MeasureString(strtitle, titleFont);
Rectangle rect = new Rectangle(e.Bounds.X + leftMargines + (int)f1.Width + 8, e.Bounds.Y + topMargines + (int)f1.Height + rectDistance, this.listBox1.ClientSize.Width - leftMargines - (int)f1.Width - 8, this.ClientSize.Height);
StringFormat stf = new StringFormat();
stf.FormatFlags = StringFormatFlags.FitBlackBox;
gfx.DrawString(strdescription, descFont, Brushes.Black, rect, stf);
}
else
{
gfx.FillRectangle(Brushes.White, e.Bounds.X + 4, e.Bounds.Y + 4, e.Bounds.Width - 7, e.Bounds.Height - 7);
gfx.FillRectangle(Brushes.SteelBlue, e.Bounds.X + 4, e.Bounds.Y + e.Bounds.Height - 8, e.Bounds.Width - 8, 1);
Pen pen = new Pen(Brushes.SteelBlue, 0.4f);
gfx.DrawString(number.ToString() + ".", titleFont, Brushes.Black, e.Bounds.X + leftMargines, e.Bounds.Y + topMargines);
SizeF f1 = gfx.MeasureString(number.ToString() + ".", titleFont);
gfx.DrawString(strtitle, titleFont, Brushes.SteelBlue, e.Bounds.X + leftMargines + f1.Width + 8, e.Bounds.Y + topMargines);
SizeF f11 = gfx.MeasureString(strtitle, titleFont);
Rectangle rect = new Rectangle(e.Bounds.X + leftMargines + (int)f1.Width + 8, e.Bounds.Y + topMargines + (int)f1.Height + rectDistance, this.listBox1.ClientSize.Width - leftMargines - (int)f1.Width - 8, this.ClientSize.Height);
StringFormat stf = new StringFormat();
stf.FormatFlags = StringFormatFlags.FitBlackBox;
gfx.DrawString(strdescription, descFont, Brushes.Black, rect, stf);
}
e.DrawFocusRectangle();
}
}
private void listBox1_MeasureItem(object sender, MeasureItemEventArgs e)
{
Graphics gfx = e.Graphics;
if ((listBox1.Items.Count == 1) && (listBox1.Items[0].ToString().Contains("error")))
{
SizeF serrorstring = gfx.MeasureString(errortext, titleFont);
e.ItemHeight = topMargines + (int)serrorstring.Height + rectDistance + 8 + 8;
return;
}
if (listBox1.Items.Count > 0)
{
int number = e.Index + 1;
string strtitle = "";
string strdescription = "";
strtitle = rssData.Rows[e.Index]["title"].ToString();
strdescription = rssData.Rows[e.Index]["description"].ToString();
SizeF f1 = gfx.MeasureString(number.ToString() + ".", titleFont);
SizeF f11 = gfx.MeasureString(strtitle, titleFont);
Rectangle rect = new Rectangle(leftMargines + (int)f1.Width + 8, topMargines + (int)f1.Height + rectDistance, this.listBox1.ClientSize.Width - leftMargines - (int)f1.Width - 8, e.ItemHeight);
StringFormat stf = new StringFormat();
stf.FormatFlags = StringFormatFlags.FitBlackBox;
SizeF f2 = gfx.MeasureString(strdescription, descFont, rect.Width, stf);
int Temp = e.ItemWidth;
if (f2.Width < (leftMargines + f1.Width + 8 + f11.Width)) Temp = leftMargines + (int)f1.Width + 8 + (int)f11.Width + 4;
e.ItemHeight = topMargines + (int)f1.Height + rectDistance + (int)f2.Height + 8 + 8;
}
}
只有当ListBox的DrawMode 属性设置为 DrawMode.OwnerDrawFixed 或 DrawMode.OwnerDrawVariable 时,才会激活MeasureItem事件,其次是DrawItem事件。首先,在绘制之前可以使用MeasureItem事件确定Item项的大小,然后激活DrawItem事件开始实际绘制操作。
在初始化主窗体时,我们先定义好所有相关变量和对象,如下:
private const string errortext = "获取服务器信息失败,请检查网络连接是否正常。";
private bool hasError = false;
private DataTable rssData;
private Font titleFont, descFont;
private int leftMargines;
private int topMargines;
private int rectDistance;
public Form1()
{
InitializeComponent();
titleFont = new Font("Microsoft Sans Serif", 10, FontStyle.Bold);
descFont = new Font("Microsoft Sans Serif", 10, FontStyle.Regular);
leftMargines = 8;
topMargines = 10;
rectDistance = 12;
}
最后,我们可以为ListBox的DoubleClick事件添加相应代码,以便当双击Item时可以启动浏览器打开对应的链接。因为ListBox中项的索引和rssData的item表的记录数是一一对应的,我们可以很方便的在它们之间建立关联并导航链接。代码如下:
private void listBox1_DoubleClick(object sender, EventArgs e)
{
string linkurl = rssData.Rows[listBox1.SelectedIndex]["link"].ToString();
System.Diagnostics.Process.Start("iexplore.exe", linkurl);
}
至此,属于我们自己版本的多线程RSS新闻阅读器制作完毕。虽然这仅仅是个思路和框架,细节之处不够完善,但是已经具备了大部分基本功能,有兴趣的网友可以继续扩展。