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

屏保启动工具包

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.69/5 (9投票s)

2007年11月6日

CPOL

7分钟阅读

viewsIcon

65626

downloadIcon

1997

编写自己的屏保的入门项目。

Screenshot - DcamScreenSaver.jpg

引言

此入门套件和示例代码可用作编写自己的屏幕保护程序的入门项目。它处理系统传递给屏幕保护程序应用程序的参数,包括桌面屏幕保护程序属性对话框中的预览以及配置对话框。此代码将必要的步骤简化到了绝对最小值。

背景

我想自己写一个简单的屏幕保护程序:蠕虫。我从 Visual Studio 2005 附带的屏幕保护程序入门套件开始,但对其实现方式并不满意。它的缺点是

  • 它在动画的每一步都刷新屏幕;每 50 毫秒执行一次此操作会导致 CPU 使用率高于正常空闲模式(我的 PC 从大约 0-2% 上升到 60%,这导致了风扇开启)
  • 不支持桌面屏幕保护程序属性中的预览功能

我的初衷不是要编写高质量和/或花哨的 OpenGL 或 DirectX 动画应用程序(网络上有很多)。它应该是一个入门套件,可以

  • 使用的 CPU 功耗尽可能少
  • 支持桌面屏幕保护程序属性中的预览功能
  • 处理设置屏幕保护程序所需的一切,这样用户就可以专注于新的动画
  • 可以在一段时间后关闭屏幕保护程序
  • 方便处理不同的动画

Using the Code

与普通应用程序的区别

屏幕保护程序应用程序与普通 Windows 应用程序最大的区别在于,运行时我们拥有全屏空间,因此无需在 paint 事件处理程序中重新绘制客户端的任何部分。要么屏幕保护程序已启动且全屏空间归我们所有,要么它已关闭。这意味着我们不使用 paint 事件处理程序来绘制动画;我们可以在 timer 事件处理程序中直接完成。此外,如果屏幕上只有很少的部分在每次动画步骤中发生变化,我们可以直接更新这些部分,而无需刷新全屏。这可以节省大量 CPU 资源。

入门套件的作用

ScreenSaverForm 是应用程序的窗体。它准备并处理所有必需的东西。实际的屏幕保护程序动画绘制在 ScreenSaverBase 的派生类中完成。屏幕保护程序所需的用于动画的所有对象都在内部类 Globals 中。

ScreenSaverForm 类

构造函数接收一个整数,这是用于预览窗口的 Windows 句柄。如果系统以预览模式调用屏幕保护程序,它会将此句柄作为 `/p` 之后的第二个参数传递(请参阅 Microsoft 支持)。ScreenSaverForm_Load 方法在动画绘制发生之前准备好所有内容。

在全屏模式下,窗体被初始化为一个最大化且置顶的窗口,这是一个简单的任务。在预览模式下,我们必须使用一些 Windows API 调用来操作窗口设置。诀窍是将我们的窗体父窗口设置为传递的 Windows 句柄。我们还必须将我们的窗体窗口样式设置为子窗口。这样,当系统屏幕保护程序属性对话框选择新的屏幕保护程序用于预览窗口时,它就能正确接收关闭消息。之后,我们读取父窗口的客户区大小,并将其也设置为我们的窗体大小。最后,我们设置全局屏幕大小以供使用。

接下来的步骤是将屏幕(全屏或预览)捕获到位图,以便以后可以重新绘制它。我们还需要加载和设置计时器值。完成所有这些之后,我们实例化所选的屏幕保护程序并调用其 OnLoad 方法。最后的步骤是创建图形、捕获鼠标位置并启动计时器。

private void ScreenSaverForm_Load( object sender, EventArgs e )
{
    // use full screen

    if( _ipParentHandle == IntPtr.Zero )
    {
        this.WindowState = FormWindowState.Maximized;
        this.TopMost = true;
    }
    // use preview screen that's handle was passed to the application

    else
    {
        _boFullScreen = false;

        // set the (new) parent window

        WinAPI.SetParent( this.Handle, _ipParentHandle );

        // make this a child window, so it will properly 

        // receive a close message

        // from the systems screen saver properties dialog when 

        // a new screen saver

        // is selected for the preview window

        WinAPI.SetWindowLong( this.Handle, WinAPI.SysConst.GWL_STYLE,
            WinAPI.GetWindowLong( this.Handle, WinAPI.SysConst.GWL_STYLE ) | 
            WinAPI.SysConst.WS_CHILD );

        // get the parents window size and set it for this form too

        Rectangle rect;
        WinAPI.GetClientRect( _ipParentHandle, out rect );
        this.Size = rect.Size;
        this.Location = new Point( 0, 0 );
    }
    Globals.ScreenSize = this.Size;

    // save the background in a bitmap

    Globals.BackGroundBitmap = 
        new Bitmap( Globals.ScreenSize.Width, Globals.ScreenSize.Height );
    Graphics g = Graphics.FromImage( Globals.BackGroundBitmap );
    g.CopyFromScreen( this.DesktopLocation, new Point( 0, 0 ), 
        Globals.ScreenSize );
    g.Dispose();

    // setup the timer, but don't start it yet

    _tsBlackAfter = 
        new TimeSpan( Math.Min( Math.Max( 0, 
        Properties.Settings.Default.Main_BlackAfterHH ), 23 ),
        Math.Min( Math.Max( 0, 
        Properties.Settings.Default.Main_BlackAfterMM ), 59 ),
        Math.Min( Math.Max( 0, 
        Properties.Settings.Default.Main_BlackAfterSS ), 59 ) );
    Globals.ScrSvrTimer = new Timer();
    Globals.ScrSvrTimer.Interval = 100;
    Globals.ScrSvrTimer.Tick += new System.EventHandler( this.timer1_Tick );

    //////////////////////////////////////////////////////////////////////

    // instantiate and load the screen saver

    switch( Properties.Settings.Default.Main_ScreenSaver % 4 )
    {
        case 0:
            _scrSvr = new ScreenSaverWorms();
            break;
        case 1:
            _scrSvr = new ScreenSaverPacman();
            break;
        case 2:
            _scrSvr = new ScreenSaverMaze();
            break;
        case 3:
            _scrSvr = new ScreenSaverChromachron();
            break;
    }
    _scrSvr.OnLoad( sender, e );
    ////////////////////////////////////////////////////////////////////


    // last inits

    Globals.Graph = CreateGraphics();
    _pntMouseLocation = Control.MousePosition;
    _dtStart = DateTime.Now;
    Globals.ScrSvrTimer.Start();
}

窗体的已实现事件处理程序非常简单。当发生 KeyDownMouseDownMouseMove 事件时,如果应用程序处于全屏模式,我们只需关闭应用程序。OnPaintBackground 将事件直接转发给当前屏幕保护程序的 OnPaintBackground 方法。ScrSvrTimer_Tick 事件处理程序首先检查关闭时间是否已过。如果是,它会停止计时器并将屏幕填充为黑色。否则,它会调用当前屏幕保护程序的 OnTimerTick 方法。

ScreenSaverBase 类

ScreenSaverBase 类是抽象的,具有以下四个方法。OnLoad 是抽象的,在屏幕保护程序窗体的加载事件处理程序中调用。设置 Globals.Timer.Interval 并初始化/加载屏幕保护程序运行所需的所有数据。OnPaintBackground 不是抽象的,默认情况下会绘制保存在 Globals.BackGroundBitmap 中的背景图像。如果需要,例如,您想要黑色背景,请覆盖此方法。OnTimerTick 是抽象的,在每个计时器事件中调用。在此实现您的动画。Dispose 用于正确清理分配的资源,正如良好的实践所示。

Globals 类

它包含动画绘制所需的所有对象,因此我们不必在每个计时器事件中进行分配。

  • ScreenSize:要使用的最大屏幕尺寸
  • BackGroundBitmap:屏幕保护程序启动时捕获的屏幕截图
  • Graph:要绘制的图形设备
  • ScrSvrTimer:动画基于的计时器
  • Rand:用于生成随机数

屏幕保护程序示例

  • ScreenSaverWorms 实现三种“吞噬”屏幕的蠕虫类型:WormStreightWormCurlyWormSinWaveWorm 是一个抽象类,负责处理移动,因此派生类只需要实现 CalcNewPosition 来控制运动/动画。
  • ScreenSaverPacman 是一个非常简单的动画,其中吃豆人“吞噬”屏幕。无需多说……
  • ScreenSaverMaze 在屏幕上生成一个迷宫,并使用深度优先搜索 (DFS) 算法寻找解决方案路径。我的实现基于 MazeWorks 的伪代码。您也可以在 Wikipedia 上找到它。生成和搜索都可以通过屏幕进行动画,也可以在后台计算。动画速度可以在属性中设置,其中步进时间为 0(零)表示在没有动画的情况下生成迷宫或搜索路径。由于当前路径在生成过程中绘制到屏幕上,因此代码必须回溯到起点以清除所有“足迹”。
  • ScreenSaverChromachron 也是一个非常简单的程序,它根据瑞士设计师 Tian Harlan 的彩色时间圆显示当前时间。它的读取方式如下:全彩色表示一整小时。两个全彩色之间的任何角度都代表两个小时之间的时间比例。例如:全黄色 = 00:00 或 12:00,一半蓝色/一半绿色 = 06:30 或 18:30。

如何编写您自己的屏幕保护程序动画

要实现您自己的屏幕保护程序,只需从 ScreenSaverBase 派生一个类。您必须至少重写抽象方法 OnLoadOnTimerTick。然后在 ScreenSaverForm_Load 中创建您的类的新实例,并将其存储在 _scrSvr 中。

关注点

在处理预览模式问题时,我创建了另一个项目 StartProg,以在所有三种模式下运行给定的应用程序。从这个应用程序运行屏幕保护程序比每次都将其复制到系统目录并从桌面属性对话框启动它要容易。在处理选项对话框时,它也非常有用。将 EXE 复制到 DcamScreenSaver 项目的工作目录并从那里启动它。

Screenshot - StartProg.jpg

我将一个批处理文件放在了调试目录中,该文件将文件复制到 *c:\windows\system32* 并将其重命名为扩展名 ***.scr*,这样您就可以在桌面属性对话框中选择它。不幸的是,有两件事我无法解决

  • 桌面属性对话框在用于选择屏幕保护程序的下拉列表中显示文件名,这对于多语言使用来说并不友好。我没有找到任何关于如何显示不同名称的信息。
  • 调试器不再工作。如果设置断点,应用程序会冻结,您必须使用任务管理器终止进程。我没有弄清楚原因。

也许有人对这两点有答案。

许可证

此代码供任何人免费使用。复制、重命名、修改和/或扩展它。

历史

2007.11.04 首次发布

© . All rights reserved.