Screen Saver AppWizard






4.97/5 (13 votes)
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 :).
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 onlyStdAfx.h
) - StdAfx.h (this includes the minimal necessary files that is
windows.h
andscrnsave.h
- StdAfx.cpp (this only includes
StdAfx.h
)
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) { CComPtrThis 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.pConfigs; HRESULT hr=pProject->get_Configurations(&pConfigs); if(FAILED(hr)) { AfxMessageBox("An error occurred while obtaining the " "IConfigurations interface pointer"); return; } CComPtr 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; }
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.cppTake 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 :), 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