Click here to Skip to main content
Click here to Skip to main content

Screen Saver AppWizard

, 17 Jun 2004
Rate this:
Please Sign up or sign in to vote.
An article on creating a Screen Saver AppWizard

Introduction

This is my first article in code project, so I hope it will be ok. I've always wondered how to make a screen saver, and after that how to go on automating the process of creating one through a Custom Wizard. After understanding how to make a screen saver, I tried to set up an AppWizard to automate the process.

Background

It came very useful to read Dudi Avramov's Driver Wizard Article to get the basic idea of making a Custom AppWizard. For the Screen Saver part I found very useful the Mehdi Mousavi's Article Creating a Screen Saver

Using the code

This article covers the basic part of creating a Custom Appwizard for a Screen Saver Application, that means that the Wizard does not include Dialogs or other more complex features. But let's start describing the Screen Saver.

The Screen Saver

Creating a Screen Saver is nothing more than creating a simple Win32 application but without implementing an entry point like WinMain. but just the message handler procedure (which is also known as WndProc or DialogProc), that for Screen Savers looks like this, and is defined int the scrnsave.h in the VS include directory.

LRESULT WINAPI ScreenSaverProc(HWND hWnd, UINT message, 
  WPARAM wParam, LPARAM lParam)
That is not that much different from other win32 application message handlers: there is a Window handler, a message and the usual 2 params. The implementations does not differ from thousand other message handlers:
LRESULT WINAPI ScreenSaverProc(HWND hWnd, UINT message, 
  WPARAM wParam, LPARAM lParam)
{

  // some variables

  switch(message)
  {
  case WM_CREATE:

    // some code

    break;

    case WM_DESTROY:

    // some other code

    break;

  case WM_TIMER:

    // and other

    break;

  case WM_PAINT:

    // and other again

    break;

    default:
    return DefScreenSaverProc(hWnd, message, wParam, lParam);
  }

  return 0;
}

Every other unhandled message is passed to the default message handler DefScreenSaverProc, as stated in the header file function comments:

/* This function performs default message processing.  Currently handles
 * the following messages:
 *
 * WM_SYSCOMMAND:   return FALSE if wParam is SC_SCREENSAVE or SC_CLOSE
 *
 * WM_DESTROY:      PostQuitMessage(0)
 *
 * WM_SETCURSOR:    By default, this will set the cursor to a null cursor,
 *                  thereby removing it from the screen.
 *
 * WM_LBUTTONDOWN:
 * WM_MBUTTONDOWN:
 * WM_RBUTTONDOWN:
 * WM_KEYDOWN:
 * WM_KEYUP:
 * WM_MOUSEMOVE:    By default, these will cause the program to terminate.
 *                  Unless the password option is enabled.  In that case
 *                  the DlgGetPassword() dialog box is brought up.
 *
 * WM_NCACTIVATE:
 * WM_ACTIVATEAPP:
 * WM_ACTIVATE:     By default, if the wParam parameter is FALSE (signifying
 *                  that transfer is being taken away from the application),
 *                  then the program will terminate.  Termination is
 *                  accomplished by generating a WM_CLOSE message.  This way,
 *                  if the user sets something up in the WM_CREATE, a
 *                  WM_DESTROY will be generated and it can be destroyed
 *                  properly.
 *                  This message is ignored, however is the password option
 *                  is enabled.
 */

Beyond the ScreenSaverProc, other functions are defined in the header file:


/* A function is also needed for configuring the screen saver.  The function
 * should be exactly like it is below and must be exported such that the
 * program can use MAKEPROCINSTANCE on it and call up a dialog box. Further-
 * more, the template used for the dialog must be called
 * ScreenSaverConfigure to allow the main function to access it...
 */

BOOL WINAPI ScreenSaverConfigureDialog (HWND hDlg, UINT message, 
  WPARAM wParam, LPARAM lParam);


/* To allow the programmer the ability to register child control windows, this
 * function is called prior to the creation of the dialog box.  Any
 * registering that is required should be done here, or return TRUE if none
 * is needed...
 */

BOOL WINAPI RegisterDialogClasses (HANDLE hInst);

and

/*
 * This message is sent to the main screen saver window when password
 * protection is enabled and the user is trying to close the screen saver.  You
 * can process this message and provide your own validation technology.  If you
 * process this message, you should also support the ScreenSaverChangePassword
 * function, described below.  Return zero from this message if the password
 * check failed.  Return nonzero for success.  If you run out of memory or
 * encounter a similar class of error, return non-zero so the user isn't left
 * out in the cold.  The default action is to call the Windows Master
 * Password Router to validate the user's password.
 */

void WINAPI ScreenSaverChangePassword( HWND hParent );

The entry point of a screen saver application is implemented in the scrnsave.lib that ships with the other Visual Studio libraries. So it must be included in the libraries of the project (even if it will be done by the wizard Smile | :) . And the output program must have the ".scr" extension (no worries, the wizard will do this for you).

Say then that you'll have now a minimal set of files that implements a screen saver:

  • main.cpp (that implements the interfaces exported by scrnsave.lib and that includes only StdAfx.h)
  • StdAfx.h (this includes the minimal necessary files that is windows.h and scrnsave.h
  • StdAfx.cpp (this only includes StdAfx.h)
optionally you could have an main icon for your saver.

Your main.cpp will appear like this:


#include "stdafx.h"

#define szAppName  "MyScreenSaver"
#define szAuthor  "Me It's the Author"
#define szPreview  "Explorer Preview Writes!"

BOOL WINAPI ScreenSaverConfigureDialog(HWND hDlg, UINT message, 
  WPARAM wParam, LPARAM lParam)
{
  // place your code here

  return FALSE;
}

BOOL WINAPI RegisterDialogClasses(HANDLE hInst)
{
  // place your code here
  return TRUE;
}

LRESULT WINAPI ScreenSaverProc(HWND hWnd, UINT message, 
  WPARAM wParam, LPARAM lParam)
{

  static PAINTSTRUCT ps = {NULL};
  static HDC hDC = NULL;
  static HBRUSH hBrush = NULL;
  static UINT uTimer = 0;
  static int xpos, ypos;
  static RECT rc;
  static RECT wrec;

  static int delay = 6500;

  switch(message)
  {
  case WM_CREATE:

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

    xpos/=2;
    ypos/=2;

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

    PostMessage(hWnd,WM_TIMER,0,0);

    break;

    case WM_DESTROY:
    if(uTimer)
      KillTimer(hWnd, uTimer);

    if(hBrush)
      DeleteObject(hBrush);

    PostQuitMessage(0);
    break;

  case WM_TIMER:
    {

      /* Here you can load a file or a resource or make display algorithms*/

    }

    GetWindowRect(hWnd,&wrec);

    InvalidateRect(hWnd, &wrec, TRUE);
    break;

  case WM_PAINT:
    hDC = BeginPaint(hWnd, &ps);

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

      /* code to Display what you want to */

    }

    EndPaint(hWnd, &ps);
    break;

    default:
    return DefScreenSaverProc(hWnd, message, wParam, lParam);
  }

  return 0;
}

The result build and run of the screen saver:

and the execution:

The preview in explorer will look like this:

The Screen Saver AppWizard

Beyond all the implementations that one could apply to a screen saver (rotating images, geometrics evolutions and so on), the interesting part is that trough a wizard we provide the skeleton of the application and then the developer can concentrate on the display stuff and algos. The AppWizard is nothing more than a DLL module renamed ".AWX" and copied under the template directory of the Visual Studio VC dir. So lets start from begin:

Create a new Custom AppWizard Application and follow the steps:

So we will create a custom new project with zero steps (that means that there will be no further dialogs or configuration steps in the wizard, but it will straight create the necessary files to set up a screen saver). The Visual C++ IDE then will have the following aspect:

Take care of including three further header files in StdAfx.h, necessary to let CL recognize the IBuildProject interface and dependencies:


#include <atlbase.h>
#include <ObjModel\bldguid.h>
#include <ObjModel\bldauto.h>

We will concentrate on the implementation file ScreenSaverWizardAw.cpp that do the main job. Initially it is like the one below:


#include "stdafx.h"
#include "ScreenSaverWizard.h"
#include "ScreenSaverWizardaw.h"

#ifdef _PSEUDO_DEBUG
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif

// This is called immediately after the custom AppWizard is loaded.  Initialize
//  the state of the custom AppWizard here.
void CScreenSaverWizardAppWiz::InitCustomAppWiz()
{
  // There are no steps in this custom AppWizard.
  SetNumberOfSteps(0);

  // TODO: Add any other custom AppWizard-wide initialization here.
}

// This is called just before the custom AppWizard is unloaded.
void CScreenSaverWizardAppWiz::ExitCustomAppWiz()
{
  // TODO: Add code here to deallocate resources used by the custom AppWizard
}

// This is called when the user clicks "Create..." on the New Project dialog
//  or "Next" on one of the custom AppWizard's steps.
CAppWizStepDlg* CScreenSaverWizardAppWiz::Next(CAppWizStepDlg* pDlg)
{
  return NULL;
}

void CScreenSaverWizardAppWiz::CustomizeProject(IBuildProject* pProject)
{
  // TODO: Add code here to customize the project.  If you don't wish
  //  to customize project, you may remove this virtual override.

  // This is called immediately after the default Debug and Release
  //  configurations have been created for each platform.  You may customize
  //  existing configurations on this project by using the methods
  //  of IBuildProject and IConfiguration such as AddToolSettings,
  //  RemoveToolSettings, and AddCustomBuildStep. These are documented in
  //  the Developer Studio object model documentation.

  // WARNING!!  IBuildProject and all interfaces you can get from it are OLE
  //  COM interfaces.  You must be careful to release all new interfaces
  //  you acquire.  In accordance with the standard rules of COM, you must
  //  NOT release pProject, unless you explicitly AddRef it, since pProject
  //  is passed as an "in" parameter to this function.  See the documentation
  //  on CCustomAppWiz::CustomizeProject for more information.
}


// Here we define one instance of the CScreenSaverWizardAppWiz class.  You can access
//  m_Dictionary and any other public members of this class through the
//  global ScreenSaverWizardaw.
CScreenSaverWizardAppWiz ScreenSaverWizardaw;

What is to be done is implement the minimal methods to set the whole thing up:
  • Implement the CScreenSaverWizardAppWiz::Next(CAppWizStepDlg* pDlg) method:

// This is called when the user clicks "Create..." on the New Project dialog
CAppWizStepDlg* CScreenSaverAppWiz::Next(CAppWizStepDlg* pDlg)
{
  ASSERT(pDlg == NULL);  // By default, this custom AppWizard has no steps

  // Set template macros based on the project name entered by the user.

  // Get value of $$root$$ (already set by AppWizard)
  CString strRoot;
  m_Dictionary.Lookup(_T("root"), strRoot);

  // Set value of $$Doc$$, $$DOC$$
  CString strDoc = strRoot.Left(6);
  m_Dictionary[_T("Doc")] = strDoc;
  strDoc.MakeUpper();
  m_Dictionary[_T("DOC")] = strDoc;

  // Set value of $$MAC_TYPE$$
  strRoot = strRoot.Left(4);
  int nLen = strRoot.GetLength();
  if (strRoot.GetLength() < 4)
  {
    CString strPad(_T(' '), 4 - nLen);
    strRoot += strPad;
  }
  strRoot.MakeUpper();
  m_Dictionary[_T("MAC_TYPE")] = strRoot;

  // Return NULL to indicate there are no more steps.  (In this case, there are
  //  no steps at all.)
  return NULL;
}

  • Implement the CScreenSaverWizardAppWiz::CustomizeProject(IBuildProject* pProject) method:
void CScreenSaverAppWiz::CustomizeProject(IBuildProject* pProject)
{

  CComPtr<IConfigurations> pConfigs;
  HRESULT hr=pProject->get_Configurations(&pConfigs);
  if(FAILED(hr))
  {
    AfxMessageBox("An error occurred while obtaining the "
         "IConfigurations interface pointer");
    return;
  }
  CComPtr<IConfiguration> pConfig;
  CComVariant index;
  VARIANT dummy = {0};
  CComBSTR Name;
  CString text;
  CString output;

  long Count=0;
  pConfigs->get_Count(&Count);

  // Iterate through all the configurations of the project
  for(int i=1; i <= Count; i++)
  {
    index=i;
    hr=pConfigs->Item(index, &pConfig);
    if(FAILED(hr))
    {
      AfxMessageBox(
        "An error occurred while obtaining the IConfiguration pointer");
      return;
    }
    pConfig->get_Name(&Name);
    text = Name;

    if (text.Find("Debug") == -1)
      output = "Release";
    else
      output = "Debug";

    text.Format("/out:\"%s/%s.scr\"",output,m_Dictionary["Root"]);
    pConfig->AddToolSettings(L"link.exe", text.AllocSysString(), dummy);

    pConfig->AddToolSettings(L"mfc", L"0", dummy);
    pConfig->AddToolSettings(L"link.exe", L"/subsystem:windows", dummy);
    pConfig->AddToolSettings(L"link.exe", L"/incremental:yes", dummy);
    pConfig->AddToolSettings(L"link.exe", L"/machine:I386", dummy);
    pConfig->AddToolSettings(L"link.exe", L"/nodefaultlib:\"MSVCRTD\"", dummy);

    // change the preprocessor definitions
    pConfig->AddToolSettings(L"cl.exe", L"/D \"_WINDOWS\"", dummy);
    pConfig->AddToolSettings(L"cl.exe", L"/nologo", dummy);
    pConfig->AddToolSettings(L"cl.exe", L"/D \"_MBCS\"", dummy);
    pConfig->AddToolSettings(L"cl.exe", L"/D \"WIN32\"", dummy);
    pConfig->AddToolSettings(L"cl.exe", L"/Od", dummy);
    pConfig->AddToolSettings(L"cl.exe", L"/MD", dummy);
    pConfig->AddToolSettings(L"cl.exe", L"/W3", dummy);
    pConfig->AddToolSettings(L"cl.exe", L"/ZI", dummy);  
           // Program Database for "Edit & Continue" can not 
           // be defined when /driver option is defined
    pConfig->AddToolSettings(L"cl.exe", L"/GZ", dummy);  
          //GZ initializes all local variables not explicitly 
          // initialized by the program. It fills all memory used 
          // by these variables with 0xCC
    pConfig->AddToolSettings(L"cl.exe", L"/Zi", dummy);  
         // Program Database
    pConfig->AddToolSettings(L"cl.exe", L"/Oi", dummy);  
    pConfig->AddToolSettings(L"cl.exe", L"/Gz", dummy);  
         // __stdcall calling convention
    pConfig->AddToolSettings(L"cl.exe", L"/Gz", dummy);


    // Change the libraries
    pConfig->AddToolSettings(L"link.exe", L"kernel32.lib", dummy);
    pConfig->AddToolSettings(L"link.exe", L"user32.lib", dummy);
    pConfig->AddToolSettings(L"link.exe", L"gdi32.lib", dummy);
    pConfig->AddToolSettings(L"link.exe", L"winspool.lib", dummy);
    pConfig->AddToolSettings(L"link.exe", L"comdlg32.lib", dummy);
    pConfig->AddToolSettings(L"link.exe", L"advapi32.lib", dummy);
    pConfig->AddToolSettings(L"link.exe", L"shell32.lib", dummy);
    pConfig->AddToolSettings(L"link.exe", L"ole32.lib", dummy);
    pConfig->AddToolSettings(L"link.exe", L"oleaut32.lib", dummy);
    pConfig->AddToolSettings(L"link.exe", L"uuid.lib", dummy);
    pConfig->AddToolSettings(L"link.exe", L"odbc32.lib", dummy);
    pConfig->AddToolSettings(L"link.exe", L"odbccp32.lib", dummy);
    pConfig->AddToolSettings(L"link.exe", L"scrnsave.lib", dummy);

    pConfig=NULL;
  }
  pConfigs=NULL;
}
This method sets up the Project Configuration of compiler, linker and output filename. Obviously this is a minimal and very simple implementation, but I think it gives the idea.

Further Steps

In the above images that report the workspace aspect after the creation of theproject, you will notice a particular directory named "Template" that contains two (for the moment) files: confirm.inf and newproj.inf. In this directory there are all the files that the wizard will use to create the skeleton for your saver application on your input basis.

  • confirm.inf:
//TODO: Place descriptive text about 
//the project generated by your
//custom AppWizard here.

As stated, in this file you will provide a description of the files the wizard will create for you or whatever you want to tell the programmer at the end of the wizard and before the creation of the project.

  • newproj.inf:
$$// newproj.inf = template for list of template files
$$//  format is 'sourceResName' \t 'destFileName'
$$//    The source res name may be preceded by any combination 
           of '=', '-',  '!', '?', ':', '#', and/or '*'
$$//       '=' => the resource is binary
$$//       '-' => the file should not be added to the project 
           (all files are added to the project by default)
$$//       '!' => the file should be marked exclude from build
$$//       '?' => the file should be treated as a help file
$$//       ':' => the file should be treated as a resource
$$//       '#' => the file should be treated as a template (implies '!')
$$//       '*' => bypass the custom AppWizard's resources when loading
$$//  if name starts with / => create new subdir

This is a more complex one. Inside this file you must specify the template files used for the application. The $$// is a comment.

Adding project related files as templates

To go on now it is necessary to include the skeleton files of the Screen Saver (our main.cpp, StdAfx.h and StdAfx.cpp) in the Template Directory. It will be necessary to rename main.cpp to root.cpp and StdAfx.* to SAfx.* or other because the IDE complains about already existent names when launching the AppWizard generated Getting to have:

And specify them in newproj.inf:


$$// newproj.inf = template for list of template files
$$//  format is 'sourceResName' \t 'destFileName'
$$//    The source res name may be preceded by any 
           combination of '=', '-',  '!', '?', ':', '#', and/or '*'
$$//       '=' => the resource is binary
$$//       '-' => the file should not be added to the project 
           (all files are added to the project by default)
$$//       '!' => the file should be marked exclude from build
$$//       '?' => the file should be treated as a help file
$$//       ':' => the file should be treated as a resource
$$//       '#' => the file should be treated as a template (implies '!')
$$//       '*' => bypass the custom AppWizard's resources when loading
$$//  if name starts with / => create new subdir

+root.cpp $$Root$$.cpp
+SAfx.h  StdAfx.h
+SAfx.cpp StdAfx.cpp

Take care of using TAB and not SPACE to separate filenames on a row in the newproj.inf file

You will have certainly noticed the notation $$Root$$.cpp in the file. That is the way the Wizard recognize that the file is a template file and it will be referenced inside the internal code through the m_Dictionary.Find(T("root")). Everytime the Wizard finds $$Root$$ in the file it will substitute it with the name of the project you will provide. So the root.cpp (ex main.cpp) will be like:


#include "stdafx.h"

#define szAppName  "$$Root$$"
#define szAuthor  "Author of $$Root$$"
#define szPreview  "$$Root$$ Preview"

BOOL WINAPI ScreenSaverConfigureDialog(HWND hDlg, 
  UINT message, WPARAM wParam, LPARAM lParam)
{
  return FALSE;
}

BOOL WINAPI RegisterDialogClasses(HANDLE hInst)
{
  return TRUE;
}

LRESULT WINAPI ScreenSaverProc(HWND hWnd, UINT message, 
  WPARAM wParam, LPARAM lParam)
{

  static PAINTSTRUCT ps = {NULL};
  static HDC hDC = NULL;
  static HBRUSH hBrush = NULL;
  static UINT uTimer = 0;
  static int xpos, ypos;
  static RECT rc;
  static RECT wrec;

  static int delay = 6500;

  switch(message)
  {
  case WM_CREATE:

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

    xpos/=2;
    ypos/=2;

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

    PostMessage(hWnd,WM_TIMER,0,0);

    break;

    case WM_DESTROY:
    if(uTimer)
      KillTimer(hWnd, uTimer);

    if(hBrush)
      DeleteObject(hBrush);

    PostQuitMessage(0);
    break;

  case WM_TIMER:
    {

      /* Here you can load a file or a resource or make display algorithms*/

    }

    GetWindowRect(hWnd,&wrec);

    InvalidateRect(hWnd, &wrec, TRUE);
    break;

  case WM_PAINT:
    hDC = BeginPaint(hWnd, &ps);

    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 - 42, szAppName, strlen(szAppName));
      TextOut(hDC, 0, ypos * 2 - 25, szAuthor, strlen(szAuthor));

      /* code to Display what you want to */

    }

    EndPaint(hWnd, &ps);
    break;

    default:
    return DefScreenSaverProc(hWnd, message, wParam, lParam);
  }

  return 0;
}

Build the project and hope there's no error Smile | :) , After that you can open a new instance if the IDE and see that there's your Screen Saver Wizard ready to fire.

Hint

If you have the SDK installed, make sure that its directories (include and libraries) are specified after the Visual Studio ones in the Tools->options->Directories Section of the IDE: the Screen Saver Applications won't compile if the SDK paths comes first the Visual Studio ones.

Conclusions

As a first article, I must say that it was an experience. Maybe there are some errors and imprecision that will be corrected in future with the help of the community; but my special thanks goes to Dudi Avramov and Mehdi Mousavi that inspired me with their articles and work about AppWizard and Screen Saver. This work is a merge of the two. Thanks. Fabio.

History

  • 17 June 2004 v1.0

License

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

About the Author

Fabio Fornaro
Software Developer (Senior)
Italy Italy
Worked as a Java developer targeting J2EE and J2SE for half a decade.
 
Now he's employed for Avanade as a Senior Associate Consultant on Microsoft technologies.
 
His hobbies are Win32 programming using SDK, MFC, ATL and COM; playing guitar and listening to music.

Comments and Discussions

 
JokeSmall correction PinmemberQwerty823-Mar-06 19:48 
Generalquestion Pinmemberwangxuan20056-Jun-05 23:41 
Generalcustom wizard question Pinmemberwangxuan20056-Jun-05 23:40 
GeneralGreate job! PinmemberJohn R. Shaw18-Jun-04 10:11 
GeneralRe: Greate job! PinmemberFabio Fornaro19-Jun-04 0:56 

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

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

| Advertise | Privacy | Mobile
Web03 | 2.8.140709.1 | Last Updated 18 Jun 2004
Article Copyright 2004 by Fabio Fornaro
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid