First, let me clear one thing up. A screen saver is nothing more than a standard Windows executable with the .scr extension instead of the standard .exe extension. It has the same file format as an exe, and of course, you can make a .NET screen saver. You could take any old application you have on your computer, and give it a .scr extension, and you would have a perfectly valid Windows screen saver. However, you probably wouldn't want to do that. That's because screen savers do have certain responsibilities:
When Windows runs a screen saver, it will pass certain arguments to it. These arguments are
"/s" (Show the screen saver), "/p" (preview the screen saver in the small little monitor on the screen saver selection dialog), and "/c" (Show the screen saver's options, or configure it). Windows expects your application to read these arguments in your screen saver's Main method, and act on them accordingly.
A screen saver usually takes up the whole screen. Thus, you must have some sort of exit signal built into the screen saver. Traditionally, a screen saver will exit if a key is pressed, or if the mouse is moved.
A screen saver also will stay on top of all other windows, and it will hide the cursor.
Reading the argumentsThe only place you can read the arguments in a C# application is in the Main() method automatically generated. Go to the solution explorer. There should be a code file called "Program.cs". Open it up in the code editor. It might look close to this:
csharp
using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows.Forms;
namespace YourApplicationNamespace
{
static class Program
{
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new YourApplicationMainForm());
}
}
}
All we are concerned with in that is the main method:
csharp
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new YourApplicationMainForm());
}
By default, the Main() method will take no arguments. You must edit it so it will take an argument of "string[] args" that way you will be able to read the arguments passed to your application:
csharp
static void Main(string[] args)
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new YourApplicationMainForm());
}
the args parameter will now hold all the arguments passed to your application. Now, you must interpret those arguments. Only one argument should be passed to a screen saver, the argument that tells the screen saver what windows wants it to do. We could read the arguments easily with a simple if statement like so:
csharp
if (args[0].ToLower().Trim().Substring(0,2) == "/s") //show
{
//show the screen saver
}
else if (args[0].ToLower().Trim().Substring(0,2) == "/p") //preview
{
//preview the screen saver
}
else if (args[0].ToLower().Trim().Substring(0,2) == "/c") //configure
{
//configure the screen saver
}
However, it is possible for no arguments to be passed to a screen saver. Thus we should probably add in another if statement
csharp
if (args.Length > 0)
{
if (args[0].ToLower().Trim().Substring(0,2) == "/s") //show
{
//show the screen saver
}
else if (args[0].ToLower().Trim().Substring(0,2) == "/p") //preview
{
//preview the screen saver
}
else if (args[0].ToLower().Trim().Substring(0,2) == "/c") //configure
{
//configure the screen saver
}
}
else
{
//no arguments were passed (we should probably show the screen saver)
}
That way, you won't get an index out of range exception if you try to reference the first argument passed and there is nothing there.
At this stage, we must now modify our main method to look like this
csharp
static void Main(string[] args)
{
if (args.Length > 0)
{
if (args[0].ToLower().Trim().Substring(0,2) == "/s") //show
{
//show the screen saver
}
else if (args[0].ToLower().Trim().Substring(0,2) == "/p") //preview
{
//preview the screen saver
}
else if (args[0].ToLower().Trim().Substring(0,2) == "/c") //configure
{
//configure the screen saver
}
}
else
{
//no arguments were passed (we should probably show the screen saver)
}
}
Setting up the screen saverAt this point, if we try to run our application, it would run through like a console application with no code because we never invoke Application.Run() anywhere in our main. After we do a few things here, that will be taken care of. Now, i have made several screen savers in C# that support multiple monitors, and i will teach you the structure i use to make them. I'm not saying my way is the best, but it works and it's organized. To make multiple monitor support, instead of simply making a main form and stretching it to fill up all the screens, i make screen savers so it will show a different main form on every screen (by the way, whenever i refer to the main form in the code, i will call it "MainForm"). To do this, the System.Windows.Forms.Screen class comes in handy. Take a look at this code
csharp
foreach (Screen screen in Screen.AllScreens)
{
MainForm screensaver = new MainForm(screen.Bounds);
screensaver.Show();
}
The foreach loop you see is looping through all the screens (monitors) on the current computer. I am then creating a new main form for that monitor, passing it the bounds of that screen, and showing it. But wait a minute, that will not work because there is no constructor method in our main form that takes an argument. So, let us make one. I added this exact constructor to my main form
csharp
public MainForm(Rectangle Bounds)
{
InitializeComponent();
this.Bounds = Bounds;
Cursor.Hide();
}
As you can see, it takes an argument of a System.Drawing.Rectangle (that's what bounds are) and in the constructor it would set the form's bounds to the one passed in the constructor. It would also hide the cursor, because like i said, screen savers hide the cursor.
So, to run the screen saver, we could create a method like so
csharp
void ShowScreenSaver()
{
foreach (Screen screen in Screen.AllScreens)
{
MainForm screensaver = new MainForm(screen.Bounds);
screensaver.Show();
}
}
Typically, i will make a method like that to show the screensaver, and put that in the class my main method is in (Program). So, your Program class would now look like
csharp
using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows.Forms;
namespace YourApplicationNamespace
{
static class Program
{
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
static void Main(string[] args)
{
if (args.Length > 0)
{
if (args[0].ToLower().Trim().Substring(0,2) == "/s") //show
{
//show the screen saver
}
else if (args[0].ToLower().Trim().Substring(0,2) == "/p") //preview
{
//preview the screen saver
}
else if (args[0].ToLower().Trim().Substring(0,2) == "/c") //configure
{
//configure the screen saver
}
}
else
{
//no arguments were passed (we should probably show the screen saver)
}
}
static void ShowScreenSaver()
{
foreach (Screen screen in Screen.AllScreens)
{
MainForm screensaver = new MainForm(screen.Bounds);
screensaver.Show();
}
}
}
}
(Note we must make the ShowScreenSaver() method static when we put it in the Program class, as it is a static class)
now, let's modify our Main() method so it will actually use that method if our application was passed /s or if it was passed nothing
csharp
using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows.Forms;
namespace YourApplicationNamespace
{
static class Program
{
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
static void Main(string[] args)
{
if (args.Length > 0)
{
if (args[0].ToLower().Trim().Substring(0,2) == "/s") //show
{
//show the screen saver
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
ShowScreenSaver(); //this is the good stuff
Application.Run();
}
else if (args[0].ToLower().Trim().Substring(0,2) == "/p") //preview
{
//preview the screen saver
}
else if (args[0].ToLower().Trim().Substring(0,2) == "/c") //configure
{
//configure the screen saver
}
}
else
{
//no arguments were passed (we should probably show the screen saver)
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
ShowScreenSaver(); //this is the good stuff
Application.Run();
}
}
static void ShowScreenSaver()
{
foreach (Screen screen in Screen.AllScreens)
{
MainForm screensaver = new MainForm(screen.Bounds);
screensaver.Show();
}
}
}
}
Guess what, we now have a working screen saver. Working, but not complete. We still need to make our main() method allow for /p and /c. We will now set it up for a /c (configure) argument as that is quite easy. Do you want the user to be able to choose some of the options in the screen saver? If so, simply create a form where the user can configure the options, and have your application so it will store those options in the application's settings, an ini file, the registry, or wherever. Now, we need only to modify the part of our main method that handles /c
csharp
else if (args[0].ToLower().Trim().Substring(0,2) == "/c") //configure
{
//configure the screen saver
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new ConfigureForm()); //this is the good stuff
}
(in this code, i referred to the configuration form as "ConfigureForm", but that can be changed)
Notice i am using an Application.Run() call to start the application, and all i am doing is passing it the configuration form as the application's main form.
On the other hand, if you don't have any options the user can set, we can do something very easy like so
csharp
else if (args[0].ToLower().Trim().Substring(0,2) == "/c") //configure
{
//nothing to configure
MessageBox.Show("This screensaver has no options that you can set",
"My Cool Screen Saver",
MessageBoxButtons.OK,
MessageBoxIcon.Information);
}
That will show a message telling the user the screen saver has no configurable options. Then, since we never invoked Appliction.Run(), the application will exit once the main method has run through, like a console application.
Now we can handle the /p (preview) argument. The preview is when Windows wants your screen saver to preview itself in the little computer monitor on the screen saver dialog. There are two ways we can go about this. Generally, i choose to screw the preview all together. By the way, if you don't want a preview, your done. You don't need any code in the section that handles the preview, so when Windows calls your application to preview, it will start, run through, and since the Application.Run() was never called, it will close in an inconceivably small amount of time like it never even ran. That was easy, however, if you so desire for your screen saver to have a preview, i will teach you how.
Setting up the previewThis is a bit tricky. It takes a few API's to do. Windows will not automatically do some voodoo magic to make your application show perfectly in the preview window, you have to do it on your own. It does, however, lend you some help. When Windows passes your application the /p command, it will also pass another argument: the handle to the preview window on the select screen saver dialog. With that, we can use a few API's to make it the parent of our screen saver's main form. This is how it will work:
1. The main function will see the first argument is /p. It then knows the second argument is the handle to the preview window.
2. The main function will run the application with MainForm as the main form using Application.Run(). It will also pass the main form the handle to the preview window.
3. The main form will configure itself so that it will show inside the preview window.
4. They all lived happily ever after.
Lets start to implement that. First, let me refresh your (and my) mind of what the section of our main routine that handles /p looks like
csharp
else if (args[0].ToLower().Trim().Substring(0,2) == "/p") //preview
{
//preview the screen saver
}
As it is now, it is set up in a way where there will be no preview since there is no code except for that lonely little comment. We must modify that to look like this
csharp
else if (args[0].ToLower().Trim().Substring(0,2) == "/p") //preview
{
//show the screen saver preview
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
//args[1] is the handle to the preview window
Application.Run(new MainForm(new IntPtr(long.Parse(args[1]))));
}
Looks like that comment's not so lonely anymore. Anyway, that should be all you need to do to the main function. However, see the line
csharp
Application.Run(new MainForm(new IntPtr(long.Parse(args[1]))));
That is passing MainForm the handle to the preview window in the form of an IntPtr. However, we have no constructor made for that form that will accept an IntPtr. Well, time to make one. Guess what, you don't have to do any work again because all you have to do is use this code i've already written
csharp
//This constructor is the handle to the select screen saver dialog preview window
//It is used when in preview mode (/p)
public MainForm(IntPtr PreviewHandle)
{
InitializeComponent();
//set the preview window as the parent of this window
SetParent(this.Handle, PreviewHandle);
//make this a child window, so when the select screen saver dialog closes, this will also close
SetWindowLong(this.Handle, -16, new IntPtr(GetWindowLong(this.Handle, -16) | 0x40000000));
//set our window's size to the size of our window's new parent
Rectangle ParentRect;
GetClientRect(PreviewHandle, out ParentRect);
this.Size = ParentRect.Size;
//set our location at (0, 0)
this.Location = new Point(0, 0);
IsPreviewMode = true;
}
In case you haven't figured out, i am making a real screen saver as i am writing this tutorial. Anyway, that is exactly what you should use. But wait a minute, that is gonna throw a whole bunch of errors at you if you try to use it right now. That's because there are four API's i'm using in there aren't in your code yet. Here they are
csharp
#region Preview API's
[DllImport("user32.dll")]
static extern IntPtr SetParent(IntPtr hWndChild, IntPtr hWndNewParent);
[DllImport("user32.dll")]
static extern int SetWindowLong(IntPtr hWnd, int nIndex, IntPtr dwNewLong);
[DllImport("user32.dll", SetLastError = true)]
static extern int GetWindowLong(IntPtr hWnd, int nIndex);
[DllImport("user32.dll")]
static extern bool GetClientRect(IntPtr hWnd, out Rectangle lpRect);
#endregion
I believe i did a fairly good job of explaining exactly what is going on in my comments, so i'll move on. Oh, and about that variable in the constructor i gave you called IsPreviewMode. That is to let the application know weather or not it is in preview mode when it's running. If you are making a screen saver as you are reading this, make a global boolean called IsPreviewMode. You will need a variable like that because
when you are in preview mode, you want to disable any mouse or key events that will make the screen saver exit. For example, if you had a key down event that had code in it to exit the application, you would want to put an if statement around that to make sure your application is not in preview mode. Why? When your screen saver is previewing in the preview window on the select screen saver dialog, any time you moved your mouse over the preview window, pressed a key, or clicked the preview window, you certainly don't want the preview to go away, and that is what will happen if we don't take those precautions. You can also use the IsPreviewMode variable to make it so certain code won't run through when in preview mode, and so code will run through in preview mode that won't run in standard mode. That can be very useful.
Putting it all togetherOk, this is about what your Program.cs file should look like at this point:
csharp
using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows.Forms;
namespace YourApplicationNamespace
{
static class Program
{
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
static void Main(string[] args)
{
if (args.Length > 0)
{
if (args[0].ToLower().Trim().Substring(0, 2) == "/s") //show
{
//run the screen saver
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
ShowScreensaver();
Application.Run();
}
else if (args[0].ToLower().Trim().Substring(0, 2) == "/p") //preview
{
//show the screen saver preview
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
//args[1] is the handle to the preview window
Application.Run(new MainForm(new IntPtr(long.Parse(args[1]))));
}
else if (args[0].ToLower().Trim().Substring(0, 2) == "/c") //configure
{
//Show the configuration dialog, or inform the user there are no options with a message box here
}
else //an argument was passed, but it wasn't /s, /p, or /c, so we don't care wtf it was
{
//show the screen saver anyway
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
ShowScreensaver();
Application.Run();
}
}
else //no arguments were passed
{
//run the screen saver
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
ShowScreensaver();
Application.Run();
}
}
//will show the screen saver
static void ShowScreensaver()
{
//loops through all the computer's screens (monitors)
foreach (Screen screen in Screen.AllScreens)
{
//creates a form just for that screen and passes it the bounds of that screen
MainForm screensaver = new MainForm(screen.Bounds);
screensaver.Show();
}
}
}
}
And just to be clear, these are the two new constructors you should have put in your MainForm (you will only have the top one if you chose not to do a preview)
csharp
//This constructor is passed the bounds this form is to show in
//It is used when in normal mode
public Form1(Rectangle Bounds)
{
InitializeComponent();
this.Bounds = Bounds;
//hide the cursor
Cursor.Hide();
}
//This constructor is the handle to the select screensaver dialog preview window
//It is used when in preview mode (/p)
public Form1(IntPtr PreviewHandle)
{
InitializeComponent();
//set the preview window as the parent of this window
SetParent(this.Handle, PreviewHandle);
//make this a child window, so when the select screensaver dialog closes, this will also close
SetWindowLong(this.Handle, -16, new IntPtr(GetWindowLong(this.Handle, -16) | 0x40000000));
//set our window's size to the size of our window's new parent
Rectangle ParentRect;
GetClientRect(PreviewHandle, out ParentRect);
this.Size = ParentRect.Size;
//set our location at (0, 0)
this.Location = new Point(0, 0);
IsPreviewMode = true;
}
Now, with what we have thus far, we are pretty good to go, but there's still one more thing we need to do. We absolutely have to add in functions that will make the screen saver exit when a key is pressed, the screen saver is clicked, or if the mouse is moved significantly over the screen saver. All you really have to do is something like this
csharp
#region User Input
private void Form1_KeyDown(object sender, KeyEventArgs e)
{
//** take this if statement out if your not doing a preview
if (!IsPreviewMode) //disable exit functions for preview
{
Application.Exit();
}
}
private void Form1_Click(object sender, EventArgs e)
{
//** take this if statement out if your not doing a preview
if (!IsPreviewMode) //disable exit functions for preview
{
Application.Exit();
}
}
//start off OriginalLoction with an X and Y of int.MaxValue, because
//it is impossible for the cursor to be at that position. That way, we
//know if this variable has been set yet.
Point OriginalLocation = new Point(int.MaxValue, int.MaxValue);
private void Form1_MouseMove(object sender, MouseEventArgs e)
{
//** take this if statement out if your not doing a preview
if (!IsPreviewMode) //disable exit functions for preview
{
//see if originallocation has been set
if (OriginalLocation.X == int.MaxValue & OriginalLocation.Y == int.MaxValue)
{
OriginalLocation = e.Location;
}
//see if the mouse has moved more than 20 pixels in any direction. If it has, close the application.
if (Math.Abs(e.X - OriginalLocation.X) > 20 | Math.Abs(e.Y - OriginalLocation.Y) > 20)
{
Application.Exit();
}
}
}
#endregion
Now, you obviously can't just copy/paste that because you have to make those functions event handlers. Anyway, that won't be hard at all. Note that i used an Application.Exit() call instead of a this.close() because, though i call it MainForm, it really isn't a "Main Form" because if it is closed the application will still keep on running. To fix that, we simply use the Application.Exit() call. Now, there's only one thing you have left to do: change the extension of your build application from .exe to .scr, and if you want to use it as a screen saver on your computer, copy it into the system32 directory and set it as your screen saver.
I said i was making a real screen saver as i was writing this tutorial, and it's only fair i give it to you as an example. The screen saver i made shows a fake, but highly authentic blue screen of death, like the BSOD screen saver on SysInternals. Anyway, that's not the point. The point is that it demonstrates everything i have said. It also has a preview.
Blue_Screen_saver.zip ( 98.3k )
Number of downloads: 985