首先我们来看一下前面一篇文章提到了一个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;
}