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

与Blog相比,RSS的知名度确实低很多。但是RSS已经越来越被人们所熟知,尽管它还没一个正式的中文名称。许多分析人士已经认识到RSS将要对互联网内容的浏览方法产生巨大影响。

RSS也称为聚合内容,它是一种描述和同步网站内容的格式,是目前使用最广泛的XML应用。RSS搭建了信息迅速传播的一个技术平台,使得每个人都成为潜在的信息提供者。发布一个RSS文件后,这个RSS Feed中包含的信息就能直接被其他站点调用,而且由于这些数据都是标准的XML格式,所以也能在其他的终端和服务中使用。目前,可以获取RSS的软件很多,譬如Outlook想必大家已经再熟悉不过了。

本次C#实践我们就来创建一个完全属于自己版本的RSS阅读器,网友可以利用这个框架和机制来扩展和完善它。
 
bb

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新闻阅读器制作完毕。虽然这仅仅是个思路和框架,细节之处不够完善,但是已经具备了大部分基本功能,有兴趣的网友可以继续扩展。
 
本文在 Visual Studio 2008 SP1 + Windows XP SP3 下编译调试通过。

【下载本文附带源码】


fj.png1.jpg

来自 “ ITPUB博客 ” ,链接:http://blog.itpub.net/14325734/viewspace-545455/,如需转载,请注明出处,否则将追究法律责任。

转载于:http://blog.itpub.net/14325734/viewspace-545455/

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值