65.9K
CodeProject 正在变化。 阅读更多。
Home

编写一个真正有效的屏幕保护程序

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.88/5 (74投票s)

2006年5月12日

MIT

8分钟阅读

viewsIcon

253955

downloadIcon

2596

一个用于管理屏幕保护程序的初始化、计时、预览视图和多显示器支持的基类,以正确的方式实现。

Sample image

引言

屏幕保护程序很不错。它们不再具有多少实际用途,因为现在的显示器不会“烧伤”图像到屏幕上,但当您不在时,它们可以将您的计算机变成漂亮的装饰品,而且整天盯着屏幕保护程序比沉迷于大型多人在线角色扮演游戏要健康得多。

屏幕保护程序不好的地方在于它们的行为方式以及编写它们的方式。屏幕保护程序的设置似乎很容易;在 Windows 上,您只需将一个 .exe 重命名为 .scr,即可完成。但在此过程中会遇到一些奇怪的问题。以下是一些最让用户头疼的问题:

  • 多显示器支持常常不稳定,尤其是在显示器尺寸不相等时。
  • 预览在“显示属性”对话框中经常无法正确显示。
  • 通常没有设置框,甚至连一个非常简单的设置框都没有。
  • 有些屏幕保护程序使用“游戏循环”设计,导致后台应用程序瘫痪,并使许多现代 CPU 过热。
  • 对鼠标和键盘输入的响应不一致;有些屏幕保护程序要到您实际 单击 鼠标后才能退出。

以下是一些开发人员遇到的麻烦:

  • 您决定使用计时器来避免占用 CPU,例如 System.Timers.Timer,但在 Windows 9x/2K/XP 上,它提供的精度不足以让屏幕保护程序以每秒 30 帧的速度运行。
  • 在全屏模式下运行屏幕保护程序时,调试可能会成为一场噩梦。
  • 您可能已经在一个 Windows 窗体中编写了整个屏幕保护程序……结果却发现您必须使用原生句柄才能在预览窗口中显示它。
  • 编写屏幕保护程序时,您希望在灵感尚存时尽快投入到有趣的部分。您不想处理乏味的初始化和关闭代码。

屏幕保护程序是奢侈品,而非必需品。这些问题中的任何一个都足以阻止人们使用它们。市面上有很多很棒的屏幕保护程序,但随着计算机设置日益多样化和问题日益暴露,屏幕保护程序作为一个应用程序类别正变得越来越不受欢迎。

Screensaver 基类通过提供一个处理屏幕保护程序编写的机械方面的骨架,试图减轻这些问题并使屏幕保护程序的开发更容易。它的目的不是创建全新的 API,而是简单地帮助让事情正常工作。

使用代码

一个基本示例

为了避免部署时的混乱,Screensaver 类及其所有辅助对象都包含在一个文件中,即 Screensaver.cs,您可以将其直接放入您的项目中。使用 Visual Studio 或 Microsoft 的 C# 编译器 (csc.exe) 以默认设置编译,大约为 32 KB。虽然一些洁癖者可能会说这“臃肿”,但我认为为了获得一致的行为和可重用的代码(许多屏幕保护程序都缺乏这两种东西),这是值得付出的代价。

这是一个使用 Screensaver 类编写的基本屏幕保护程序示例

class MyCoolScreensaver : Screensaver
{
   public MyCoolScreensaver()
   {
      Initialize += new EventHandler(MyCoolScreensaver_Initialize);
      Update += new EventHandler(MyCoolScreensaver_Update);
      Exit += new EventHandler(MyCoolScreensaver_Exit);
   }

   void MyCoolScreensaver_Initialize(object sender, EventArgs e)
   {
   }

   void MyCoolScreensaver_Update(object sender, EventArgs e)
   {
      Graphics0.Clear(Color.Black);
      Graphics0.DrawString(
         DateTime.Now.ToString(),
         SystemFonts.DefaultFont, Brushes.Blue,
         0, 0);
   }

   void MyCoolScreensaver_Exit(object sender, EventArgs e)
   {
   }

   [STAThread]
   static void Main()
   {
      Screensaver ss = new MyCoolScreensaver();
      ss.Run();
   }
}

就这样!这个示例将在主显示器的左上角以蓝色打印当前时间。Initialize 事件在窗口创建后立即触发。Update 默认每秒触发 30 次;可以通过更改 Framerate 的值来改变此速度。Exit 在窗口关闭之前调用。

运行模式

运行模式由 Screensaver.Run() 自动确定。如果不带任何参数,当文件扩展名为 .scr 时,屏幕保护程序将显示“设置”窗口;如果文件扩展名为 .exe,它将在屏幕大小的 9/10 的窗口中显示屏幕保护程序。

您可以通过将 ScreensaverMode 值传递给 Run() 来更改此设置:Normal 将以全屏模式启动屏幕保护程序,Settings 将启动设置对话框,Windowed 将以窗口模式启动屏幕保护程序。该值不能是 Preview;预览模式仅在 Windows 显示属性提供正确的参数(特别是预览控件的句柄)时才有效。运行模式会被显示属性传递给屏幕保护程序的命令行参数覆盖。

使用 System.Drawing 进行渲染

Graphics0 提供主窗口的 System.Drawing.Graphics 对象。要在其他窗口中绘图,您可以使用 Windows[n].Graphics,其中 n 是显示器的编号。请注意,您负责先清除屏幕。

默认情况下启用了双缓冲,但为了避免过多的开销,直到 Window.Graphics 对象或 Window.DoubleBuffer 属性首次使用后,它才真正生效。如果您使用 DirectX 等其他渲染方法,最好显式将其关闭。与 .NET 2.0 版本相比,.NET 1.1 版本中的双缓冲效率较低。

安装屏幕保护程序

要在 Windows XP 上安装屏幕保护程序,您只需将屏幕保护程序可执行文件的扩展名更改为 .scr,然后在 shell 中右键单击并选择“安装”。在较旧版本的 Windows 中,.scr 文件应放在 Windows 的 System32 文件夹中。在 XP 上也可以这样做,使其始终出现在屏幕保护程序菜单中,但我更喜欢不接触 Windows 文件夹。

您可以通过在项目属性下的“生成后事件”中添加此命令,让 Visual Studio 自动将可执行文件的副本制作成 .scr 文件

copy "$(TargetFileName)" "$(TargetName).scr" /y

一个稍微复杂一点的 Direct3D 示例

任何可用的渲染技术都可以与 Screensaver 类一起使用——DirectX、OpenGL、WPF (Avalon) 等。下面是使用托管 Direct3D 和 Screensaver 类的一个示例。

设置托管 Direct3D 并不困难,前提是您对 DirectX 有一定的了解。如果没有,您可以通过 Google 搜索找到许多不错的教程。浏览几个,然后仔细研究一个您喜欢的。

class MyCoolScreensaver : Screensaver
{
   public MyCoolScreensaver()
      : base(FullscreenMode.MultipleWindows)
   {
      Initialize += new EventHandler(MyCoolScreensaver_Initialize);
      Update += new EventHandler(MyCoolScreensaver_Update);
      Exit += new EventHandler(MyCoolScreensaver_Exit);
   }

   Device device;

   Microsoft.DirectX.Direct3D.Font font;
   int updates;

   void MyCoolScreensaver_Initialize(object sender, EventArgs e)
   {
      PresentParameters pp = new PresentParameters();

      if (this.Mode != ScreensaverMode.Normal)
         pp.Windowed = true;
      else
      {
         pp.Windowed = false;
         pp.BackBufferCount = 1;
         pp.BackBufferWidth =
            Manager.Adapters[Window0.DeviceIndex].CurrentDisplayMode.Width;
         pp.BackBufferHeight =
            Manager.Adapters[Window0.DeviceIndex].CurrentDisplayMode.Height;
         pp.BackBufferFormat = 
            Manager.Adapters[Window0.DeviceIndex].CurrentDisplayMode.Format;
      }

      pp.SwapEffect = SwapEffect.Flip;
      device = new Device(
         Window0.DeviceIndex, DeviceType.Hardware,
         Window0.Handle, CreateFlags.HardwareVertexProcessing, pp);
      
      Window0.DoubleBuffer = false;
      font = new Microsoft.DirectX.Direct3D.Font(
         device, System.Drawing.SystemFonts.DefaultFont);
   }

   void MyCoolScreensaver_Update(object sender, EventArgs e)
   {
      System.IO.StringWriter writer = new System.IO.StringWriter();
      writer.WriteLine("Time: " + DateTime.Now);
      writer.WriteLine("Achieved framerate: " + this.AchievedFramerate);
      writer.WriteLine("Update count: " + updates++);
      writer.WriteLine("Device: " + Window0.DeviceIndex);

      device.Clear(ClearFlags.Target, System.Drawing.Color.Black, 0, 0);

      device.BeginScene();

      font.DrawText(null, writer.ToString(), 0, 0, 
                    System.Drawing.Color.Blue.ToArgb());

      device.EndScene();
      device.Present();
   }

   void MyCoolScreensaver_Exit(object sender, EventArgs e)
   {
      device.Dispose();
   }

   [STAThread]
   static void Main()
   {
      Screensaver ss = new MyCoolScreensaver();
      ss.Run();
   }
}

要编译此示例,您需要托管 DirectX 运行时。您可以从 此处获取最新版本(截至 2006 年 5 月)。添加对 Microsoft.DirectXMicrosoft.DirectX.Direct3DMicrosoft.DirectX.Direct3DX 的引用。

初始化屏幕保护程序的 DirectX,实际上比初始化典型应用程序要容易一些,因为您不必担心设备重置和大小调整等问题。只需确保在最后释放设备,否则会导致意外关机。

全屏模式

Screensaver 类的重写构造函数接受一个 FullscreenMode 值:SingleWindow 用于用一个窗口覆盖所有屏幕,MultipleWindows 用于用一个窗口覆盖每个屏幕。对于 DirectX,我们希望使用 MultipleWindows,并在其中一个窗口中绘图。默认值为 MultipleWindows,但在本示例中,我显式指定它只是为了演示。

请注意,ScreensaverMode.Normal 是屏幕保护程序以全屏模式运行的唯一模式。虽然您可以跳过大部分全屏初始化,而仅将其作为窗口应用程序运行,但正确初始化全屏以获得额外的性能提升是一个好主意。

Window 类

就像 Graphics0Windows[0].Graphics 的别名一样,Window0Windows[0] 的别名。Screensaver.Window 类封装了 Windows 窗体或在屏幕保护程序处于预览模式时的窗口句柄。这些对象中提供了与图形相关的各种属性。

杂项功能

Default settings dialog box

  • 设置对话框 - 您可以使用默认的设置对话框,它仅显示一个包含从程序集信息中收集的一些文本的消息框,或者通过重写 ShowSettingsDialog() 来显示您自己的对话框。如果您选择坚持使用默认对话框,可以通过设置 SettingsText 属性来输入一些附加文本。在此示例中,我将其设置为我的电子邮件地址。
  • 键盘和鼠标事件 - Windows 窗体的所有键盘和鼠标事件都可以在 Window 类中获得。此外,您可以使用 Form 属性访问底层的 Windows Form,但该值不一定会被设置,因此这些也提供了方便。
  • 帧率设置 - AchievedFramerate 属性检索过去一秒处理的实际帧数,而 Framerate 属性设置目标帧率。Screensaver 类使用多媒体计时器,因此您可以期望它相当精确。但请注意,没有帧跳过。
  • CloseOnClick、CloseOnMouseMove、CloseOnKeyboardInput - 这些属性可以设置为更改屏幕保护程序退出的条件。默认情况下,在正常模式下,所有这三个属性都设置为 true
  • 调试模式差异 - 为了简化,在调试模式下编译应用程序时,某些行为会有所不同。以下是当前版本的差异:
    • 在调试模式下,窗口不是最顶层的。这样您就可以看到您的调试器。
    • 当屏幕保护程序在预览模式下启动时,Run() 会提供启动调试器的选项。

PixieSaver 示例应用程序

PixieSaver 是一个简单的、功能齐全的屏幕保护程序,使用 Screensaver 基类编写,代码行数(包括空格)约 150 行。它使用 SingleWindow 全屏模式,并使用 System.Drawing 进行绘图。每个小精灵从底部开始,闪烁着向上移动。为了增加可爱度,每个小精灵都有自己向左或向右漂移的倾向。这是我目前使用的屏幕保护程序。我很喜欢它,因为我的电脑放在我的房间里,而花哨的屏幕保护程序往往会让我保持清醒。

如果您有任何问题或建议,请告诉我,如果您制作了任何很酷的屏幕保护程序,也请告诉我。

历史

  • 2006.05.12 - 首次发布。
  • 2006.05.16 - 上传了与 .NET 1.1 兼容的代码版本。关闭代码中的小改动以匹配 .NET 1.1 版本;应该不会有实际影响。
© . All rights reserved.