Creating a Simple Screen Saver



Anybody who has dabbled with Windows even for a few hours has knowledge about the existence of a program called Screen Saver. A Screen Saver can be installed by right clicking on the screen saver’s executable file and selecting the ‘Install’ option from the menu that appears. As soon as we do this, our screen saver file is entered in the list of screen savers that the Windows OS maintains. This list is displayed in a combo box in a dialog along with its preview. This dialog box, the combo box and the preview window are shown in the Figure 1. Note that our screen saver ‘mysaver’ also appears in the list.


 

                         The screen savers are different than other executables in their extension. All screen savers have an extension of ‘.scr’ rather than the traditional ‘.exe’. To ensure that it gets this extension, follow these steps:

  1. Select ‘Project | Settings | Debug’ property page from the ‘Developer Studio’. Change the extension from ‘.exe’ file to ‘.scr’ in the edit box with a title ‘Executable for debug session’.
  2. Select ‘Project | Settings | Link’ property page from the ‘Developer Studio’. Change the extension from ‘.exe’ file to ‘.scr’ in the edit box that has a title ‘Output file name’. Press OK.

    If we click on the ‘Preview’ button in the dialog box in Figure 1 the screen saver would be put into action. If we click on another button called ‘Settings’ one more dialog box would appear which would help the user to control the various elements of the screen saver. Naturally, the appearance and contents of this dialog box would vary from one screen saver to another. The dialog box for our screen saver is shown in the following figure.

                                    As soon as we change any of the settings in the dialog box the change is immediately noticed in the preview window in the dialog box.

Working

The screen saver developed here randomly displays colorful squares/circles on the screen. It uses an array of structures s[ ] to display the squares/circles. We have defined this array to hold 10 elements. The information stored in this array controls the size, position and color of each square/circle. At any point of time, a total of 10 squares/circles are displayed. When the 11th square/circle is drawn the 1st one is erased. This continues till either some key is depressed or the mouse is moved or any button on the mouse is clicked.

The screen saver program runs in three different modes:

  1. Full Screen mode: The Screen Saver runs in this mode in 4 cases:
  1. When the PC is idle for a time greater than one that has been set up through the spin button control in the ‘Start | Settings | Control Panel | Display | Screen Saver’ property page.
  2. When the ‘Preview’ button in ‘Start | Settings | Control Panel | Display | Screen Saver’ property page is clicked.
  3. When we right click on the screen saver file and select ‘Test’ from the menu that appears.
  4. When we run the screen saver by double clicking the screen saver from the ‘Explorer’ or from ‘Start | Run’.
  1. Small Preview Window mode: The Screen Saver runs in this mode in 2 cases:
  1. When ‘Start | Settings | Control Panel | Display | Screen Saver’ property page is displayed.
  2. When we right click on ‘.scr’ file and select ‘Install’.
  1. Settings Dialog Preview mode: The Screen Saver runs in this mode in 3 cases:
  1. When we run the screen saver from the ‘Developer Studio’.
  2. When we right click on the screen saver file and select ‘Configure’ from the menu that appears as shown in the Figure 4.
  1. When we click on the ‘Settings’ button from ‘Start | Settings | Control Panel | Display | Screen Saver’ property page.

     Figure 4

Figure 1 shows the screen saver active in the ‘Small Window Preview’ mode, whereas Figure 3 shows it active in the ‘Settings Dialog Preview’ mode. The following figure shows it active in the ‘Full Screen’ mode.

                              Figure 5

Depending upon which of the above three modes is used to invoke the screen saver it is passed an argument ‘s’, ‘p’ and ‘c’ respectively. However there are two exceptions here. In cases 3(a) and 3(b) above no argument is passed to the program. However, in these two cases too we want the screen saver to run in ‘Settings Dialog preview mode’. It is necessary to identify which of these three cases has occurred, because the size of the window where the random squares/circles are to appear is different in the three cases.

When the ‘Settings’ dialog box is popped up, we have made a provision to let the user select the shape of the objects (rectangle or circle) that are going to get displayed on the screen when the screen saver goes into action. Additionally, the user can also select the fill style for the rectangles/circles. We have provided three styles; solid, hatch and pattern to choose from. For permitting the user to select the shape and the style we have provided two combo boxes in the ‘Settings’ dialog box.

Irrespective of the mode in which our Screen Saver is running, the control would ultimately land in the myapp::InitInstance( ) function. Here it is first determined which command line argument has been passed (if at all) to our program by calling the myapp::checkoption( ) function. It then appropriately calls the function myapp::doconfig( ), myapp::dofullscreen( ), or myapp:: doperview( ).

The myapp::doconfig( ) Function

If it is determined that the settings dialog box should be popped up, an object of the settings dialog class is created through the statement,

settingdialog d ;

This calls the constructor of the settings dialog. Here the settings dialog is created in memory. This dialog is then displayed by calling CDialog::DoModal( ).

The myapp::dopreview( ) Function

The code of this function given below:

void myapp::dopreview( )
{

CWnd* parent = CWnd::FromHandle ( ( HWND ) atol ( __argv[2] ) ) ;
CRect r ;
parent -> GetClientRect ( &r ) ;
drawwnd* p = new drawwnd ( TRUE ) ;
p -> create ( NULL, WS_VISIBLE | WS_CHILD, r, parent, NULL ) ;
m_pMainWnd = p ;

}

When the function is called the drawing activity should take place in the preview window. The drawing activity would be managed by the drawwnd class object. When this object is created the window associated with it should be the child of the preview window present in the property page. Hence the address of this parent window needs to be determined. This has been achieved by calling the function CWnd::FromHandle( ). The argument passed to this function is the handle to the preview window of the property page. This handle is passed to our program as a command line argument (argv[2]). Once the pointer to the parent window has been obtained the drawwnd::create( ) function is called to create the window.

The myapp::dofullscreen( ) Function

Lastly, if it is determined that the actual screen saver should be put into action then a full screen window is created. The code of dofullscreen( ) is shown below:

void myapp::dofullscreen( )
{

saverwindow* p = new saverwindow ( TRUE ) ;
p -> create( ) ;
m_pMainWnd = p ;

}

The Settings Dialog

When the DoModal( ) function is called to display the dialog box control first reaches settingdialog::OnInitDialog( ). The code of this function is given below.

BOOL mydialog::OnInitDialog( )
{

m_shape = AfxGetApp( ) -> GetProfileInt ( "Config", "Shape", 0 ) ;
m_fillstyle = AfxGetApp( ) -> GetProfileInt ( "Config", "FillStyle", 0 ) ;
CDialog::OnInitDialog( ) ;
CRect r ;
CStatic *s = ( CStatic * ) GetDlgItem ( IDC_PREVIEW ) ;
s -> GetWindowRect ( &r ) ;
ScreenToClient ( &r ) ;
m_preview.create ( NULL, WS_VISIBLE | WS_CHILD, r, this, NULL ) ;
CenterWindow( ) ;
return TRUE ;

}

In this function, to begin with, the values of shape and fill style are read from the registry by calling the CWinApp::GetProfileInt( ) function. When you run the program for the first time there won’t be any entry in the registry. Hence default values of 0 and 0 (last parameter passed to GetProfileInt( )) would be assumed for the shape and fill style. The values read from the registry (or the assumed values if there are no entries in the registry) are used to show the default selections for shape and fill style when the dialog is popped up.

Next, the base class implementation of OnInitDialog( ) is called, which in turn calls settingdialog::DoDataExchange( ). In this function the values of m_shape and m_fillstyle are used to set up the default selections of the two combo boxes. Next, in the OnInitDialog( ) function the size of the preview window is obtained and a preview window is created. Note that the object m_preview (object of drawwnd class) which is a private data member is used to create the preview window. As the window gets created, the drawwnd::OnCreate( ) handler gets called. Here, to set the speed at which the squares/circles should be displayed in the window, the CWnd::SetTimer( ) function is called. This function simply sets a timer. Later on we would see how this timer gets serviced.

As we make selections in the dialog box its results are immediately shown in the preview window. To ensure this a function settingdialog::newshapestyle( ) is called as soon as the selections from the combo boxes are changed. This function updates the drawing tools by calling the drawwnd::setdrawingtool( ) function.

Finally, when the user dismisses the dialog box by clicking OK the selections made by him are written to the registry by calling CWinApp::WriteProfileInt( ) function. The logic of drawing in the preview window has been managed in the drawwnd class.

Drawing in The Window

To manage drawing in the small preview window or the full screen window we have developed a class called drawwnd. Whenever an object of this class is created its zero argument constructor gets called. This constructor function is shown below:

drawwnd::drawwnd ( BOOL deleteflag )
{

m_deleteflag = deleteflag ;
m_shape = AfxGetApp( ) -> GetProfileInt ( "Config", "Shape", 0 ) ;
m_fillstyle = AfxGetApp( ) -> GetProfileInt ( "Config", "FillStyle", 0 ) ;

for ( int i = 0 ; i < 10 ; i++ )
s[i].x = s[i].y = -1 ;

setdrawingtool ( m_shape, m_fillstyle ) ;
currentnum = 0 ;

}

Here, to begin with, the values of shape and fill style are read from the registry. Next, the array s[ ] is initialised such that the x, y coordinates of all 10 squares/circles are set to -1. These values are later on used to keep track of how many square/circles have been drawn so far. Lastly, the values read from the registry are used to create 10 brushes by calling the drawwnd::setdrawingtool( ) function.

Whenever the full screen window or the small preview window is created, control reaches drawwnd::OnCreate( ). Here we have set up a timer by calling CWnd::SetTimer( ).

When the time interval set in CWnd::SetTimer( ) is over the function drawwnd::OnTimer( ) gets called. The code of this function is shown below:

void drawwnd::OnTimer ( UINT id )
{

CClientDC *dc ;
dc = new CClientDC ( this ) ;
CRect rect ;
GetClientRect ( &rect ) ;
draw ( dc, rect ) ;
delete dc ;

}

In this function we have obtained the device context, determined the client area size and then called drawwnd::draw( ) to actually carry out the drawing.

Full Screen Mode

When the screen saver runs in the full screen mode the myapp::dofullscreen( ) function gets called. In this function we have created an object of saverwindow class which has been derived from the drawwnd class. On creating this object the constructor of saverwindow gets called. In the constructor we have set up the saverwindow::lastpoint variable with a value (-1,-1). This value is later on used in the saverwindow::OnMouseMove( ) function, as we would soon see. Once the object has been created, using it, saverwindow::create( ) function is called. This function in turn calls drawwnd::create( ) with WS_EX_TOPMOST as the first argument and WS_POPOUP as one of the values in the second argument. The first argument ensures that the full screen window created becomes the topmost window. The value WS_POP ensures that the window created doesn’t have a caption bar and the border.

Once the window is created the drawnd::OnCreate( ) gets called which once again sets up a timer in response to which the OnTimer( ) function calls drawwnd::draw( ) to do the drawing in the full screen window.

Disabling The Screen Saver

When the screen saver runs in the full screen mode it should get disabled whenever the user presses any key or performs any mouse operation. It means in the saverwindow class we must handle all keyboard and mouse related messages. Hence we have written the handlers OnKeyDown( ), OnSysKeyDown( ), OnLButtonDown( ), etc. in the saverwindow class. In each of these handlers all that we have done is, sent a WM_CLOSE message to the message queue and then called the base class implementation of the handler. When the WM_CLOSE message would get picked up from the message queue, the application would close itself. The only exception is the OnMouseMove( ) handler. The code of this handler is as shown below:

void saverwindow::OnMouseMove ( UINT flags, CPoint pt )
{

if ( lastpoint == CPoint ( -1, -1 ) )
lastpoint = pt ;

else if ( lastpoint != pt )
PostMessage ( WM_CLOSE ) ;

drawwnd::OnMouseMove ( flags, pt ) ;

}

 

If you remember, in the constructor of the saverwindow class we have set up the variable saverwindow::lastpoint to a value (-1, -1). In the OnMouseMove( ) handler we have checked whether the coordinates of lastpoint are still (-1, -1). If they are then we do not post the WM_CLOSE message into the message queue. It would be necessary to do so in the following situation. Suppose a WM_MOUSEMOVE message is present in the message queue and the screen saver gets active before the WM_MOUSEMOVE message could get processed. In such an event, as soon as the screen saver becomes active it would get deactivated when the pending WM_MOUSEMOVE message gets processed. Our code given above prevents this by not posting the WM_CLOSE into the message queue in this situation. We also update the value of lastpoint such that next time a WM_MOUSEMOVE message arrives the screen saver is shut down by posting a WM_CLOSE into the message queue.

Destroying The Window

Once the screen saver becomes active we can deactivate it in a number of ways, as discussed above. Even though it has been deactivated from the screen it continues to run in memory. If once again a considerable time elapses without you hitting a key or clicking a mouse yet again it would become active. If it gets activated again then it would yet again reach the myapp::dofullscreen( ) function to create a window. Here the code,

p = new saverwindow;

would get executed again.

But what has happened to the object that p was pointing last time around. It is still surviving, and now if p starts pointing to another object then a memory leak would occur since we would have no way to access the earlier object. Note that the earlier object would not get destroyed through the destructor of myapp. This is because the destructor doesn’t get called since the application is still running and the myapp a object has not gone out of scope.

To avoid this situation we have used a drawwnd::deleteflag variable. While creating the preview window of ‘Start | Control Panel | Display | Screen Saver’, or the full screen window we have passed a value TRUE to the drawwnd constructor which in turn sets it up in drawwnd::m_deleteflag. As against this, in case of the preview window of the settings dialog we have passed a value FALSE to the drawwnd constructor. This is because on dismissing the dialog box the application would come to an end resulting in call to the myapp class’s destructor function. This would destroy the object associated with the preview window using its address which is stored in myapp::m_pMainWnd.

In case of the preview window of ‘Start | Control Panel | Display | Screen Saver’ and the full screen window the window object would be destroyed in the drawwnd::PostNcDestroy( ) function using the m_deleteflag variable.

Download