Useful screensavers
Written by Harry Fairhead   
Thursday, 17 September 2009
Article Index
Useful screensavers
Display
Bouncing text
Look and behavior

More like a screensaver

So far we have the basic functionality of a screensaver but it doesn’t look or behave like one. To make it look like one we have to resize the form to fill the screen and remove its border in the ShowScreenSaver function:

this.FormBorderStyle =
FormBorderStyle.None;
this.TopLevel = true;
this.WindowState =
FormWindowState.Maximized;
this.Capture = true;
Cursor.Hide();

To make the screensaver close when the mouse moves or if its button is clicked we need a simple event handler:

private void OnMouseEvent(
object sender,
System.Windows.Forms.MouseEventArgs e)
{
if (!MousePos.IsEmpty)
{
if (MousePos != new Point(e.X, e.Y))
this.Close();
if (e.Clicks > 0)
this.Close();
}
MousePos = new Point(e.X, e.Y);
}

and another global variable to store the mouse position:

private Point MousePos;

This one-event handler can deal with MouseMove and MouseDown events. View the form in design mode and use the properties window to set both events to the OnMouseEvent function.

Now when you run the program it should fill the screen and close when the mouse moves or is clicked. You can improve the way that it works by adding code to close only if the mouse moves more than a set amount and you can trap keyboard events to close it.

Preview

The screensaver can also be run in /p – preview or /c – configuration mode. The configuration mode is fairly easy to implement and it’s described in the documentation. The /p mode is much more difficult and not described anywhere so this is the puzzle to solve next!

preview

The screensaver in miniature

When the /p option is passed via the command line the windows handle (hwnd) of the preview window is passed as a sequence of decimal digits on the command line.

For example, if the command line is /p 8324238 then the handle to the preview window is 8324238. You could simply retrieve the hwnd and use it to construct a graphics object to draw on the preview window.

This works but there is a fundamental problem with doing things in this way. The system expects the screensaver to attach a child window to the preview window and to draw on the child, not the preview.

When the system needs to stop the screensaver, for any reason, it simply closes the child window. If you draw directly on the preview window then there is no child window to close and your screensaver will not be terminated when the user looks at another screensaver or views a full screen preview. The result it that you end up with lots of copies of your screensaver running at the same time and all trying to draw to the preview window – the system then crashes.

There seems to be no easy way of making a window a child of another window using the .NET framework and once again we have to resort to using the Windows API.

First we need to change the switch to deal with the hwnd passed on the command line case "/p":

//preview
if (m_args.Length > 2)
{
this.TopLevel = false;
m_hwnd = (IntPtr)Int32.Parse(m_args[2]);
ShowScreenSaver();
}
break;

and we need to add the global variable:

private IntPtr m_hwnd;

If m_hwnd has a value then we need to display using the preview screen and the ShowScreenSaver function has to be modified to initialise the form either for full screen or preview display:

void ShowScreenSaver()
{
if (this.m_hwnd == IntPtr.Zero)
{
All of the code
already in ShowScreenSaver
}

The else part of the if handles the preview and uses a number of API calls, definitions of which are given later. We need to make the form’s window a child window of the preview window. To do this we need to retrieve and modify the form’s window style to make it a child window:

else
{
int style = GetWindowLong(
this.Handle, GWL_STYLE);
style = style | WS_CHILD;
SetWindowLong(this.Handle,
GWL_STYLE, style);

Next we tell the system that the preview window is the parent of the form’s window and tell the form’s window that its parent is the preview window:

SetParent(this.Handle,m_hwnd);
SetWindowLong(this.Handle,
GWL_HWNDPARENT,(int) m_hwnd);

Now everything is set up for the form to display within the preview window except for the fact that it’s the wrong size and still has a border. To resize it we need to know the size of the preview window:

this.FormBorderStyle =
FormBorderStyle.None;
RECT r=new RECT();
GetClientRect(m_hwnd, out r);

To display the window a final API call is used do the resizing and place the form’s window in front of the preview window:

 SetWindowPos(this.Handle,
HWND_TOP, 0, 0,
r.Right, r.Bottom,
SWP_NOZORDER |
SWP_NOACTIVATE | SWP_SHOWWINDOW);
}

For all of this to work we need to add:

using System.Runtime.InteropServices;

at the start of the file and add the following declarations:

public struct RECT
{
public int Left;
public int Top;
public int Right;
public int Bottom;
}
[DllImport("user32.dll")]
static extern bool GetClientRect(
IntPtr hWnd,
out RECT lpRect);
[DllImport("user32.dll",
SetLastError = true)]
static extern int GetWindowLong(
IntPtr hWnd,
int nIndex);
const int GWL_STYLE = (-16);
const int WS_CHILD = 0x40000000;
[DllImport("user32.dll")]
static extern int SetWindowLong(
IntPtr hWnd,
int nIndex,
int dwNewLong);

[DllImport("user32.dll")]
static extern IntPtr SetParent(
IntPtr hWndChild,
IntPtr hWndNewParent);
const int GWL_HWNDPARENT = (-8);
[DllImport("user32.dll")]
static extern bool SetWindowPos(
IntPtr hWnd,
IntPtr hWndInsertAfter,
int X,int Y,
int cx, int cy,
uint uFlags);
static readonly IntPtr HWND_TOP
= new IntPtr(0);

const UInt32 SWP_NOZORDER = 0x0004;
const UInt32 SWP_NOACTIVATE = 0x0010;
const UInt32 SWP_SHOWWINDOW = 0x0040;

To see the preview in action you have to install the screensaver. Notice that you don’t have to display the same graphics in the tiny preview window that you do full screen. You could, for example, show a few lines of explanation of the purpose of your screensaver.

Screensavers still have a lot of potential left in them as a way of delivery information and as a way of using otherwise idle processor time.

If you would like the code for this project then register and click on CodeBin.

<ASIN:1890774464>

<ASIN:047019135X>

<ASIN:0596514824>

<ASIN:1590599551>

The screensaver is no longer needed as a way of protecting displays from damage due to “burn in” but it is a very useful way of automatically running a program. If the user hasn’t pressed a key or moved the mouse for a set time then the configured screensaver kicks in. This provides us with a way of running a program when the machine is idle and a way of presenting the user with some information on their return. Notice that it is usual for a screensaver to unload itself when the user presses a key or moves the mouse – but this isn’t mandatory. What the screensaver does after it has been loaded is entirely up to you and when you realise that a screensaver will run even before you have logged on to the machine and you can see that the scope for clever stuff is even greater. So perhaps screensavers aren’t a complete waste of time – in fact they might well be just the opposite, i.e. a way of using otherwise waste computing time…

So how do you create a screensaver application? It turns out to be surprisingly easy but if you look it up in the documentation you might not think so. There have been various attempts over the years to create a screensaver “framework” which have generally only made something very simple seem much more difficult. If you have C# Express (or VB Express) you will discover that there is a “Screen Saver Starter Kit” project type. This creates a screensaver for you without any effort and is worth studying but, like many Microsoft supplied examples, it’s too sophisticated and does slightly too much to see the basic principles. It also shows you how to create a “traditional” screensaver rather than giving you the information you need to do something creative and it does not explain how to implement a screensaver preview. This month’s project aims to correct these omissions.

Getting started saving screens

It’s always helpful to see what the simplest example of a project type so that you can really see how things work. Although screensavers seem complicated they are just standard .EXE files but with their file names changed to end in .SCR. They also live in either the Windows/System or Windows/System32 directory. When you use the desktop properties dialog box to choose a screensaver all that happens is that Windows scans the relevant directories for all files ending in .SCR and lists them as potential screensavers.

***PROP.TIF

Select your screensaver

If you select a screensaver when the machine has been idle for the specified amount of time it is loaded and run as a standard EXE file would be. Well there is a little more to it than this but this is a good starting point for a program. The only additional thing we need to take into account when writing a first screensaver is that the screensaver loader passes the screensaver a command line parameter to tell it what to do.

Parameter Meaning

/s Start in screensaver mode.

/c Show configuration dialog box with whatever window is currently active as the parent window.

/p Show a preview of your screensaver

(None) Show the configuration dialog box with no parent window.

The good news is that you really only need to support the /s option to create the simplest possible screensaver.

Start a new Console project called ScreenSaver1. This provides you with the initial code you need, i.e. a main function that is run when the application starts. The main function receives the command line parameters as a single string, which is called “args” for arguments by default:

static void Main(string[] args)

{

If there are any command line parameters the args string will have a length greater than zero:

if (args.Length > 0)

{

Given the simple nature of the possible parameters we can convert them into a standard form of two characters in lower case:

string arg = args[0].ToLower().

Trim().Substring(0, 2);

The result can now be used to decide what to do. You could use nested if statements but a switch is so much neater and easier to follow:

switch (arg)

{

case "/c":

//configure

break;

case "/s":

ShowScreenSaver();

break;

case "/p":

//preview

break;

}

}

else

{

ShowScreenSaver();

}

}

The final else clause belongs to the initial if statement and is executed if there are no command line parameters when we simply show the screensaver.

At some point in the future we are going to have to supply functions to deal with all three possibilities but for the moment all we need is the ShowScreenSaver function. In principle this could do anything you want it to but the normal action of a screensaver is to load graphics to fill the screen. The simplest way of doing this in many languages is to load a form and customise it to fill the screen. For this simple example we just load a form and leave the customisation to later. Add a form called screen to the project, there is no need to do anything to it and add, just after the main function just defined:

static void ShowScreenSaver()

{

Application.Run(new screen());

}

This uses the Application object to run, on a new thread, a new instance of the form. This not only loads the form but start its “message pump” running which makes it responsive. After this the thread running the main function can terminate and the screen form will remain active until the user closes it. Finally to make this work we need to add:

using System.Windows.Forms;

to the start of the file continuing the main function.

Now we are already to try the screensaver but there is one small complication. At the moment the project is designated as a console project and as such it displays a command window and refuses to display Windows forms. In fact we really want a Windows project and have created a console project just to get the system to generate the main function and other related code. To make the project work all that is necessary is to use the Project,Properties menu item and in the dialog box that opens change the Output type from Console Application to Windows Application and set the Startup object to be the ScreenSaver1.Program to ensure that it is the main function that starts running first. Now we can run the program to check that the screen form does appear. Next we need to install the screensaver. To do this you have to navigate to the project’s debug directory, find ScreenSaver1.exe and rename it to ScreenSaver1.scr. Next you can either copy it to the Windows/System32 directory, right click on the desktop, select the Screen Saver tab and finally select ScreenSaver1 in the drop down list. Copying the file to the System32 directory makes the installation permanent in the sense that it is available in the Screen Saver tab dropdown list.

While you are just testing the screensaver a quicker way is to right click on the ScreenSaver1.scr file and select Install from the drop-down menu. This starts the Windows Screen Saver Dialog Box which you can use to select your screensaver. This isn’t a permanent installing in the sense that if you select another screensaver at a later time your screensaver isn’t re-presented in the list of possible choices. You can click the preview button to see what the screensaver actually does. If you want to check that it really does start after a period of inactivity change the “Wait” time to 1 minute. It is worth noticing that all that happens when your “screensaver” starts is that the screen form appears. Nothing else happens to the form unless you close it, using the close box. You can see that the screensaver mechanism is a good way of getting almost any program started during a period of inactivity and it doesn’t have to stop running the instant the user returns.

A form based screensaver

The method described so far is the traditional approach that you will find in the documentation and examples. Sometimes it’s a good way of implementing a screensaver but if you are planning to use a form to create the display then there is a better way to build an application. Start a new Windows form project called ScreenSaver2. The problem with using a Windows form project is that it is usually thought that there is no easy way to get the command line parameters to the form. In fact it’s fairly easy. If you open Program.cs you will see the form application’s main function and you can change this so that it receives any command line parameters:

static void Main(string[] args)

{

The form is created using the command:

Application.Run(new Form1());

The problem is we can’t pass the form’s constructor the command line parameters in args. The solution is to simply define a new constructor that does accept the args array. Change the line that runs Form1 to:

Application.Run(new Form1(args));

Now we have to write the new constructor. Add to Form1.cs, following the default constructor Form1():

public Form1(string[] args)

{

InitializeComponent();

m_args = args;

this.TopLevel = false;

}

and add the global variable:

private string[] m_args;

Now any method in Form1 can access m_args to discover what the command line parameters are. The TopLevel property is set to false to make the form invisible. Notice you can’t make the form loaded on startup invisible by using Hide or setting Visible=false because the Run method makes the form visible after the Load event handler is finished. With these changes all of the command line argument processing can be performed in the Form Load event handler:

private void Form1_Load(

object sender, EventArgs e)

{

if (m_args.Length > 0)

{

string arg = m_args[0].ToLower().

Trim().Substring(0, 2);

switch (arg)

{

case "/c":

//configure

break;

case "/s":

ShowScreenSaver();

break;

case "/p":

//preview

break;

}

}

else

{

ShowScreenSaver();

}

}

And the ShowScreenSaver function is just:

void ShowScreenSaver()

{

this.TopLevel = true;

}

Bouncing text

We really need some graphics to show the sort of thing a screensaver can do. As an example we are going to bounce a message round the form. The simplest way of creating animation is to use a timer – so place a timer control on the form and change the ShowScreenSaver to:

void ShowScreenSaver()

{

this.TopLevel = true;

this.DoubleBuffered = true;

this.timer1.Interval = 20;

this.timer1.Enabled = true;

}

This sets the timer to 20 milliseconds which produces an update every 1/50th of a second. Setting DoubleBuffered to true makes the animation smooth. The Timer event handler does all the work. First it gets the graphics object associated with the form so that it can use it to draw:

private void timer1_Tick(

object sender, EventArgs e)

{

Graphics g = this.CreateGraphics();

As will become clear, you can’t assume that the screensaver is going to have a fixed sized form to draw on and everything should be done in terms of a fraction of the available space. To do this we first need the size of the display area:

RectangleF bounds =this.ClientRectangle;

Next we can set the message to display and a font size that is 1/20th of the height of the display area:

string displayM =

"Buy Computer Shopper";

int fontsize =(int) bounds.Height / 20;

We need to set a font to use and any font from the Arial family will do:

FontFamily fontFamily =

new FontFamily("Arial");

Font font = new Font(

fontFamily,

fontsize,

FontStyle.Regular,

GraphicsUnit.Pixel);

To blank the message out at its current position we can use the TextRenderer object and its MeasureText method. This is new in .NET 2.0 and returns a size struct giving the height and width of the rectangle that the text fits into:

Size psize=new Size(int.MaxValue,

int.MaxValue);

Size size = TextRenderer.

MeasureText(g, displayM,

font, psize,

TextFormatFlags.SingleLine);

Now that we have the size of the text the FillRectangle method can be used to draw a rectangle of just the correct size in the current form’s background colour:

SolidBrush b = new SolidBrush(this.BackColor);

g.FillRectangle(b, textpos.X,

textpos.Y,

size.Width,

size.Height);

To move the text its position is updated and if it is just about to go off the edge of the form we reverse its direction of motion, i.e. bounce it:

textpos.X +=v.X;

textpos.Y +=v.Y;

if (textpos.X+size.Width >= bounds.Width

|| textpos.X<=0) v.X = -v.X;

if (textpos.Y +size.Height>= bounds.Height

|| textpos.Y < 0) v.Y = -v.Y;

Finally the text is drawn at its new position on the screen:

TextRenderer.DrawText(g, displayM,

font, textpos,

Color.Red);

g.Dispose();

}

We also need some additional global variables:

private Point textpos =

new Point(0, 0);

private Point v =

new Point(1, 1);

If you now try the screensaver out you should see the message bounce its way around the form and if you resize the form the text will automatically resize and bounce at the new edge.

More like a screensaver

So far we have the basic functionality of a screensaver but it doesn’t look or behave like one. To make it look like one we have to resize the form to fill the screen and remove its border in the ShowScreenSaver function:

this.FormBorderStyle =

FormBorderStyle.None;

this.TopLevel = true;

this.WindowState =

FormWindowState.Maximized;

this.Capture = true;

Cursor.Hide();

To make the screensaver close when the mouse moves or if its button is clicked we need a simple event handler:

private void OnMouseEvent(

object sender,

System.Windows.Forms.MouseEventArgs e)

{

if (!MousePos.IsEmpty)

{

if (MousePos != new Point(e.X, e.Y))

this.Close();

if (e.Clicks > 0)

this.Close();

}

MousePos = new Point(e.X, e.Y);

}

and another global variable to store the mouse position:

private Point MousePos;

This one-event handler can deal with MouseMove and MouseDown events. View the form in design mode and use the properties window to set both events to the OnMouseEvent function.

***PROP2.TIF
Setting the event handler.

Now when you run the program it should fill the screen and close when the mouse moves or is clicked. You can improve the way that it works by adding code to close only if the mouse moves more than a set amount and you can trap keyboard events to close it.

Preview

The screensaver can also be run in /p – preview or /c – configuration mode. The configuration mode is fairly easy to implement and it’s described in the starter kit. The /p mode is much more difficult and not described anywhere so this is the puzzle to solve next!

When the /p option is passed via the command line the windows handle (hwnd) of the preview window is passed as a sequence of decimal digits on the command line. For example, if the command line is /p 8324238 then the handle to the preview window is 8324238. You could simply retrieve the hwnd and use it to construct a graphics object to draw on the preview window. This works but there is a fundamental problem with doing things in this way. The system expects the screensaver to attach a child window to the preview window and to draw on the child, not the preview. When the system needs to stop the screensaver, for any reason, it simply closes the child window. If you draw directly on the preview window then there is no child window to close and your screensaver will not be terminated when the user looks at another screensaver or views a full screen preview. The result it that you end up with lots of copies of your screensaver running at the same time and all trying to draw to the preview window – the system then crashes.

There seems to be no way of making a window a child of another window using the .NET framework and once again we have to resort to using the Windows API. First we need to change the switch to deal with the hwnd passed on the command line:

case "/p":

//preview

if (m_args.Length > 1)

{

this.TopLevel = false;

m_hwnd = (IntPtr)Int32.

Parse(m_args[1]);

ShowScreenSaver();

}

break;

and we need to add the global variable:

private IntPtr m_hwnd;

If m_hwnd has a value then we need to display using the preview screen and the ShowScreenSaver function has to be modified to initialise the form either for full screen or preview display:

void ShowScreenSaver()

{

if (this.m_hwnd == IntPtr.Zero)

{

All of the code

already in ShowScreenSaver

}

The else part of the if handles the preview and uses a number of API calls, definitions of which are given later. We need to make the form’s window a child window of the preview window. To do this we need to retrieve and modify the form’s window style to make it a child window:

else

{

int style = GetWindowLong(

this.Handle, GWL_STYLE);

style = style | WS_CHILD;

SetWindowLong(this.Handle,

GWL_STYLE, style);

Next we set tell the system that the preview window is the parent of the form’s window and tell the form’s window that its parent is the preview window:

SetParent(this.Handle,

m_hwnd);

SetWindowLong(this.Handle,

GWL_HWNDPARENT,(int) m_hwnd);

Now everything is set up for the form to display within the preview window except for the fact that it’s the wrong size and still has a border. To resize it we need to know the size of the preview window:

this.FormBorderStyle =

FormBorderStyle.None;

RECT r=new RECT();

GetClientRect(m_hwnd, out r);

To display the window a final API call is used do the resizing and place the form’s window in front of the preview window:

SetWindowPos(this.Handle,

HWND_TOP, 0, 0,

r.Right, r.Bottom,

SWP_NOZORDER |

SWP_NOACTIVATE | SWP_SHOWWINDOW);

}

For all of this to work we need to add:

using System.Runtime.InteropServices;

at the start of the file and add the following declarations:

public struct RECT

{

public int Left;

public int Top;

public int Right;

public int Bottom;

}

[DllImport("user32.dll")]

static extern bool GetClientRect(

IntPtr hWnd,

out RECT lpRect);

[DllImport("user32.dll",

SetLastError = true)]

static extern int GetWindowLong(

IntPtr hWnd,

int nIndex);

const int GWL_STYLE = (-16);

const int WS_CHILD = 0x40000000;

[DllImport("user32.dll")]

static extern int SetWindowLong(

IntPtr hWnd,

int nIndex,

int dwNewLong);

[DllImport("user32.dll")]

static extern IntPtr SetParent(

IntPtr hWndChild,

IntPtr hWndNewParent);

const int GWL_HWNDPARENT = (-8);

[DllImport("user32.dll")]

static extern bool SetWindowPos(

IntPtr hWnd,

IntPtr hWndInsertAfter,

int X,int Y,

int cx, int cy,

uint uFlags);

static readonly IntPtr HWND_TOP

= new IntPtr(0);

const UInt32 SWP_NOZORDER = 0x0004;

const UInt32 SWP_NOACTIVATE = 0x0010;

const UInt32 SWP_SHOWWINDOW = 0x0040;

To see the preview in action you have to install the screensaver. Notice that you don’t have to display the same graphics in the tiny preview window that you do full screen. You could, for example, show a few lines of explanation of the purpose of your screensaver. Screensavers still have a lot of potential left in them as a way of delivery information and as a way of using otherwise idle processor time.

**PREVIEW.TIF
The screensaver in miniature



Last Updated ( Friday, 25 September 2009 )
 
 

   
RSS feed of all content
I Programmer - full contents
Copyright © 2014 i-programmer.info. All Rights Reserved.
Joomla! is Free Software released under the GNU/GPL License.