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)
{
switch(message)
{
case WM_CREATE:
break;
case WM_DESTROY:
break;
case WM_TIMER:
break;
case WM_PAINT:
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:
Beyond the ScreenSaverProc
, other functions are defined in the header file:
BOOL WINAPI ScreenSaverConfigureDialog (HWND hDlg, UINT message,
WPARAM wParam, LPARAM lParam);
BOOL WINAPI RegisterDialogClasses (HANDLE hInst);
and
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 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)
{
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:
{
}
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));
}
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
void CScreenSaverWizardAppWiz::InitCustomAppWiz()
{
SetNumberOfSteps(0);
}
void CScreenSaverWizardAppWiz::ExitCustomAppWiz()
{
}
CAppWizStepDlg* CScreenSaverWizardAppWiz::Next(CAppWizStepDlg* pDlg)
{
return NULL;
}
void CScreenSaverWizardAppWiz::CustomizeProject(IBuildProject* pProject)
{
}
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:
CAppWizStepDlg* CScreenSaverAppWiz::Next(CAppWizStepDlg* pDlg)
{
ASSERT(pDlg == NULL);
CString strRoot;
m_Dictionary.Lookup(_T("root"), strRoot);
CString strDoc = strRoot.Left(6);
m_Dictionary[_T("Doc")] = strDoc;
strDoc.MakeUpper();
m_Dictionary[_T("DOC")] = strDoc;
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;
}
- 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);
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);
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);
pConfig->AddToolSettings(L"cl.exe", L"/GZ", dummy);
pConfig->AddToolSettings(L"cl.exe", L"/Zi", dummy);
pConfig->AddToolSettings(L"cl.exe", L"/Oi", dummy);
pConfig->AddToolSettings(L"cl.exe", L"/Gz", dummy);
pConfig->AddToolSettings(L"cl.exe", L"/Gz", dummy);
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.
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.
$$
$$
$$
of '=', '-', '!', '?', ':', '#', and/or '*'
$$
$$
(all files are added to the project by default)
$$
$$
$$
$$
$$
$$
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
:
$$
$$
$$
combination of '=', '-', '!', '?', ':', '#', and/or '*'
$$
$$
(all files are added to the project by default)
$$
$$
$$
$$
$$
$$
+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:
{
}
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));
}
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
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.