text: I would like to start off this article by thanking all of those individuals who have sent me unbelievable number of emails appreciating my previous articles, specially the ISAPI Extension one! You cannot imagine how I have been inspired by those lovely emails. I do appreciate your time, and I hope that this article suits your needs too.
  Unlike the human life that could be hardly saved 'coz of being invaded by other different so-called humans, computer monitors could be easily saved against the phosphor burn using a screen saver! Today, I am going to talk about Windows Screen Savers, one of those interesting topics that most of the programmers out there would like to know about.
  A screen saver is just a plain Win32 application that will be launched automatically when the mouse and keyboard have been idle for a specified period of time. Whenever this timer expires, the user could see either a blank screen or a very complex animation. Thereafter, if the user presses a key on her keyboard or moves her mouse, the screen will be changed back to the normal one that disappeared when the screen saver started.
  To create your own screen saver, you have got two choices. First, you can create a normal Win32 application and handle the necessary messages sent to your window. Choosing the first way, you have to develop a program that creates a full screen window, as well as a window procedure that processes window messages sent to your application. Within the window procedure, you could have something as follows:
  switch(message) { case WM_SYSCOMMAND: if(wParam == SC_SCREENSAVE || wParam == SC_CLOSE) return FALSE; break; case WM_LBUTTONDOWN: case WM_MBUTTONDOWN: case WM_RBUTTONDOWN: case WM_KEYDOWN: case WM_KEYUP: case WM_MOUSEMOVE: //Quit the application here break; }
  In other words, you have to hard code the pieces required for your screen saver and take care of the messages that is common along all the Windows screen savers.
  The second choice, though, is trying to use a library file that is being shipped with your compiler and does all of the above-mentioned hard coding (and even more)! This way, you can focus on the actual problem - that is creating the visual effects. Obviously, we are going to use this library to avoid re-inventing wheels.
  The screen saver library (scrnsave.lib) contains the
  WinMain function. This function among the other things, registers a window class that looks something as follows:
  WNDCLASS cls; cls.hCursor = NULL; cls.hIcon = LoadIcon(hInst, MAKEINTATOM(ID_APP)); cls.lpszMenuName = NULL; cls.lpszClassName = "WindowsScreenSaverClass"; cls.hbrBackground = GetStockObject(BLACK_BRUSH); cls.hInstance = hInst; cls.style = CS_VREDRAW | CS_HREDRAW | CS_SAVEBITS | CS_DBLCLKS; cls.lpfnWndProc = (WNDPROC) ScreenSaverProc; cls.cbWndExtra = 0; cls.cbClsExtra = 0;
  In other words, it registers a black background window, having no mouse cursor and an icon identified by
  ID_APP. Moreover, it introduces its window procedure as
  ScreenSaverProc. This procedure is the heart of any screen saver, and almost all the coding is done here.
  This procedure, like all the other window procedures, is declared as follows:
  LONG ScreenSaverProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);
  where
  hWnd is the handle of the desktop window,
  message is the actual message sent to the screen saver window, and
  wParam and
  lParam are additional message-specific information. If you could remember from my previous "MFC vs. Win32" series, you could recall that whenever we do not need to process a message, we pass it to the
  DefWindowProc function. However, when developing a screen saver, we need to use the
  DefScreenSaverProc instead. It is the same as
  DefWindowProc with the exception of taking care of the other messages needed to keep the screen saver running!
  Anyway, the library header file is named scrnsave.h and is placed in the include directory of the compiler. It contains the necessary prototypes to support the screen saver library. There are some global variables defined in the library and the
  extern version is included in the above-mentioned header file. Two of those important variables have been declared as follows:
  extern HINSTANCE hMainInstance; extern BOOL fChildPreview;
  where
  hMainInstance is the instance handle of the screen saver, and
  fChildPreview states whether the screen saver is currently running in preview mode. Don't worry, we will examine each of these, shortly.
  We are going to create a screen saver, named, Ball Fusion, that contains 7 balls moving after each other on a sine wave path:
  x = sin(�) * cos(�) * cos(2�) y = cos(�)
  where
  � is a positive number between 0 and 360. You can change the formula, if you want.
  The Ball Fusion will not have any configuration dialog box, and this means if you try to choose the Settings button in the screen saver control panel, you will get nothing in response. This is just because of the lack of time. Actually, my boss started to shout at me when he saw the screen saver running on my system at office! So I stopped any improvements, and that's why it has got no Settings box!
  To start, launch MSVC++ 6.0. From the "File" menu, select the "New" item. With the "Projects" tab selected, highlight "Win32 Application", and in the "Project Name" edit box, type BallFusion, and press the Ok button. Now, choose "An empty project" radio button, and press "finish" and "ok" respectively. Now, it is time to add the BallFusion.cpp to our project to start coding. To do so, select the "New" item from the "File" menu. Having the "C++ Source File" item selected in the "Files" tab, enter BallFusion as the "File Name" and press OK. This way, we have got the base framework in place.
  Open the BallFusion.cpp to insert the following code:
  #include "windows.h" #include "scrnsave.h" LRESULT WINAPI ScreenSaverProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) { return 0; }
  The screen saver library also needs us to include two other functions in our screen saver,
  ScreenSaverConfigureDialog and
  RegisterDialogClasses. The former receives messages sent to a screen saver's configuration dialog box whilst the latter is used to register any window class required by the configuration box. Since our screen saver doesn't have any configuration dialog box, we could simply skip the implementation. However, we have still included them in our screen saver program:
  BOOL WINAPI ScreenSaverConfigureDialog(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam) { //We simply return FALSE to indicate that //we do not use the configuration dialog box return FALSE; } BOOL WINAPI RegisterDialogClasses(HANDLE hInst) { //Since we do not register any special window class //for the configuration dialog box, we must return TRUE return TRUE; }
  The next step is to add scrnsave.lib to the library modules we are going to use in our program. To do so, from the "Project" menu, select "Settings" and make the necessary changes shown below:
  Having completed the setting process of the compiler options, it is now time to focus on the actual program. I have defined some global variables within the program, as follows:
  #define MAX_BALLS 7 #define pi 3.141592625 #define szAppName "BallFusion 1.0" #define szAuthor "Written by Mehdi Mousavi, � 2001" #define szPreview "Ball Fusion 1.0" typedef struct _BALLS { UINT uBallID; HICON hIcon; int x; int y; int angle; }BALLS; BALLS gBalls[] = {IDI_REDBALL, NULL, 0, 0, 0, IDI_GREENBALL, NULL, 0, 0, 25, IDI_BLUEBALL, NULL, 0, 0, 50, IDI_PURPLEBALL, NULL, 0, 0, 75, IDI_LIGHTREDBALL, NULL, 0, 0, 100, IDI_YELLOWBALL, NULL, 0, 0, 125, IDI_BLACKBALL, NULL, 0, 0, 150};
  where
  MAX_BALLS is the number of the maximum balls,
  szAppName is the application's name that is shown at the bottom of the leftmost of the screen as well as the author's name (
  szAuthor),
  szPreview is the string that is shown to the user when she is previewing the screen saver from its control panel, and the
  BALLS structure is the structure to store a ball configuration. This configuration consists of the identifier of the ICON (
  uBallID), a handle to the icon itself after being loaded (
  hIcon), the
  x and
  y position of the ball, and the starting
  angle in which the ball starts moving. Finally, there's
  gBalls, an array of the above-mentioned structure.
  When the screen saver procedure receives the
  WM_CREATE message, it loads the ball icons according to the identifier of each ball already declared in the
  gBalls variable and stores this loaded icons under the handle already placed in the
  BALL structure (
  hIcon). However, please pay attention that to load the icons, the main instance handle of the application (
  hMainInstance) is given to the
  LoadImage function:
  for(i = 0; i < MAX_BALLS; i++) gBalls[i].hIcon = (HICON)LoadImage(hMainInstance, MAKEINTRESOURCE(gBalls[i].uBallID), IMAGE_ICON, 48, 48, LR_DEFAULTSIZE);
  Then the starting position (
  x and
  y member of the
  BALL structure) is calculated for each ball, according to the following formula:
  x = sin(�) * cos(�) * cos(2�) y = cos(�)
  In other words:
  xpos = GetSystemMetrics(SM_CXSCREEN) / 2; ypos = GetSystemMetrics(SM_CYSCREEN) / 2; for(i = 0; i < MAX_BALLS; i++) { double alpha = gBalls[i].angle * pi / 180; gBalls[i].x = xpos + int((xpos - 30) * sin(alpha) * cos(alpha) * cos(2 * alpha)); gBalls[i].y = ypos - 30 + int(265 * cos(alpha)); }
  And a black brush is created to be used while putting the icons on screen and a timer is started:
  hBrush = CreateSolidBrush(RGB(0, 0, 0)); uTimer = SetTimer(hWnd, 1, 1, NULL);
  When the timer is started, the screen saver procedure receives the
  WM_TIMER message. For this message, all we do is calculate the
  x and
  y position of each ball, while increasing the
  angle variable (0 <=
  angle <= 360).
  Then, the rectangular area of which the ball should be invalidated is calculated and placed in the
  rc variable.
  for(i = 0; i < MAX_BALLS; i++) { double alpha = gBalls[i].angle * pi / 180; gBalls[i].x = xpos + int((xpos - 30) * sin(alpha) * cos(alpha) * cos(2 * alpha)); gBalls[i].y = ypos - 30 + int(265 * cos(alpha)); gBalls[i].angle = (gBalls[i].angle >= 360) ? 0 : gBalls[i].angle + 1; rc.left = gBalls[i].x; rc.right = gBalls[i].x + 48; rc.top = gBalls[i].y; rc.bottom = gBalls[i].y + 48; InvalidateRect(hWnd, &rc, FALSE); }
  When the invalidate function is called, the program receives the
  WM_PAINT message. All we need to do upon the reception of this message is to put each ball on its position indicated by
  x and
  y:
  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 - 40, szAppName, strlen(szAppName)); TextOut(hDC, 0, ypos * 2 - 25, szAuthor, strlen(szAuthor)); for(i = 0; i < MAX_BALLS; i++) DrawIconEx(hDC, gBalls[i].x, gBalls[i].y, gBalls[i].hIcon, 48, 48, 0, (HBRUSH)hBrush, DI_IMAGE); }
  And what about
  fChildPreview? As we said earlier, this flag states whether the screen saver is currently running in preview mode. I.e., if the user tries to watch the screen saver within the screen saver's control panel,
  fChildPreview is
  TRUE. Otherwise, it is
  FALSE.
  The compiler generates a .EXE file for this project. However, you need to rename it to .SCR so that the screen saver control panel can load it within the Screen Saver combo-box (as shown above). Another important thing to remember, is to include an ICON identified by
  ID_APP in your application. This will be the screen saver's icon, since the library introduces this ID as the application's ID when registering the window class.
  cls.hIcon = LoadIcon(hInst, MAKEINTATOM(ID_APP));
  That's all folks - Aloha!
  I've got an idea that improves this saver, that is keeping the current desktop as the background of the saver and start the animating process on that. To understand what I am talking about, add the
  WM_ERASEBKGND message to your window procedure and compile the program again. Of course, this improvement will be possible if, and only if, I can convince my boss!
  Any comments, suggestions and/or questions are welcomed.
  General
  News
  Question
  Answer
  Joke
  Rant
  Admin