Clear Night Software

How to make a Screen Saver

Creating a Windows Screen Saver using Visual Basic:

Essentials:

1. A Screen Saver is a program. Create a project and program it to do what you want your Screen Saver to do.

2. Windows can call your Screen Saver with various options. Your program must respond to the following:
- /S on the command line when Windows wants to run the Screen Saver or the user tests it.
- /P and a window handle to create the preview window on the Display Properties Dialog Box
- /A and a window handle when the user wishes to change the Screen Saver password.
See the code examples below.

3. When you are ready to Make EXE, you must follow these two important steps:
- Change the filename to end in .SCR instead of .EXE
screen shot
- Click the Options button, and in the Application Title field insert SCRNSAVE: before the title of the Screen Saver.
screen shot

4. In order to run your Screen Saver you must move the .SCR file into your Windows or Windows\System directory, then select it from your Display Properties.

5. To distribute your Screen Saver to other computers you should package it with any Controls and Libraries it uses.

Example Code

Here are the declarations for all the following examples. Paste them into a Module in the (General) (Declarations) section.

Declare Function SetParent Lib "user32" (ByVal hWndChild As Long, ByVal _
 hWndNewParent As Long) As Long
Declare Function GetWindowRect Lib "user32" (ByVal hWnd As Long, lpRect _
 As RECT) As Long
Declare Function BitBlt Lib "gdi32" (ByVal hDestDC As Long, ByVal X As Long, _
 ByVal Y As Long, ByVal nWidth As Long, ByVal nHeight As Long, ByVal hSrcDC _
 As Long, ByVal xSrc As Long, ByVal ySrc As Long, ByVal dwRop As Long) As Long
Declare Function GetDC Lib "user32" (ByVal hwnd As Long) As Long
Declare Function GetDesktopWindow Lib "user32" () As Long
Declare Function ReleaseDC Lib "user32" (ByVal hwnd As Long, ByVal hdc As Long) As Long
Declare Function ShowCursor& Lib "user32" (ByVal bShow&)
Declare Function SetWindowLong Lib "user32" Alias "SetWindowLongA" (ByVal hwnd As Long, ByVal nIndex As Long, ByVal dwNewLong As Long) As Long
Declare Function CallWindowProc Lib "user32" Alias "CallWindowProcA" (ByVal lpPrevWndFunc As Long, ByVal hwnd As Long, ByVal Msg As Long, ByVal wParam As Long, ByVal lParam As Long) As Long
Declare Function SystemParametersInfo Lib "user32" Alias "SystemParametersInfoA" (ByVal uAction As Long, ByVal uParam As Long, ByRef lpvParam As Any, ByVal fuWinIni As Long) As Long

Public Const GWL_WNDPROC = (-4)
Public Const WM_CLOSE = &H10
Public Const SRCCOPY = &HCC0020 ' (DWORD) dest = source
Public Const HWND_TOPMOST = -1
Public Const SPI_SCREENSAVERRUNNING = 97
Type RECT
        Left As Long
        Top As Long
        Right As Long
        Bottom As Long
End Type
Public lpPrevWndProc As Long
Public gHW As Long, gHWF As Long
Public gKillMe As Boolean

Create a subroutine "Main" to handle the command line options. Select Tools/Options/Project and change Startup Form to Sub Main.
screen shot

Sub main()
    dim strCommand as string
    strCommand = UCase(Command)
    If InStr(strCommand, "/S") > 0 Then
         frmScreenSaver.Show 1
    ElseIf InStr(strCommand, "/P") > 0 Then
        Load frmPreview
    ElseIf InStr(strCommand, "/A") > 0 Then
        frmPassword.Show 1
    Else
        frmSetup.Show 1
    End If
End Sub

frmScreenSaver code is up to you. See tips later on this page.

frmPreview is the form you create to fill in the Preview window on the Display Properties Dialog. Windows calls your program with /P and a number which is a handle to the Preview window. Code for this form needs to detect the message Windows sends to close down your preview. This example form has a Timer control which loads images into a Picture control to simulate the Screen Saver in miniature.

Private Sub Form_Load()
    On Error GoTo frmPreview_Load_err
    
    Dim rectPreview As RECT
    Dim hwndParent As Long
    
    hwndParent = CLng(Right(Command, Len(Command) - 3))	' window handle
    x = GetWindowRect(hwndParent, rectPreview)
    Picture1.Move 0, 0, (rectPreview.Right - rectPreview.Left) * Screen.TwipsPerPixelX, (rectPreview.Bottom - rectPreview.Top) * Screen.TwipsPerPixelY
    oldParent = SetParent(Picture1.hWnd, hwndParent)

    gHW = Picture1.hwnd
    gHWF = Me.hwnd
    Hook    ' receive Windows messages so Window can close
    Timer1.Enabled=True
    
frmPreview_Load_exit:
    Exit Sub
    
frmPreview_Load_err:
    MsgBox CStr(Err) & " " & Error$
    Resume frmPreview_Load_exit

End Sub

Private Sub Timer1_Timer()
    DoEvents
    
    If gKillMe Then End ' received Windows close message

    ' ... code to update Picture1 with your screen saver preview

End Sub

These routines must be in a code module, not attached to the form, in order for "AddressOf" to work:

' Next three routines are used to hook Windows messages for frmPreview:

Public Sub Hook()
   lpPrevWndProc = SetWindowLong(gHW, GWL_WNDPROC, _
   AddressOf WindowProc)
End Sub

Public Sub Unhook()
   Dim temp As Long
   temp = SetWindowLong(gHW, GWL_WNDPROC, _
   lpPrevWndProc)
End Sub

Sub WindowProc(ByVal hw As Long, ByVal umsg As Long, ByVal wp As Long, ByVal lp As Long)
    Dim x As Long
    
    If umsg = WM_CLOSE Then
        Unhook
        DoEvents
        x = SetParent(gHW, gHWF)
        DoEvents
        gKillMe = True
    Else
        x = CallWindowProc(lpPrevWndProc, hw, umsg, wp, lp)
    End If
End Sub

frmPassword should be a dialog to set the Windows Screen Saver password.

The following information about password handling is adapted from Microsoft's Knowledge Base Article ID: Q182382

The dialog box is displayed when the Screen Saver is started with a "/a <HWND>" argument, where <HWND> is an unsigned decimal number representing the HWND of the owner window of the password dialog box.

If no HWND is passed in the Password Change dialog box, the foreground window should own the Password Change dialog box.

When the Screen Saver runs it should check the HKEY_CURRENT_USER/ REGSTR_PATH_SCREENSAVE/REGSTR_VALUE_USESCRPASSWORD (REGSTR_PATH_SCREENSAVE and REGSTR_VALUE_USESCRPASSWORD can be found in regstr.h) registry key if it needs to use the Screen Saver's password protection.

If the Screen Saver does need to use its password protection, it needs to prevent the user from switching to other applications without giving the password. To do this the Screen Saver needs to call:

    Dim nPreviousState

    x = SystemParametersInfo(SPI_SCREENSAVERRUNNING, True, nPreviousState, 0)
This prevents the user from using ALT+TAB to switch to another application or CTRL+ALT+DELETE to kill the Screen Saver. When the user hits a key or moves the mouse, the Screen Saver confirms the password. If the password is validated, the Screen Saver calls the following to re-enable task switching:
    Dim nPreviousState

    x = SystemParametersInfo(SPI_SCREENSAVERRUNNING, False, nPreviousState, 0)

frmSetup should give the user access to any settings or parameters your program allows. It is also a good place to handle registration for shareware Screen Savers. Use the SaveSetting and GetSetting commands to access the Windows Registry.

Tips

Presumably you want your frmScreenSaver to occupy the whole screen area. You will also want to make the mouse pointer invisible while the Screen Saver is running. You also want your Screen Saver to cover all other windows, including 'floating' windows (set to "Always On Top"). Here is some code for frmScreenSaver. Timer1 produces the Screen Saver effects. To create the illusion of interacting with the windows which are already on the screen, you can grab a copy of the current screen image, and then modify it within your program.
Private Sub Form_Load()
     ' make mouse pointer invisible
    Do
    Loop Until ShowCursor(False) <-5 Width="Screen.Width:" Height="Screen.Height" ' setwindowpos topmost X="SetWindowPos(hwnd," HWND_TOPMOST, 0, 0, 0, 0, 0) Call GrabScreen timer1.Enabled="True" End Sub Sub GrabScreen() DoEvents picSnag.Width="Screen.Width:" picSnag.Height="Screen.Height" picSnag.Cls hwndSrc%="GetDesktopWindow()" hSrcDC&="GetDC(hwndSrc%)" ' BitBlt requires coordinates in pixels. hDestDC&="picSnag.hdc" dwRop&="SRCCOPY" w%="Screen.Width" / Screen.TwipsPerPixelX : h%="Screen.Height" / Screen.TwipsPerPixelY Suc%="BitBlt(hDestDC&," 0, 0, w%, _ h%, hSrcDC&, 0, 0, dwRop&) Dmy%="ReleaseDC(hwndSrc%," hSrcDC&) Me.Picture="picSnag.Image" End Sub 

For GrabScreen to work, you need a PictureBox control called picSnag on your form with the following properties:
Autoredraw=True
Scalemode=3 - Pixel
Visible=False