In this article – part two of creating a screen saver in C#, we are going to pick up from where we left off in part one, which I suggest you read before continuing with this article if you haven’t already done so.
So far (in part one) we have created a Settings
class and a form where the user can edit the screen saver settings. The next part is creating the actual screen saver form.
The Screen Saver Form
The screen saver form has the following custom properties set:
this.BackColor = System.Drawing.Color.Black;
this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.None;
this.Name = "ScreenSaverForm";
this.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen;
this.WindowState = System.Windows.Forms.FormWindowState.Maximized;
The form also has a System.Windows.Forms.Timer
control placed on it which will be used as the interval between drawing each shape.
Form Mouse Events
The form must also handle mouse down and mouse move events so that when the mouse is either moved or clicked the screen saver will exit. Below is the code for these events plus the declarations we will be needing for the screen saver:
// Graphics object to use for drawing private Graphics graphics; // Random object for randomizing drawings private Random random; // Settings object which contains the screensaver settings private Settings settings; // Flag used to for initially setting mouseMove location private bool active; // Used to determine if the Mouse has actually been moved private Point mouseLocation; // Used to indicate if screensaver is in Preview Mode private bool previewMode = false; private void ScreenSaverForm_MouseMove(object sender, MouseEventArgs e) { if (!this.previewMode) { // If the mouseLocation still points to 0,0, move it to its actual // location and save the location. Otherwise, check to see if the user // has moved the mouse at least 10 pixels. if (!this.active) { this.mouseLocation = new Point(e.X, e.Y); this.active = true; } else { if ((Math.Abs(e.X - this.mouseLocation.X) > 10) || (Math.Abs(e.Y - this.mouseLocation.Y) > 10)) { // Exit the screensaver Application.Exit(); } } } } private void ScreenSaverForm_MouseDown(object sender, MouseEventArgs e) { if (!this.previewMode) { // Exit the screensaver if not in preview mode Application.Exit(); } }
As you can see in both event handlers we are checking that the screen saver is not in preview mode before exiting. Preview mode is the small window where a screen saver is previewed on the Windows Screen Saver Settings dialog. Obviously, we do not want to close our screen saver application when the user moves the mouse while in preview mode, so that is why we are performing this check.
The ScreenSaverForm_MouseMove
event handler is a little more complex. We are only closing the screen saver if the user has moved the mouse more than 10 pixels. This way we won’t exit just because the mouse sensed a one pixel move, maybe because of imperfections in the mouse surface or because of a faulty mouse (don’t you just hate faulty mice which wobble on screen when you’re not even touching them! :)).
The Form_Load Event
In the ScreenSaverForm_Load
event we need to initialise our screen saver variables and also load the settings from the Settings
class.
private void ScreenSaverForm_Load(object sender, EventArgs e) { try { // Initialise private members this.random = new Random(); this.settings = new Settings(); this.settings.LoadSettings(); this.active = false; // Hide the cursor Cursor.Hide(); // Create the Graphics object to use when drawing. this.graphics = this.CreateGraphics(); // Set the draw speed from the settings file switch (this.settings.DrawSpeed) { case Settings.Speed.Custom: tmrMain.Interval = this.settings.CustomSpeed; break; case Settings.Speed.Fast: tmrMain.Interval = 100; break; case Settings.Speed.Normal: tmrMain.Interval = 200; break; case Settings.Speed.Slow: tmrMain.Interval = 500; break; } // Enable the timer. tmrMain.Enabled = true; } catch (Exception ex) { MessageBox.Show(string.Format("Error loading screen saver! {0}", ex.Message), "Dave on C# Screen Saver", MessageBoxButtons.OK, MessageBoxIcon.Error); } }
The Draw Method
The meat of this project is the method which draws the shapes on screen. We will be calling this method DrawShape()
. To draw shapes we will be making use of Window’s Graphics Device Interface +, or GDI+ in short. GDI+ offers many cool features such as drawing with gradients, adding anti-aliasing, working with JPEG and PNG files, etc, but for the purpose of this example we only need to scratch the surface of GDI+.
The .NET Framework wraps GDI+ in a nice, easy to use, class which is called System.Drawing.Graphics
. This Graphics
class contains methods such as FillRectangle
and FillEllipse
which we will be using to draw our shapes. You can see how they are used within the DrawShape()
method below:
private void DrawShape() { try { // Rectangle object for painting shapes in Rectangle rect; // Color object used for colouring the shapes Color colour; // Generate random coordinates for drawing shapes int x1 = random.Next(0, this.Width); int x2 = random.Next(0, this.Width); int y1 = random.Next(0, this.Height); int y2 = random.Next(0, this.Height); // Get smaller coordinates int x = Math.Min(x1, x2); int y = Math.Min(y1, y2); // Get smallest dimension for use when drawing in rectangles of equal sides int smallerSide = Math.Min(Math.Abs(x1 - x2), Math.Abs(y1 - y2)); // Generate a random shape to display when user selects All shapes int allId = random.Next(0, 4); if (this.settings.UseTransparency) { // Generate a random colour with a random level of transparency colour = Color.FromArgb(this.random.Next(255), this.random.Next(255), this.random.Next(255), this.random.Next(255)); } else { // Generate a random colour with no transparency colour = Color.FromArgb(255, this.random.Next(255), this.random.Next(255), this.random.Next(255)); } // Create a coloured brush SolidBrush brush = new SolidBrush(colour); // Display shapes according to settings switch (this.settings.AllowedShapes) { case Settings.Shapes.Circles: // Draw a circle rect = new Rectangle(x, y, smallerSide, smallerSide); graphics.FillEllipse(brush, rect); break; case Settings.Shapes.Ellipses: // Draw an ellipse rect = new Rectangle(x, y, Math.Abs(x1 - x2), Math.Abs(y1 - y2)); graphics.FillEllipse(brush, rect); break; case Settings.Shapes.Rectangles: // Draw a rectangle rect = new Rectangle(x, y, Math.Abs(x1 - x2), Math.Abs(y1 - y2)); graphics.FillRectangle(brush, rect); break; case Settings.Shapes.Squares: // Draw a square rect = new Rectangle(x, y, smallerSide, smallerSide); graphics.FillRectangle(brush, rect); break; case Settings.Shapes.All: // Draw all the different type of shapes if (allId == 0) { // Draw a circle rect = new Rectangle(x, y, smallerSide, smallerSide); graphics.FillEllipse(brush, rect); } else if (allId == 1) { // Draw an ellipse rect = new Rectangle(x, y, Math.Abs(x1 - x2), Math.Abs(y1 - y2)); graphics.FillEllipse(brush, rect); } else if (allId == 2) { // Draw a rectangle rect = new Rectangle(x, y, Math.Abs(x1 - x2), Math.Abs(y1 - y2)); graphics.FillRectangle(brush, rect); } else { // Draw a square rect = new Rectangle(x, y, smallerSide, smallerSide); graphics.FillRectangle(brush, rect); } break; } } catch {} }
The comments in the code clearly explain what is being done. As you can see extensive use of the Random
class is being made because we are randomly generating the size of the shapes, the position of the shapes, the colour and also the level of transparency if UseTransparency
is enabled.
Previewing the Screen Saver
For the screen saver to run in the Windows Screen Saver Settings preview window we need to add some special code.
First of all we need to add some declarations to our form which are not normal .NET variables/classes, but they are actually calls to Window’s dlls – specifically the user32.dll.
// Changes the parent window of the specified child window [DllImport("user32.dll")] private static extern IntPtr SetParent(IntPtr hWndChild, IntPtr hWndNewParent); // Changes an attribute of the specified window [DllImport("user32.dll")] private static extern int SetWindowLong(IntPtr hWnd, int nIndex, IntPtr dwNewLong); // Retrieves information about the specified window [DllImport("user32.dll", SetLastError = true)] private static extern int GetWindowLong(IntPtr hWnd, int nIndex); // Retrieves the coordinates of a window's client area [DllImport("user32.dll")] private static extern bool GetClientRect(IntPtr hWnd, out Rectangle lpRect);
Then we must overload the screen saver form’s constructor so that we can receive the handle to the Windows Screen Saver Settings preview window. This will be given to us by Windows and I’ll explain it in more detail soon. The overloaded constructor looks like this:
public ScreenSaverForm(IntPtr previewHandle) { InitializeComponent(); // Set the preview window of the screen saver selection // dialog in Windows as the parent of this form. SetParent(this.Handle, previewHandle); // Set this form to a child form, so that when the screen saver selection // dialog in Windows is closed, this form will also close. SetWindowLong(this.Handle, -16, new IntPtr(GetWindowLong(this.Handle, -16) | 0x40000000)); // Set the size of the screen saver to the size of the screen saver // preview window in the screen saver selection dialog in Windows. Rectangle ParentRect; GetClientRect(previewHandle, out ParentRect); this.Size = ParentRect.Size; this.Location = new Point(0, 0); this.previewMode = true; }
In the above code we are receiving the handle as a parameter and passing it to the SetParent
Windows method. That method is changing our screen saver’s parent window so that the screen saver will display in the preview window. The SetWindowLong
and GetWindowLong
Windows methods are being used to inform Windows to close our screen saver application if the user selects to close the Windows Screen Saver Settings dialog. If we do not add this code we can create a memory leak because our application will still be running in the background. The GetClientRect
Windows method sets the size of our screen saver to fit the preview window. Then finally we are initialising the location of the form and setting the previewMode
flag to true
.
That all might seem pretty complicated but it’s not that bad if you take it a step at a time and understand what each call does.
So that’s basically it for our screen saver form. Now to put everything together.
Putting It All Together
To tie it all up we must edit our static void Main
method which is found in the static class Program
. But first you need to understand how Windows communicates with a screen saver.
Windows will control a screen saver by sending it one of three command line arguments. It will either send a “/p”, a “/s”, or a “/c”.
“/p” means the screen saver should run in preview mode.
“/s” means the screen saver should run normally – it stands for show screen saver.
“/c” means display the screen saver’s configuration – in our case the settings form we created in part one of this article.
So what we need to do is to check which parameter Windows passed us and then show the appropriate form. The code would look like this:
[STAThread] static void Main(string[] args) { Application.EnableVisualStyles(); Application.SetCompatibleTextRenderingDefault(false); // Check for any passed arguments if (args.Length > 0) { switch (args[0].ToLower().Trim().Substring(0, 2)) { // Preview the screen saver case "/p": // args[1] is the handle to the preview window ScreenSaverForm screenSaverForm = new ScreenSaverForm(new IntPtr(long.Parse(args[1]))); screenSaverForm.ShowDialog(); break; // Show the screen saver case "/s": RunScreensaver(); break; // Configure the screesaver's settings case "/c": // Show the settings form SettingsForm settingsForm = new SettingsForm(); settingsForm.ShowDialog(); break; // Show the screen saver default: RunScreensaver(); break; } } else { // No arguments were passed so we show the screensaver anyway RunScreensaver(); } } private static void RunScreensaver() { ScreenSaverForm screenSaverForm = new ScreenSaverForm(); screenSaverForm.ShowDialog(); }
As you can see when Windows sends us /p
we are constructing the ScreenSaverForm
using the overloaded constructor since we are sending the preview window handle as a parameter. The preview window handle was also given to us by Windows as a command line parameter, so all we had to do with it was pass it on.
When Windows sends us /s
we are just calling the RunScreensaver
method which calls the default constructor of our ScreenSaverForm
.
And finally when Windows sends us /c
we are launching the SettingsForm
dialog.
And that’s it. Our screen saver is complete.
Installing the Screen Saver
When you compile the project you will end up with an exe file. To make that exe a screen saver just rename the exe extension to scr and you’re done.
Then to install it just copy the scr file to the Windows\System32 directory and then open the Windows Screen Saver Settings dialog and you should find our screen saver listed in the drop down list box. 🙂
I hope you enjoyed this two part article series. Please feel free to leave your comments below or maybe you want to share your screen saver designs by linking to them in a comment. Either way please leave me your feedback below.
You can download the source code for this screen saver at the end of the article but remember it requires Microsoft Visual Studio 2008, although you can easily copy the code to an older version of Visual Studio.
Stay tuned for more articles soon.
Dave
Download Screen Saver source – 21.9 KB
Dave,
This is a very good article with detailed comments. I am however having a problem with the preview. In VS 2010 when I put in /p I get the following error? What am I missing?
Thanks,
Tom
Thanks for the article! Very easy to follow.
I ran into one problem, however: the mouse move and mouse click events are not firing. I added a key down event as a workaround.
The form contains panels, which contain split containers and table layouts, which contain picture boxes. I tried added the events to the panels and picture boxes, but they are still not firing. The form’s topmost property is set to true, and the cursor is hidden, but reversing these properties doesn’t make a difference either. Any ideas???
Another issue that I ran into: I added the call to SetWindowLong, but that app does not close when the screen saver settings dialog is closed, i.e. memory leak. I am running on Win7 x86
Strange – the FormClosed event was firing, even though the app wasn’t closing. Adding ‘Application.Exit()’ to this event solved the problem. Something else that I don’t understand is that adding a call to MessageBox.Show() in this event also caused the app to close.
Great tutorial Dave,
I’d like to use this as the starting point for a side-project: screensaver that reports project dashboard information. You have a source download, so I hope this is ok, but I couldn’t find any license terms. Would you consider a BSD style license?
Thanks!
Hi Daniel,
The code is licensed under a BSD license so you’re free to use it as you please. I’ve updated the website to include source code license information since I didn’t have this section before – Source Code Licensing.
Cheers,
Dave
Thanks Dave for this useful program.
Hi Dave,
Really handy article. Just one slight problem I’m having and while I’m looking for a solution online I thought I’d ask you just in case you knew!
I’ve copied the exe to System32 and renamed it .SCR. I can see it in the list but when a click on it I get an “Application could not be started” error, when I follow through the MS website it says it’s a SHIM error because windows can’t determine what version of .NET to use. Followed all that through and 3.5 is enabled on my OS. The exe also runs in any other folder I’ve tried, just not System32.
Running on Windows 7 Pro x64 and I am an Admin user.
Cheers,
Jim.
Update to my previous post. On Win7 x64 you have to use the SysWOW64 folder rather than System32.