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

public interface IItem
    {
        string Description { get; }
        string Title { get; }
}

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

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

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

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

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

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

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

接下来我们看一下主窗体: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.17M的bmp文件,然后让程序显示我们新添加的这张默认图片

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

internal static System.Drawing.Bitmap SSaverBackground3
        {
            get
            {
                object obj = ResourceManager.GetObject("_7", resourceCulture);
                return ((System.Drawing.Bitmap)(obj));
            }
        }

然后在ScreenSaverForm的LoadDefaultBackgroundImages()方法添加语句

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)
        {
            // 只有在这个事件第一次调用的时候才设置IsActive和MouseLocation
            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;
            }