You must have seen many beautiful Screen
Savers developed in OpenGL. Good examples are '3D Flower Box', '3D Maze' and '3D
Text'. Now that we are familiar with OpenGL we too can develop such screen
savers.
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 following figure. Note
that our screen saver titled 'screensaver' also appears in the list.
p
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:
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' (refer
Figure 8-8).
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 the first figure 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 the value of time in the dialog
box, the change is immediately noticed in the preview window in the dialog box.
How It Works
The screen saver developed here displays a
colorful cube having a different color for each of its corners. Moreover, the
cube keeps rotating on the screen. This movement is a common feature for most of
the OpenGL screen savers. The movement 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:
- Full Screen mode: The Screen Saver runs in this
mode in 4 cases:
- 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.
- When the 'Preview' button in 'Start | Settings |
Control Panel | Display | Screen Saver' property page is clicked.
- When we right click on the screen saver file and
select 'Test' from the menu that appears.
- When we run the screen saver by double clicking the
screen saver from the 'Explorer' or from 'Start | Run'.
- Small Preview Window mode: The Screen Saver
runs in this mode in 2 cases:
- When 'Start | Settings | Control Panel | Display |
Screen Saver' property page is displayed.
- When we right click on '.scr' file and select
'Install'.
- Settings Dialog Preview mode: The Screen Saver
runs in this mode in 3 cases:
- When we run the screen saver from the 'Developer
Studio'.
- When we right click on the screen saver file and
select 'Configure' from the menu that appears as shown in Figure 8-10.
- When we click on the 'Settings' button from 'Start
| Settings | Control Panel | Display | Screen Saver' property page.

The first figure shows the screen saver
active in the 'Small Window Preview' mode. The following figure shows it active
in the 'Full Screen' mode.
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 cube is 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 speed of rotation of the
cube.
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:: dopreview( ).
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 is 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 this function is called the drawing
activity should take place in the preview window. The drawing activity would be
managed by the drawwnd class's 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 settingdialog::OnInitDialog( )
{
time = AfxGetApp( ) -> GetProfileInt ( "Config",
"Time", 0 ) ;
CStatic *s = ( CStatic * ) GetDlgItem (
IDC_PREVIEW ) ;
CRect r ;
s -> GetWindowRect ( &r )
;
ScreenToClient ( &r ) ;
m_preview.Create ( NULL, WS_VISIBLE |
WS_CHILD, r, this, NULL ) ;
return CDialog::OnInitDialog( ) ;
}
In this function, to begin with, the value
of time is 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 value of 0
(last parameter passed to GetProfileInt( )) would be
assumed for the time. The value read from the registry (or the assumed value if
there is no entry in the registry) is used to show the default selection for
speed 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 value of settingdialog::time is used to set up the default value
in the edit box. 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 cube should rotate
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 an entry in the dialog box its
result is immediately shown in the preview window. To ensure this a function settingdialog::editchange( ) is called by the framework
as soon as we type a new value in the edit box. This function picks up the new
value from the edit box using CWnd::GetDlgItemInt(
). This value is then used to reset the timer.
Finally, when the user dismisses the
dialog box by clicking OK the time entered by him is 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_time = AfxGetApp( ) ->
GetProfileInt ( "Config", "Time", 0 ) ;
}
Here the value of time is read from the
registry. 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 )
{
draw( ) ;
MSG m ;
while ( ::PeekMessage ( &m, m_hWnd,
WM_TIMER, WM_TIMER, PM_REMOVE ) ) ;
}
In this function we have called the drawwnd::draw( ) to actually carry out the drawing of
the cube.
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_POPUP 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_POPUP 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.