Click here to Skip to main content
15,881,882 members
Articles / Desktop Programming / MFC
Article

Creating a screen saver

Rate me:
Please Sign up or sign in to vote.
4.69/5 (24 votes)
8 Nov 2001CPOL8 min read 274.7K   6.5K   90   46
Discusses Windows screen savers and shows you how to implement your own.

Sample screensaver image

Acknowledgment

I would like to start off this article by thanking all of those individuals who have sent me unbelievable number of emails appreciating my previous articles, specially the ISAPI Extension one! You cannot imagine how I have been inspired by those lovely emails. I do appreciate your time, and I hope that this article suits your needs too.

Introduction

Unlike the human life that could be hardly saved 'coz of being invaded by other different so-called humans, computer monitors could be easily saved against the phosphor burn using a screen saver! Today, I am going to talk about Windows Screen Savers, one of those interesting topics that most of the programmers out there would like to know about.

Fundamentals

A screen saver is just a plain Win32 application that will be launched automatically when the mouse and keyboard have been idle for a specified period of time. Whenever this timer expires, the user could see either a blank screen or a very complex animation. Thereafter, if the user presses a key on her keyboard or moves her mouse, the screen will be changed back to the normal one that disappeared when the screen saver started.

To create your own screen saver, you have got two choices. First, you can create a normal Win32 application and handle the necessary messages sent to your window. Choosing the first way, you have to develop a program that creates a full screen window, as well as a window procedure that processes window messages sent to your application. Within the window procedure, you could have something as follows:

switch(message)
{
    case WM_SYSCOMMAND:
        if(wParam == SC_SCREENSAVE || wParam == SC_CLOSE)
            return FALSE;
    break;

    case WM_LBUTTONDOWN:
    case WM_MBUTTONDOWN:
    case WM_RBUTTONDOWN:
    case WM_KEYDOWN:
    case WM_KEYUP:
    case WM_MOUSEMOVE:
        //Quit the application here
    break;
}

In other words, you have to hard code the pieces required for your screen saver and take care of the messages that is common along all the Windows screen savers.

The second choice, though, is trying to use a library file that is being shipped with your compiler and does all of the above-mentioned hard coding (and even more)! This way, you can focus on the actual problem - that is creating the visual effects. Obviously, we are going to use this library to avoid re-inventing wheels.

The screen saver library (scrnsave.lib) contains the WinMain function. This function among the other things, registers a window class that looks something as follows:

WNDCLASS cls; 

cls.hCursor = NULL;
cls.hIcon = LoadIcon(hInst, MAKEINTATOM(ID_APP));
cls.lpszMenuName = NULL;
cls.lpszClassName = "WindowsScreenSaverClass";
cls.hbrBackground = GetStockObject(BLACK_BRUSH);
cls.hInstance = hInst;
cls.style = CS_VREDRAW | CS_HREDRAW | CS_SAVEBITS | CS_DBLCLKS;
cls.lpfnWndProc = (WNDPROC) ScreenSaverProc;
cls.cbWndExtra = 0;
cls.cbClsExtra = 0;

In other words, it registers a black background window, having no mouse cursor and an icon identified by ID_APP. Moreover, it introduces its window procedure as ScreenSaverProc. This procedure is the heart of any screen saver, and almost all the coding is done here.

This procedure, like all the other window procedures, is declared as follows:

LONG ScreenSaverProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);

where hWnd is the handle of the desktop window, message is the actual message sent to the screen saver window, and wParam and lParam are additional message-specific information. If you could remember from my previous "MFC vs. Win32" series, you could recall that whenever we do not need to process a message, we pass it to the DefWindowProc function. However, when developing a screen saver, we need to use the DefScreenSaverProc instead. It is the same as DefWindowProc with the exception of taking care of the other messages needed to keep the screen saver running!

Anyway, the library header file is named scrnsave.h and is placed in the include directory of the compiler. It contains the necessary prototypes to support the screen saver library. There are some global variables defined in the library and the extern version is included in the above-mentioned header file. Two of those important variables have been declared as follows:

extern HINSTANCE hMainInstance;
extern BOOL   fChildPreview;

where hMainInstance is the instance handle of the screen saver, and fChildPreview states whether the screen saver is currently running in preview mode. Don't worry, we will examine each of these, shortly.

Goals

We are going to create a screen saver, named, Ball Fusion, that contains 7 balls moving after each other on a sine wave path:

x = sin(ß) * cos(ß) * cos(2ß)
y = cos(ß)

where ß is a positive number between 0 and 360. You can change the formula, if you want.

Other considerations

The Ball Fusion will not have any configuration dialog box, and this means if you try to choose the Settings button in the screen saver control panel, you will get nothing in response. This is just because of the lack of time. Actually, my boss started to shout at me when he saw the screen saver running on my system at office! So I stopped any improvements, and that's why it has got no Settings box!

Getting started

To start, launch MSVC++ 6.0. From the "File" menu, select the "New" item. With the "Projects" tab selected, highlight "Win32 Application", and in the "Project Name" edit box, type BallFusion, and press the Ok button. Now, choose "An empty project" radio button, and press "finish" and "ok" respectively. Now, it is time to add the BallFusion.cpp to our project to start coding. To do so, select the "New" item from the "File" menu. Having the "C++ Source File" item selected in the "Files" tab, enter BallFusion as the "File Name" and press OK. This way, we have got the base framework in place.

Open the BallFusion.cpp to insert the following code:

#include "windows.h"
#include "scrnsave.h"

LRESULT WINAPI ScreenSaverProc(HWND hWnd, 
     UINT message, WPARAM wParam, LPARAM lParam)
{
    return 0;
}

The screen saver library also needs us to include two other functions in our screen saver, ScreenSaverConfigureDialog and RegisterDialogClasses. The former receives messages sent to a screen saver's configuration dialog box whilst the latter is used to register any window class required by the configuration box. Since our screen saver doesn't have any configuration dialog box, we could simply skip the implementation. However, we have still included them in our screen saver program:

BOOL WINAPI ScreenSaverConfigureDialog(HWND hDlg, 
           UINT message, WPARAM wParam, LPARAM lParam)
{
    //We simply return FALSE to indicate that
    //we do not use the configuration dialog box

    return FALSE;
}

BOOL WINAPI RegisterDialogClasses(HANDLE hInst)
{
    //Since we do not register any special window class 
    //for the configuration dialog box, we must return TRUE

    return TRUE;
}

The next step is to add scrnsave.lib to the library modules we are going to use in our program. To do so, from the "Project" menu, select "Settings" and make the necessary changes shown below:

Project Settings

Description

Having completed the setting process of the compiler options, it is now time to focus on the actual program. I have defined some global variables within the program, as follows:

#define MAX_BALLS 7
#define pi 3.141592625

#define szAppName "BallFusion 1.0"
#define szAuthor "Written by Mehdi Mousavi, © 2001"
#define szPreview "Ball Fusion 1.0"

typedef struct _BALLS
{
    UINT uBallID;
    HICON hIcon;
    int x;
    int y;
    int angle;
}BALLS;

BALLS gBalls[] = {IDI_REDBALL, NULL, 0, 0, 0,
          IDI_GREENBALL, NULL, 0, 0, 25,
          IDI_BLUEBALL, NULL, 0, 0, 50,
          IDI_PURPLEBALL, NULL, 0, 0, 75,
          IDI_LIGHTREDBALL, NULL, 0, 0, 100,
          IDI_YELLOWBALL, NULL, 0, 0, 125,
          IDI_BLACKBALL, NULL, 0, 0, 150};

where MAX_BALLS is the number of the maximum balls, szAppName is the application's name that is shown at the bottom of the leftmost of the screen as well as the author's name (szAuthor), szPreview is the string that is shown to the user when she is previewing the screen saver from its control panel, and the BALLS structure is the structure to store a ball configuration. This configuration consists of the identifier of the ICON (uBallID), a handle to the icon itself after being loaded (hIcon), the x and y position of the ball, and the starting angle in which the ball starts moving. Finally, there's gBalls, an array of the above-mentioned structure.

When the screen saver procedure receives the WM_CREATE message, it loads the ball icons according to the identifier of each ball already declared in the gBalls variable and stores this loaded icons under the handle already placed in the BALL structure (hIcon). However, please pay attention that to load the icons, the main instance handle of the application (hMainInstance) is given to the LoadImage function:

for(i = 0; i < MAX_BALLS; i++)
    gBalls[i].hIcon = (HICON)LoadImage(hMainInstance,
                    MAKEINTRESOURCE(gBalls[i].uBallID),
                    IMAGE_ICON,
                    48,
                    48,
                    LR_DEFAULTSIZE);

Then the starting position (x and y member of the BALL structure) is calculated for each ball, according to the following formula:

x = sin(ß) * cos(ß) * cos(2ß)
y = cos(ß)

In other words:

xpos = GetSystemMetrics(SM_CXSCREEN) / 2;
ypos = GetSystemMetrics(SM_CYSCREEN) / 2;

for(i = 0; i < MAX_BALLS; i++)
{
    double alpha = gBalls[i].angle * pi / 180;
    gBalls[i].x = xpos + 
      int((xpos - 30) * sin(alpha) * cos(alpha) * cos(2 * alpha));
    gBalls[i].y = ypos - 30 + int(265 * cos(alpha));
}

And a black brush is created to be used while putting the icons on screen and a timer is started:

hBrush = CreateSolidBrush(RGB(0, 0, 0));
uTimer = SetTimer(hWnd, 1, 1, NULL);

When the timer is started, the screen saver procedure receives the WM_TIMER message. For this message, all we do is calculate the x and y position of each ball, while increasing the angle variable (0 <= angle <= 360).

Then, the rectangular area of which the ball should be invalidated is calculated and placed in the rc variable.

for(i = 0; i < MAX_BALLS; i++)
{
    double alpha = gBalls[i].angle * pi / 180;
    gBalls[i].x = xpos + 
      int((xpos - 30) * sin(alpha) * cos(alpha) * cos(2 * alpha));
    gBalls[i].y = ypos - 30 + int(265 * cos(alpha));
    gBalls[i].angle = (gBalls[i].angle >= 360) ? 0 : gBalls[i].angle + 1;

    rc.left = gBalls[i].x;
    rc.right = gBalls[i].x + 48;
    rc.top = gBalls[i].y;
    rc.bottom = gBalls[i].y + 48;

    InvalidateRect(hWnd, &rc, FALSE);
}

When the invalidate function is called, the program receives the WM_PAINT message. All we need to do upon the reception of this message is to put each ball on its position indicated by x and y:

if(fChildPreview)
{
    SetBkColor(hDC, RGB(0, 0, 0));
    SetTextColor(hDC, RGB(255, 255, 0));
    TextOut(hDC, 25, 45, szPreview, strlen(szPreview));
}
else
{
    SetBkColor(hDC, RGB(0, 0, 0));
    SetTextColor(hDC, RGB(120, 120, 120));
    TextOut(hDC, 0, ypos * 2 - 40, szAppName, strlen(szAppName));
    TextOut(hDC, 0, ypos * 2 - 25, szAuthor, strlen(szAuthor));

    for(i = 0; i < MAX_BALLS; i++)
        DrawIconEx(hDC,
        gBalls[i].x,
        gBalls[i].y,
        gBalls[i].hIcon,
        48,
        48,
        0,
        (HBRUSH)hBrush,
        DI_IMAGE);
}

And what about fChildPreview? As we said earlier, this flag states whether the screen saver is currently running in preview mode. I.e., if the user tries to watch the screen saver within the screen saver's control panel, fChildPreview is TRUE. Otherwise, it is FALSE.

Display Properties

Final note

The compiler generates a .EXE file for this project. However, you need to rename it to .SCR so that the screen saver control panel can load it within the Screen Saver combo-box (as shown above). Another important thing to remember, is to include an ICON identified by ID_APP in your application. This will be the screen saver's icon, since the library introduces this ID as the application's ID when registering the window class.

cls.hIcon = LoadIcon(hInst, MAKEINTATOM(ID_APP));

That's all folks - Aloha!

Further improvements

I've got an idea that improves this saver, that is keeping the current desktop as the background of the saver and start the animating process on that. To understand what I am talking about, add the WM_ERASEBKGND message to your window procedure and compile the program again. Of course, this improvement will be possible if, and only if, I can convince my boss!

Any comments, suggestions and/or questions are welcomed.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Architect
United States United States
Mehdi Mousavi is a professional computer programmer. He has written many programs using C, C++, MFC, Win32, COM, DCOM, JavaScript, ASP, HTML, DHTML, SQL, C# and MC++.

Comments and Discussions

 
QuestionUnicode compilation Pin
virtualnik28-Jul-11 0:45
virtualnik28-Jul-11 0:45 
AnswerRe: Unicode compilation Pin
Jinoh7-Apr-12 12:14
Jinoh7-Apr-12 12:14 
GeneralWM_MOUSEMOVE is being continuously sent Pin
dc_200021-Oct-10 12:58
dc_200021-Oct-10 12:58 
AnswerRe: WM_MOUSEMOVE is being continuously sent Pin
Jinoh7-Apr-12 3:47
Jinoh7-Apr-12 3:47 
GeneralCode by VS 2005 with C++ . Pin
leminhphung9-Dec-08 22:35
leminhphung9-Dec-08 22:35 
AnswerRe: Code by VS 2005 with C++ . Pin
Jinoh7-Apr-12 3:49
Jinoh7-Apr-12 3:49 
QuestionScreen Saver Issues Pin
boloni00715-Apr-07 9:27
boloni00715-Apr-07 9:27 
AnswerRe: Screen Saver Issues Pin
wantsonson227-Sep-07 8:11
wantsonson227-Sep-07 8:11 
GeneralRe: Screen Saver Issues Pin
wantsonson229-Sep-07 7:52
wantsonson229-Sep-07 7:52 
AnswerRe: Screen Saver Issues Pin
Jinoh7-Apr-12 2:34
Jinoh7-Apr-12 2:34 
GeneralDebugging tip Pin
Punchey7-May-06 16:14
Punchey7-May-06 16:14 
GeneralQuestion Pin
alexei_p6-Nov-04 0:04
alexei_p6-Nov-04 0:04 
GeneralRe: Question Pin
Member 1333663226-May-05 16:20
Member 1333663226-May-05 16:20 
GeneralRe: Question Pin
alexei_p28-May-05 3:23
alexei_p28-May-05 3:23 
GeneralThanks! Pin
mark0matic12-May-04 18:10
mark0matic12-May-04 18:10 
GeneralRe: Thanks! Pin
Mehdi Mousavi26-Jul-04 10:02
Mehdi Mousavi26-Jul-04 10:02 
GeneralScreensaver newbie Pin
Anonymous20-Apr-04 1:43
Anonymous20-Apr-04 1:43 
Generalpassword protecting screensaver Pin
jancek11-Nov-03 23:31
jancek11-Nov-03 23:31 
GeneralSome problems Pin
d_kilshtein3-Apr-03 3:50
d_kilshtein3-Apr-03 3:50 
GeneralRe: Some problems Pin
Mehdi Mousavi3-Apr-03 8:42
Mehdi Mousavi3-Apr-03 8:42 
GeneralNewbie assistance Pin
GeoffTek6-Mar-03 0:48
sussGeoffTek6-Mar-03 0:48 
Please help me I get the following errors:

Compiling...
BallFusion.cpp
C:\Documents and Settings\geoff\Desktop\temp\BallFusion\BallFusion.cpp(22) : error C2065: 'IDI_REDBALL' : undeclared identifier
C:\Documents and Settings\geoff\Desktop\temp\BallFusion\BallFusion.cpp(23) : error C2065: 'IDI_GREENBALL' : undeclared identifier
C:\Documents and Settings\geoff\Desktop\temp\BallFusion\BallFusion.cpp(24) : error C2065: 'IDI_BLUEBALL' : undeclared identifier
C:\Documents and Settings\geoff\Desktop\temp\BallFusion\BallFusion.cpp(25) : error C2065: 'IDI_PURPLEBALL' : undeclared identifier
C:\Documents and Settings\geoff\Desktop\temp\BallFusion\BallFusion.cpp(26) : error C2065: 'IDI_LIGHTREDBALL' : undeclared identifier
C:\Documents and Settings\geoff\Desktop\temp\BallFusion\BallFusion.cpp(27) : error C2065: 'IDI_YELLOWBALL' : undeclared identifier
C:\Documents and Settings\geoff\Desktop\temp\BallFusion\BallFusion.cpp(28) : error C2065: 'IDI_BLACKBALL' : undeclared identifier
C:\Documents and Settings\geoff\Desktop\temp\BallFusion\BallFusion.cpp(30) : error C2065: 'i' : undeclared identifier
C:\Documents and Settings\geoff\Desktop\temp\BallFusion\BallFusion.cpp(38) : error C2065: 'x' : undeclared identifier
C:\Documents and Settings\geoff\Desktop\temp\BallFusion\BallFusion.cpp(38) : error C2065: 'sin' : undeclared identifier
C:\Documents and Settings\geoff\Desktop\temp\BallFusion\BallFusion.cpp(38) : error C2018: unknown character '0xdf'
C:\Documents and Settings\geoff\Desktop\temp\BallFusion\BallFusion.cpp(38) : error C2065: 'cos' : undeclared identifier
C:\Documents and Settings\geoff\Desktop\temp\BallFusion\BallFusion.cpp(38) : error C2018: unknown character '0xdf'
C:\Documents and Settings\geoff\Desktop\temp\BallFusion\BallFusion.cpp(38) : error C2018: unknown character '0xdf'
C:\Documents and Settings\geoff\Desktop\temp\BallFusion\BallFusion.cpp(39) : error C2146: syntax error : missing ';' before identifier 'y'
C:\Documents and Settings\geoff\Desktop\temp\BallFusion\BallFusion.cpp(39) : error C2065: 'y' : undeclared identifier
C:\Documents and Settings\geoff\Desktop\temp\BallFusion\BallFusion.cpp(39) : error C2018: unknown character '0xdf'
C:\Documents and Settings\geoff\Desktop\temp\BallFusion\BallFusion.cpp(41) : error C2146: syntax error : missing ';' before identifier 'xpos'
C:\Documents and Settings\geoff\Desktop\temp\BallFusion\BallFusion.cpp(41) : error C2065: 'xpos' : undeclared identifier
C:\Documents and Settings\geoff\Desktop\temp\BallFusion\BallFusion.cpp(42) : error C2065: 'ypos' : undeclared identifier
C:\Documents and Settings\geoff\Desktop\temp\BallFusion\BallFusion.cpp(51) : error C2065: 'hBrush' : undeclared identifier
C:\Documents and Settings\geoff\Desktop\temp\BallFusion\BallFusion.cpp(51) : error C2440: '=' : cannot convert from 'struct HBRUSH__ *' to 'int'
This conversion requires a reinterpret_cast, a C-style cast or function-style cast
C:\Documents and Settings\geoff\Desktop\temp\BallFusion\BallFusion.cpp(52) : error C2065: 'uTimer' : undeclared identifier
C:\Documents and Settings\geoff\Desktop\temp\BallFusion\BallFusion.cpp(61) : error C2065: 'rc' : undeclared identifier
C:\Documents and Settings\geoff\Desktop\temp\BallFusion\BallFusion.cpp(61) : error C2228: left of '.left' must have class/struct/union type
C:\Documents and Settings\geoff\Desktop\temp\BallFusion\BallFusion.cpp(62) : error C2228: left of '.right' must have class/struct/union type
C:\Documents and Settings\geoff\Desktop\temp\BallFusion\BallFusion.cpp(63) : error C2228: left of '.top' must have class/struct/union type
C:\Documents and Settings\geoff\Desktop\temp\BallFusion\BallFusion.cpp(64) : error C2228: left of '.bottom' must have class/struct/union type
C:\Documents and Settings\geoff\Desktop\temp\BallFusion\BallFusion.cpp(71) : error C2065: 'hDC' : undeclared identifier
Error executing cl.exe.

BallFusion.exe - 29 error(s), 0 warning(s)


I know I have assembled the code wrong but I do not what I have done wrong!


Many thanx

GeoffTekCry | :((
AnswerRe: Newbie assistance Pin
Jinoh7-Apr-12 3:36
Jinoh7-Apr-12 3:36 
GeneralDirectX Screensaver Pin
J.C. Coelho2-Jan-02 8:07
J.C. Coelho2-Jan-02 8:07 
GeneralRe: DirectX Screensaver Pin
Carlos Antollini2-Jan-02 8:19
Carlos Antollini2-Jan-02 8:19 
GeneralRe: DirectX Screensaver Pin
J.C. Coelho2-Jan-02 12:12
J.C. Coelho2-Jan-02 12:12 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.