自己动手制作多线程RSS新闻阅读器

与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新闻阅读器制作完毕。虽然这仅仅是个思路和框架,细节之处不够完善,但是已经具备了大部分基本功能,有兴趣的网友可以继续扩展。

自己动手制作多线程RSS新闻阅读器

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

滚动到顶部