寻找Zihuatanejo

Starter Kit系列(一):Screen Saver Starter Kit

如同标题显示的,这只是一篇使用介绍,未涉及源码分析。

 

如果你英文够好,看这篇文章可能有些多余,因为完全依照Starter kit的文档所写,你可以自己看那个英文文档。对我自己算是看了实验以后的一点记录。

 

这个Starter Kit是一个屏幕保护程序,跟普通的窗体稍微有些不同,但大家都知道屏保的一些行为,比如鼠标或者键盘有所动作,屏保就会自动关闭。

 

程序使用的相关技术有:

1.GDI+:在屏保上绘制图象跟文本

2.System.XML:其实这个算是这个屏保程序的最大特点,从网络上的Rss Feed或者文本信息显示在屏保上,大家都知道Rss使用的是XML格式,使用这个命名空间的相关类操作也是很自然的事情

4.The WebClient class:从网络上下载信息(由于要从网络上下载信息,所以如果网络不通畅,程序打开的时候会有一段时间没有响应)

5.RSS:包括RSS feeds, channels and items

6.C#2.0语言特性:泛型,这个肯定是要用地…..

 

编译运行

程序是Ready to Run的,你按F5就可以编译运行了,默认的Rss Feed是从微软的网站上的,稍后介绍如何更改,程序自带的图片也不是很好看,呵呵

 

修改代码

显示额外的一行文本:

  1. 找到 Solution Explorer 窗口找到 ScreenSaverForm.cs 查看它的代码
  2. 找到 OnPaint 方法
  3. 加入以下代码:
    e.Graphics.DrawString("<Your name>C# Screen Saver", rssView.TitleFont, Brushes.Orange, new PointF(00));
  4. F5运行程序

 

使用Options Dialog Box

屏保使用命令行参数来显示选项窗口,如果我们要在IDE中使用命令行参数运行程序,可以使用如下步骤:

  1. 打开 Solution Explorer双击 Screen Saver project下的Properties节点.
    然后我们就可以配置我们的工程了。
  2. 点击 Debug 标签找到 Command Line Arguments 文本框.
  3. 键入 /c 到文本框.
    这样就可以让 Visual C# 使用 /c 命令行参数运行程序.
  4. 按 F5 运行程序.
    选项窗口就出现了.
  5. 然后你可以自己输入Rss Feed的地址和图片的保存目录(如:http://www.cnblogs.com/rss.aspx 或者http://blog.joycode.com/MainFeed.aspx,博客园的rss第一次成功了,后面几次都失败了,可能是我网络的问题,在google的个性化主页上,博客园的rss总是失败,但是博客堂的就可以,不知道为什么)
  6. 点击OK关闭选项窗口.

更改以后,我们把参数换成/s就可以看到我们自定义以后的屏保效果了。

 

把屏保安装到你的电脑上

1.    保存好目前的项目,File – SaveAll

2.    使用 Build - Build Solution创建屏保的可执行程序.

3.    在项目文件夹的Debug目录找到屏保的Debug版本的可执行程序,默认为ScreenSaver1.exe,把后缀名改为scr,即ScreenSaver1.scr

4.    然后选中这个文件点击右键选择安装

5.    点击预览就可查看

6.    点击配置可以运行选项窗口让我们自定义Rss Feed和图片保存目录

确定以后就把我们的屏保安装在我们的电脑上了。

 

扩展

程序运行以后是自动按次序显示各Item的内容的,这让心急的我们很是苦恼,所以我们可以自己扩展程序,使用键盘控制Item选择,步骤如下:

1.    Solution Explorer, 右击 ScreenSaverForm.cs 选者View Designer

2.    在属性的下拉框选者ScreenSaverForm

3.    点击事情按钮(那个有闪电图标的按钮)]

4.    找到KeyDown事件,双击(其实我们发现已经有事件处理方法了)

使用以下代码代替 Close();

switch (e.KeyCode)

            
{

                
case Keys.Down:

                     
// Rotate down through the articles.

                     rssView.NextArticle();

                     
break;

                 
case Keys.Up:

                     
// Rotate up through the articles.

                     rssView.PreviousArticle();

                     
break;

                 
default
                     Close();

                     
break;

             }
  

保存文件,F5运行

这是我们就可以使用按钮控制Item选择了。

更多提示

这个屏保被设计为非常容易自定义和扩展,以下想法可以帮助你自己扩展程序:

  • 选择你自己的内建默认图片:
    你自己选择默认图片
  • 在右上角显示天气信息:
    使用web service比如说这个来取得天气信息,然后显示在屏保的右上角。 (这个俺不会

     -_-~~)

  • 创建你自己的屏保:
    把 ScreenSaverForm 替换为你自己的Windows Form来显示一个完全不同的屏保,你可以考察一些系统的屏保来获取一些灵感,然后你可以继续使用Program.cs 文件来操作命令行参数来支持新的屏保
  • 多个 RSS feeds:
    扩展选项窗口来让你的用户选择一个Rss feeds集合,你可以使用以下想法:
    1. 在选项窗口让用户选择一个OPML文件,这个文件保存着一个rss feeds列表 
    -或者-
    2. 
    在选项窗口让用户来组合一个列表的rss feed
    一个随机的news feed可以在屏保运行的时候被选择
  • 完成RSS Class的实现:
    Starter Kit提供的Rss 类并不支持整个 RSS 2.0 specification剩下的任务可以由你完成,然后你可以把这个类封装为dll,以便在另一个程序中使用
  • 重用Rss classes:

    Rss classes可以在另一个程序中重用。你可以在写你自己RSS阅读器或者创建一个系统任务栏程序每隔一个小时弹出一篇文章。

    在项目文件中,我们可以看到有一个Rss文件夹,里面有三个文件,保存有三个类,分别是RssChannelRssFeedRssItem

     

    通过查看代码,我们可以了解到,基本数据流程是这样的,首先从网络上的Rss2.0 XML文件得到XML格式的数据,然后使用XML命名空间中的相关类进行解析,提取出我们所需要的数据保存在内存中。

     

    首先我们考察RssFeed类,它的类结构是这样的:
    rssfeed.jpg

    其中有三个方法,使用了factory pattern 。(关于这个模式,请参考吕老师相关文章

    RssFeed的方法摘要为Private constructor to be used with factory pattern.  (即为工厂模式提供构造器)

    FromUriFromText为两个工厂。

    FromUri的摘要为Factory that constructs RSSFeed objects from a uri pointing to a valid RSS 2.0 XML file.(从一个指向RSS2.0 XML文件的uri取得一个RSSFeed对象)

    FromText摘要为Factory that constructs RssFeed objects from the text of an RSS 2.0 XML file.(从一个RSS2.0 XML文件取得一个RssFeed对象)

    工厂取得RssFeed对象以后就使用对象构造器(即RssFeed()方法)进行构造,取出里面的channel节点构造RssChannel对象,然后加入到一个使用了泛型的List<RssChannel>

     

    再考察RssChannel类的类结构如下:
    rsschannel.jpg

    类很简单,RssChannel的构造器从RssFeed获得了channel节点的对象以后,进一步解析出里面的Item对象,使用RssItem进行构造,添加进一个List<RssItem>

     

    然后我们考察RssItem类,结构如下:

    rssItem.jpg

    查看代码,我们可以看到这个类实现了一个IItem接口,关于这个接口以后再介绍。

    这个类的构造器就是从RssChannel传递来的RssItem对象中取出我们需要的数据:Titledescriptionlink

     

    这三个类的处理流程就是这样,不算复杂,处理其他类型的XML文件也可以借鉴参考。

    首先我们来看一下前面一篇文章提到了一个RssItem类实现了一个IItem接口,我们发现这个接口是存放在UI文件夹下的,这个接口本身很简单:

        public interface IItem

        {

            string Description { get; }

            string Title { get; }

    }

    //接口不能定义字段成员

    //所有的接口成员都是公共的,不能使用包括public在内的任何访问修饰符

    //在接口中定义的属性可以确定访问快get和/或set中的哪一个能用于该属性



    它的具体实现是由UI文件夹下的另两个类来实现的。

    我们首先考察ItemListView类,这个类的作用就是封装了Item List的显示方式:每一个的description在一个列表中显示,某一个Item处于选中状态。

    我们查看代码可以发现它是一个泛型类

    public class ItemListView<T> : IDisposable where T : IItem

    类本身需要实现IDisposable接口,以释放一些资源,还定义了一个泛型约束,以使我们的T的类型都要实现IItem接口(关于泛型约束请参考我的文章

    类具体的内容都是GDI+的编程实现,大家可以自己查看

     

    另一个类ItemDescriptionView.cs的作用是封装了每一个ItemDescription的显示方式

    接下来我们看一下主窗体:ScreenSaverForm

    在类构造器里,初始化组件以后,我们看到第一个方法是SetupScreenSaver(),这个方法的作用是把主窗体设为一个全屏的屏保程序

    我们查看一下代码:

                // 使用双倍缓冲增加绘制的表现,其实这里使用的OptimizedDoubleBuffer我查看了VS2003

                //的文档,并没有这个枚举值,只有DoubleBuffer,看样子是2.0新增的

                this.SetStyle(ControlStyles.OptimizedDoubleBuffer | ControlStyles.UserPaint | ControlStyles.AllPaintingInWmPaint, true);

                //获取鼠标

                this.Capture = true;

     

                // 把应用程序设为全屏,然后隐藏鼠标

                Cursor.Hide();

                Bounds = Screen.PrimaryScreen.Bounds;

                WindowState = FormWindowState.Maximized;

                ShowInTaskbar = false;

                DoubleBuffered = true;

                BackgroundImageLayout = ImageLayout.Stretch;

     

    接下来的方法是LoadBackgroundImage()

     

                // 初始化图像

                backgroundImages = new List<Image>();

                currentImageIndex = 0;

     

                if (Directory.Exists(Properties.Settings.Default.BackgroundImagePath))

                {

                    try

                    {

                        // 尝试载入用户给出的图像

                        LoadImagesFromFolder();

                    }

                    catch

                    {

                        // 如果失败,再入默认图像

                        LoadDefaultBackgroundImages();

                    }

                }

     

                // 如果没有图像可载入,就载入默认的

                if (backgroundImages.Count == 0)

                {

                    LoadDefaultBackgroundImages();

                }

     

     其实查看Properties.Settings,我们发现BackgroundImagePath刚开始是没有值的,所以肯定会载入默认图像。

    这里又涉及到两个方法,第一个是

            private void LoadImagesFromFolder()

            {

                DirectoryInfo backgroundImageDir = new DirectoryInfo(Properties.Settings.Default.BackgroundImagePath);

                // 遍历每一个图片扩展名 (.jpg, .bmp, etc.)

                foreach (string imageExtension in imageExtensions)

                {

                    //遍历用户提供的文件夹内的每一个文件

                    foreach (FileInfo file in backgroundImageDir.GetFiles(imageExtension))

                    {

                        // 尝试载入图像

                        try

                        {

                            Image image = Image.FromFile(file.FullName);

                            backgroundImages.Add(image);

                        }

                        catch (OutOfMemoryException)

                        {

                            // 如果图像无法再入,继续下一个文件

                            continue;

                        }

                    }

                }

            }

    第二个是

            private void LoadDefaultBackgroundImages()

            {

                //如果背景图像由于任何原因不能被载入

                //使用储存在resources里的图像

                backgroundImages.Add(Properties.Resources.SSaverBackground);

                backgroundImages.Add(Properties.Resources.SSaverBackground2);

            }

     

    我们查看Properties.Resources可以看到

            internal static System.Drawing.Bitmap SSaverBackground {

                get {

                    object obj = ResourceManager.GetObject("SSaverBackground", resourceCulture);

                    return ((System.Drawing.Bitmap)(obj));

                }

            }

           

            internal static System.Drawing.Bitmap SSaverBackground2 {

                get {

                    object obj = ResourceManager.GetObject("SSaverBackground2", resourceCulture);

                    return ((System.Drawing.Bitmap)(obj));

                }

            }

    而这里的SSaverBackgroud是嵌入在资源文件里的图片的名称

     

    那么发布出去的屏保文件是否包含着两张图片呢,我们就动手做一个小实验

    首先以默认的情况下发布,我们发现生成的exe文件为120K大小,而然后我在资源文件里添加一张1.17Mbmp文件,然后让程序显示我们新添加的这张默认图片

    Properties.Resources添加如下语句(图片文件名为_7

            internal static System.Drawing.Bitmap SSaverBackground3

            {

                get

                {

                    object obj = ResourceManager.GetObject("_7", resourceCulture);

                    return ((System.Drawing.Bitmap)(obj));

                }

            }

    然后在ScreenSaverFormLoadDefaultBackgroundImages()方法添加语句

    backgroundImages.Add(Properties.Resources._7);

    然后编译运行,我们就可以看到我们新添加的默认图片,然后查看生成的exe文件,大小变为1.29M

    所以我们可以得知新加入的图片已经嵌入在PE文件里面了。

     

    接下来的就是  LoadRssFeed();方法了

            private void LoadRssFeed()

            {

                try

                {

                    //尝试从用户的settings或者rssFeed

                    rssFeed = RssFeed.FromUri(Properties.Settings.Default.RssFeedUri);

                }

                catch

                {

                    //如果载入Rss有问题,则载入一个错误信息

                    rssFeed = RssFeed.FromText(Properties.Resources.DefaultRSSText);

                }

            }

    这里的RssFeed类在前面已经分析过了

     

    后面的是

                // 初始化显示ItemListView来显示RssItem里的Items列表

                // 放在屏幕的左侧   

                rssView = new ItemListView<RssItem>(rssFeed.MainChannel.Title, rssFeed.MainChannel.Items);

                InitializeRssView();

     

    InitializeRssView()方法里设置了我们需要的具体数值

            private void InitializeRssView()

            {

                rssView.BackColor = Color.FromArgb(120, 240, 234, 232);

                rssView.BorderColor = Color.White;

                rssView.ForeColor = Color.FromArgb(255, 40, 40, 40);

                rssView.SelectedBackColor = Color.FromArgb(200, 105, 61, 76);

                rssView.SelectedForeColor = Color.FromArgb(255, 204, 184, 163);

                rssView.TitleBackColor = Color.Empty;

                rssView.TitleForeColor = Color.FromArgb(255, 240, 234, 232);

                rssView.MaxItemsToShow = 20;

                rssView.MinItemsToShow = 15;

                rssView.Location = new Point(Width / 10, Height / 10);

                rssView.Size = new Size(Width / 2, Height / 2);

            }

    接下来的ItemDescriptionView雷同

     

     

    至此,我们的屏保就可以显示出来的,但是作为屏保,我们还缺少一些对鼠标、键盘事件的响应。

            private void ScreenSaverForm_MouseMove(object sender, MouseEventArgs e)

            {

                // 只有在这个事件第一次调用的时候才设置IsActiveMouseLocation

                if (!isActive)

                {

                    mouseLocation = MousePosition;

                    isActive = true;

                }

                else

                {

                    // 在第一次调用以后,如果鼠标发生位置移动,就关闭窗体

                    if ((Math.Abs(MousePosition.X - mouseLocation.X) > 10) ||

                        (Math.Abs(MousePosition.Y - mouseLocation.Y) > 10))

                    {

                        Close();

                    }

                }

            }

    当然还有

     

            private void ScreenSaverForm_MouseDown(object sender, MouseEventArgs e)

            {

                Close();

            }

    关于键盘事件,可以根据我们的需要定制

     

            private void ScreenSaverForm_KeyDown(object sender, KeyEventArgs e)

            {

                switch (e.KeyCode)

                {

                    case Keys.Down:

                        rssView.NextArticle();

                        break;

                    case Keys.Up:

                        rssView.PreviousArticle();

                        break;

                    default:

                        Close();

                        break;

                }

     

posted on 2009-02-17 18:56  Zihuatanejo  阅读(209)  评论(0)    收藏  举报

导航