Quick Start
The Smoov Code Project Screen Saver moves text and images
around the screen. It runs with or without internet access,
but is mainly intended to display the latest articles, comments and
messages available from the Code Project web service.
Copy Code Project Smoov Saver.scr to your Windows folder
on your computer. Right click on the desktop, select
Properties and go to the Screen Saver tab. Select "Code Project Smoov Saver"
from the drop down list. It will show the preview in the little window.
You can select Settings... to see the configuration dialog which allows you to
turn off internet web service access, and to turn on web access
display (which signals web access in the upper left corner). You
can select Preview... to see the full screen saver. You can also
just invoke the screen saver directly from your File Explorer.
Thanks go to "The Code Project" and its tremendous
community of users. This is for you!
Introduction
This screen saver combines several areas of interest.
Firstly, there is the screensaver aspect. This article starts by
describing the steps I took to create the basic screen saver application which
handles certain Windows command line options. Secondly, there
is the animation aspect. This article introduces CAnimationItem
and CAnimationEngine
classes for facilitating the
development of text and bitmap animation effects.
Thirdly, there is the web service client aspect. This screen
saver was developed for the Code Project Screen Saver Competition
which revolved around displaying information from the latest articles,
messages and comments provided by the Code Project's web service.
However, other than a brief mention, this aspect is mostly saved for a later article.
Note: release 1.1 fixes proxy access, includes
multi-monitor option (in the source code), and fixes VC7 compiler
warnings. There is no password support.
Create Dialog-based App
I poked around first and found among other things an article in MSDN
entitled "Creating 32-Bit Screen Savers with Visual C++ and MFC"
by Nigel Thompson 12/04/1994 which tells how to do it. And
I played with an old screen saver I created in 1996. In 1996 I
believe I was concerned with creating one that worked on 16-bit
windows as well as 32-bit with the same source code. To do that I had
to link with the SCRNSAVE library and use straight Windows. Now I no
longer want to support 16-bit, and I prefer to use MFC.
I used the MFC AppWizard, selected "Dialog Based" for the
configuration dialog, and checked "Windows Sockets" for accessing
the Code Project information.
Try it as a .SCR File
First I wanted to make sure it would work as a screen saver. I
renamed this simple AppWizard executable to a .scr extension and
copied it to the Windows (Winnt) directory. Sure enough it came
up in the Desktop Screen Saver list and executed the dialog when selected. When it
dawns on you that a screen saver "plug-in" is simply an
EXE renamed to SCR, you'll say to yourself "I can do this!"
The problem was that the name that appeared in the list was
the filename (I am using NT 4.0). I then took a long detour for a few hours trying to
figure out how to change the name that appears in that drop-down
list. MSDN seems to say you put the name under string resource ID=1.
But two MSDN articles (Q137250 Q126239) both dated 09/25/1995 conflict on this
issue. Finally I accepted that the easiest way is to name the executable the
way you want the name to appear.
Having used up all my pizza and coffee, all I had to show was a
vanilla MFC-generated application. But actually I was feeling
like I had done a lot of my background research now and was
ready to make some progress.
I've noticed being among programmers that we generally share a lack of
awareness about where development time is wasted. The majority of
time is wasted in the "in-betweens", those integration issues that shouldn't
be so difficult, but are. Its okay as long as you expect these
problems and allow time in your schedule for them.
Creating a Screen Saver Window
I used Visual Studio to create a generic CWnd
class called CSaverWnd
,
and used the Nigel Thompson article as a starting point to implement the Screen
Saver Window Class functionality. I also used the website
called Windows Screen Saver Programming
by Lucian Wischik mentioned in the discussion area of the Code
Project article
Creating a screen saver by Mehdi Mousavi 11/09/2001 which is a great article
by the way. The different functionality of the screen saver exe is called upon
by the Windows Desktop Properties Screen Saver dialog using command
line parameters as follows:
none
Bring up a configuration dialog with no parent window.
It appears that later Windows operating systems automatically
supply a /s when you double click on the .scr file in Explorer,
and it runs the screen saver rather than the configuration dialog.
/c
Bring up a configuration dialog using the active window as the parent
/s
Run as regular screen saver. It should be a topmost
popup window occupying the whole screen that
closes on keyboard or mouse input.
/p <hwnd>
Run as preview screen saver inside specified window (preview pane).
It must be a child window without sensitivity to keyboard or mouse input,
and close when this child window is destroyed.
/a
Password option which I do not know anything about
Getting this going took about 2 hours. The hardest part
was finding instructions for how to do the preview because the
articles I was using neglected to document the command line /p.
The preview is when the screen saver renders inside the Windows Desktop
Properties dialog Screen Saver tab miniature screen. In /p mode,
the screen saver window is a child window of the command line
provided HWND
and the screen saver closes
on WM_DESTROY
. In /s mode, the screen saver window handles certain
messages like keyboard and mouse actions to close the screen saver.
The screen saver debugging problem is due to the screen saver running
with the WS_EX_TOPMOST
style. I did not realize
this for some time and was frustrated because it is not possible to use breakpoints
at certain parts of the code. I originally attributed this to the scrnsave.lib
and so I was surprised to encounter it again. Now I use #ifdef _DEBUG
to turn off this style during debugging. It is documented that
you cannot debug SCRNSAVE applications very well
in MSDN Q123871 "PRB: Screen Saver Applications Cannot Be Debugged Properly" 07/25/1997.
Windows Timer-Based Animation
The main thing about creating Windows animations is to use the
timer (SetTimer
) with somewhere around 20 or 50 milliseconds
between WM_TIMER
messages. This interval is like the frames in a film.
A film has somewhere around 15 to 30 frames per second, and the
20 milliseconds used in this screen saver is up to 50 frames per second which
is probably not necessary.
This timer mechanism allows the message pump to be free to process
other windows messages while your animations are moving around.
In fact, it also allows frames to be gracefully "dropped" so that if the
animation pauses due to an unrelated CPU surge it won't appear to "speed up"'
afterwards like it is trying to catch up. Many games take over the computer to
avoid the occasional jerkiness that is natural in a multitasking operating system.
The best way you can make your animations smooth in a Windows program is
to keep down the CPU requirements of your own animations, so that other
applications can easily fit their operations between
your OnTimer
handling.
The alternative to a timer is a background thread
(or threads) posting notification messages when its time to redraw something.
Like timer-based animation, this frees up your GUI thread's message pump,
but it introduces tricky synchronization issues.
Animation Item and Engine
Each animation item that is on the screen is generally
represented by a structure or class that maintains its
status. I use a CAnimationItem
class to hold item information,
and the items are stored in a linked list CList
class. The CAnimationEngine
class renders a frame in
the RunFrame
method by looping once through all of the existing items,
updating their status, and drawing them. This creates one frame of
the movie. In the next WM_TIMER
handler it is called again to draw the next frame.
These two animation classes are designed together to make the job of
creating animations quick and intuitive. A window class that contains animations,
declares a CAnimationEngine
member and all of the animations
can be created from there. Here is an overview of the animation engine calls to
be made if your engine object is m_ae
.
m_ae.SetScreen( rectClient );
CClientDC dc(this);
int nItems = m_ae.RunFrame( dc );
if ( nItems == 0 )
m_ae.CreateItem( "hello" ).SetPoint(0,40).SetFrames(50).FadeOut();
m_ae.DestroyGDIs();
That's all you really have to do! This just shows the word hello
located at 0,40 and repeatedly fading out every 50 frames
(about 1 second if the timer interval is 20 milliseconds). Of course you can
create a bunch of items with all kinds of different settings. One of
the things to notice in the example is that RunFrame
returns a
number of items remaining. You generally want to create a fresh batch of
new items whenever the previous batch runs out, like separate scenes in
the movie. When an item runs out of frames,
the engine deletes it automatically.
Animation Item Settings
Now let's look at some more examples of the settings you
can use with the items. These settings use the return *this
reference trick to make the code more easy and readable by
allowing you to stack the desired settings methods.
POSITION pos = m_ae.CreateItem( "The Code Project" )
.SetFont( "36,Arial,B" )
.AlignCenter()
.SetStart( ptIntro.x, ptIntro.y - 70 )
.SetFinishRel( 0, 20 )
.SetFrames( 70 )
.GetPos();
Here it is setting the font and alignment of the text,
a starting and a relative finishing point. The font string is an
intuitive way of specifying basic font characteristics comma-delimited. The engine will
move the item from the start to the finish point over the
course of 70 frames. This above example also uses GetPos
as the last method to store the position and create
a continuation item after it:
m_ae.CreateItemAfter( pos )
.SetFrames( 20 )
.FadeOut();
This creates another item with the same text, font and alignment,
that will fade out in a matter of 20 frames.
The appearance will be seamless as if one item was there for some time before
fading out. You can also use positions to create new items
that GoAfter
or GoWith
rather than starting with
a copy of the previous item.
Use a resource ID to create a simple bitmap item.
Thanks go to J�rg K�nig's DIB256 CDIBitmap
class which isolates the complexities
of 256 vs. big color monitor settings.
m_ae.CreateItem( "" ).SetBitmap( IDB_BOB1 );
To use a bitmap like an image list (as in the case of the emoticons),
create a bitmap containing a row of square individual pictures
(emoticons are 20X20, so 540X20 for 27 of them). This is because the
height of the bitmap is currently used as the width of the individual pictures,
but this could be easily changed. I started to implement AVI-like items
by storing a first picture offset and last picture offset. Going the extra
step to rotate through these pictures every few frames is a simple exercise I
have left for the reader if you are inclined (or you can just create separate items instead).
POSITION posEmot = m_ae.CreateItem( "" )
.GoAfter( pos )
.SetBitmap( IDB_EMOTICONS, m_ae.Rand(27) )
.SetStart( m_ae.RandPt() )
.SetFinish( m_ae.RandPt() )
.SetFrames( 150 )
.GetPos();
Notice that the engine's random number methods are used to choose
one of the 27 emoticons, and start and finish points on the screen.
How This Animation Works and Its Limitations
The animation implemented here uses a simple principle: to draw a
single frame, each item is erased if it moves and is redrawn in its
new position. The time between one frame and the next is what
allows the scene to look solid rather than flashy. To erase a text item,
the text is drawn over its old position in the background color. To erase
a bitmap and reduce flashing, only the area to the side and top
or bottom that is being vacated is filled with the background color.
One problem is that depending on the order of items in the list, they
will have small conflicts with each other such as erasing part of
an item that was already drawn. But to see
this you have to slow it down to frame by frame.
This type of animation can only really work on a
plain single colored background, unless you want smearing designs. In order
to use a background image, one would start each frame with the original
background image in memory, laying the items on in their new positions,
and then only the affected regions are drawn directly onto the screen.
Accessing the Web Service
The CHttpThread
class is used to go out and
asynchronously access the Code Project web service in the background.
It posts a WM_APP
message when the information has been successfully
received. The screen saver window copies the resulting information before
submitting another request.
Simply put, being asynchronous allows you
to exit the application at any time instantly without waiting for any current internet
access to complete. HTTP GET retrieves the information just
as if you click on the following link, try it:
http://www.codeproject.com/webservices/latest.asmx/GetLatestArticleBrief?NumArticles=1
The result is a text document that happens to be XML. This XML is
navigated using CMarkup
, a small footprint XML class
presented in my other article
markupclass.asp.
The Code Project Web Service currently has 11 operations described and
demonstrated at
latest.asmx in a very simple and helpful way. Even if you don't intend
to program with it, I recommend that you check it out.
Downloading and Refreshing
All these web service operations are queued up and performed
one after another when the screen saver starts up.
See the CallWebService
and ProcessWebInfo
methods.
The operations are numbered 0 to 10 and are fully described in the source code.
Four of these operations provide the interval in minutes for how often to
download the summaries. Although it is a simple concept, implementing this
can be a little tricky. First of all I keep times as integer seconds since
the screen saver was started. For each type of interval-based web
service access I store the time of the most recent request sent to the
web service. In OnTimer
whenever there are no web service requests
pending, I compare the current time (in seconds since start of screen saver)
to the stored time of each type of access. When it is time to
submit another request, I update the stored time of most recent
request to the current time.
Make Your Own
Feel free to develop your own screen savers and animations using this project
source code. Let me know if you create some cool designs and enhancements.
Enjoy!
Ben Bryant, bcbryant@firstobject.com, www.firstobject.com