Random Images Screen Saver - a complete screen saver example

By: Corbin Dunn

Abstract: How to easily create a screen saver with Delphi and make it behave properly

Random Images Screen Saver in Delphi
By Corbin Dunn
Delphi Developer Support

There are some older documents on our site that describe how to go about writing a 32-bit screen saver with Delphi. I decided to update them with a completely new example that addresses several issues in some other screen savers.

First you will want to download the complete source from CodeCentral so you can follow along.

If you are new to creating a screen saver, there are certain things you have to do. First, you have to process command line switches and start your screen saver in the right "mode" depending on what was passed to it. For example, if you are passed /c as a parameter, you should start in the "Configure" mode. Inside my sample source code, I have a ScreenSaverUtils unit that takes care of the command line processing and some other utility routines. Inside the interface notice the global variables and function:

type
  TScreenSaverMode = (ssPassword, ssPreview, ssConfigure, ssRun);
var
  ScreenSaverMode: TScreenSaverMode;
  ParentSaverHandle: THandle = 0;

procedure SetScreenSaverMode;
Calling SetScreenSaverMode as the first thing in the screen savers .dpr file will cause ScreenSaverMode and optionally ParentSaverHandle (if present) to be properly set. I can then use it in the .dpr file to create the screen saver form, or the configuration form:
begin
  // First, process the Params passed to see
  // what mode the screen saver is to run in
  // This set's the global variable's ScreenSaverMode
  // and ParentHandle. If the mode was to set the password,
  // a call to this function will never return (it calls Halt).
  SetScreenSaverMode;

  Randomize;
  Application.Initialize;
  Application.Title := 'Random Images Screen Saver';
  // Remove the start bar button
  SetWindowLong(Application.Handle, GWL_EXSTYLE, WS_EX_TOOLWINDOW);
  if ScreenSaverMode = ssConfigure then
  begin
    // Create the configure form to configure the screen saver
    Application.CreateForm(TConfigureForm, ConfigureForm);
  end
  else
  begin
    // We are either doing a preview or actually running the screen saver
    Application.CreateForm(TMainForm, MainForm);
  end;
  Application.Run;
end.

ConfigureForm configures some registry settings for the screen saver. When the user selects "Settings..." from inside of the Display Properties of the control panel the configuration form will be shown. Some screen saver's don't modally show their configuration form, so it is possible to configure two screen savers at the same time. To correct this problem, I had to use the ParentSaverHandle, which should be properly set up after a call to SetScreenSaverMode. Inside of TConfigureForm.FormCreate I have:

  if ParentSaverHandle <> 0 then
  begin
    EnableWindow(GetParent(ParentSaverHandle), False);
  end;
ParentSaverHandle is the handle to the little mini-desktop window that we are to draw a little "preview" on in the Display Properties dialog box. The parent of this little window is the actual Display Properties dialog box. The call to GetParent retrieves the Display Properties dialog box, and EnableWindow actually disables the window (making the configuration form modal). Notice the corresponding code in TConfigureForm.FormDestroy that re-enables the Display Properties dialog and brings it to the front:
  if ParentSaverHandle <> 0 then
  begin
    // The parent of the handle passed to us is
    // the main Properties dialog box
    EnableWindow(GetParent(ParentSaverHandle), True);
    SetForegroundWindow(GetParent(ParentSaverHandle));
  end;

The next thing to take a look at is the main screen saver form: MainForm, inside of MainFrm.pas. This form is created when the ScreenSaverMode is ssRun or ssPreview. When it is ssRun, we want the screen saver to:

  1. Run in full screen mode
  2. Hide the mouse
  3. Close when the mouse is moved or a key is pressed
  4. Prompt for a password on Windows 95/98/Me
To make it run in full screen mode, I set the BorderStyle property of MainForm to be bsNone and the FormStyle to be fsStayOnTop. Then, in the OnCreate event I have something close to this:
  Top := 0;
  Left := 0;
  Width := Screen.Width;
  Height := Screen.Height;
  if ScreenSaverMode = ssRun then
  begin
    SystemParametersInfo(SPI_SCREENSAVERRUNNING,1,@Dummy,0);
    SetCapture(Self.Handle);
    ShowCursor(False);
  end
Setting the width and height based on the Screen's properties makes it take up the full screen. The call to SystemParametersInfo is a crude way of telling Windows that the screen saver is running. SetCapture allows us to get all mouse movements, and ShowCursor(False); hides the cursor. The OnDestroy event simply undoes all these things.

To test for key's being pressed, I simply call Close in the OnKeyDown event. Mouse movements are a little trickier because we want to have a certain tolerance to mouse movement. Take a look at TMainForm.FormMouseMove for how to do this.

Next, we have to prompt for a password, if this option is turned on (in Windows 95/98/Me only - Windows NT and 2000 do it for you). In the OnCloseQuery event I simply call the function PromptIfPasswordNeeded from ScreenSaverUtils.pas. If the function returns True, then CanClose is set to True and the screen saver will exit.

The actual drawing for the screen saver is done by timer events. The code is rather simple, and is left for you to examine yourself. Basically, you just draw whatever you want to the MainForm's Canvas.

The next bit of trouble is to create the little preview in the Display Properties dialog box. If the ScreenSaverMode is ssPreview, then the OnCreate event of the MainForm set's the Application to not show the MainForm (with Application.ShowMainForm := False) and then calls TMainForm.DoPreviewDrawing. Here is this function:

procedure TMainForm.DoPreviewDrawing;
var
  Rect: TRect;
  Bitmap: TBitmap;
  Canvas: TCanvas;
begin
  FreeMutex; // Free the mutex so that the screen saver will actually run in preview
  Canvas := TCanvas.Create;
  try
    Canvas.Handle := GetDC(ParentSaverHandle);
    try
      GetWindowRect(ParentSaverHandle, Rect);
      ...
      while IsWindowVisible(ParentSaverHandle) do
      begin
        // Drawing to Canvas.Handle will create your preview
        ...
      end;
    finally
      ReleaseDC(ParentSaverHandle, Canvas.Handle);
    end;
  finally
    Canvas.Free;
  end;
  Application.Terminate; // Done with the preview, so quit
end;

First, notice the call to FreeMutex, which is inside of the unit SingleInstance. This unit allows only one instance of the screen saver to run at any time. If we don't do this, then every screen saver timeout period, Windows will start a new instance of your screen saver, even if it is already running! The call to FreeMutex allows the Preview button to work correctly inside of the Display Settings dialog box (otherwise, timing issues will not allow it to show).

We get the Device Context (DC) from the little preview window that was passed to the Application via parameters. Setting the TCanvas's Handle allows us to easily draw to it with a familiar Delphi class. Once the ParentSaverHandle window is not visible anymore, we terminate the application. The code to do the actual drawing is left out for simplicity.

Inside of the Display Properties dialog box you may have noticed that some screen savers specify a long name for the screen saver. In particular, it is different from just the executable's name. Microsoft claims that you can set this by creating a string table resource with an ID of 1 a value of whatever string you want. The included text file delphi_string_table.rc can be compiled into a resource (.res) file with the included create_res.bat (you may have to modify the path information if you installed Delphi 5 into a different location). Inside of the project's source I then have {$R delphi_string_table.RES} to link in the resource. However, this doesn't always work. To make it work on Windows 2000/NT I discovered that the screen saver's executable name MUST be less than 8 characters long, AND all lowercase. I have no idea why this is required, but it seems to work.

Finally, you should be able to compile the source and you will have a nice little sample screen saver.

Download the complete source from CodeCentral


Server Response from: ETNASC03