Issues covered
This article explains:
- the basics of a screensaver
- loading a screensaver
- filling the whole screen
- handling multiple monitors
- handling events
- some little intricacies
Additionally, the code provides a basic screensaver framework which you could use easily to create screensavers on your own.
Introduction
A screensaver in Windows� is simply an executable file with the extension .scr. The only difference between a normal executable and a screensaver is that a screensaver does some specific things, viz:
- parses the command line to find out what Windows� wants it to do
- loads the screensaver appropriate to that request
- ends the screensaver (usually), when the user uses the mouse or the keyboard
The arguments Windows� passes to a screensaver are:
/s - load the screensaver
/c - load the configuration screen
/p - load the preview
Once we determine the argument passed, we load the screensaver appropriately. When there is some kind of activity, you end the screensaver or do something else.
The code explained
Step 1: Making the Main method accept command-line arguments
The first step in developing a screensaver in C# is to modify the Main method so that it could accept command-line arguments. The default Main method is like this:
static void Main()
{
...
}
As you can see, this Main method accepts no parameters. So, we add a string array parameter like this:
static void Main(string[] args)
{
...
}
Step 2: Checking the arguments
Now we need to find out what arguments were passed.
First, we check whether Windows did pass some arguments, by checking whether the length of the string array is zero or not. If it's zero, there were no arguments. A typical scenario where no arguments are passed to a screensaver is when users just double click on your screensaver file.
Secondly, if there indeed were arguments, we catch them. Windows may pass arguments in either the lower case or the upper case. So it's a good idea to induce generalization of some sort. We do this by changing the arguments to lower case (or upper case) first and then checking them. The complete Main method would look something like:
static void Main(string[] args)
{
if (args.Length > 0)
{
if (args[0].ToLower().Trim().Substring(0,2) == "/c")
{
...
}
else if (args[0].ToLower() == "/s")
{
...
}
else if (args[0].ToLower() == "/p")
{
...
}
}
else
{
...
}
}
Quite straight forward, isn't it? The only code you need to add here is what you would like to do for each argument.
Step 3: Invoking the screensaver
To run the screensaver, you call the System.Windows.Forms.Application.Run method. You pass a new form object as the argument. The code will look like:
System.Windows.Forms.Application.Run(new frmScreenSaver());
where frmScreenSaver is the main screensaver form.
When the screensaver loads, we want it to fit the entire screen. For this, we need to figure out which screen our screensaver is running in right now and its dimensions. To access screens, we use the Screen class. Using the PrimaryScreen property of this class, we could access the primary screen of the system as well as its properties. Among those properties is Bounds, representing the size of the screen. What we do is set the form's Bounds property to that of this screen so that the form would be resized to occupy the entire screen. This procedure is usually executed in the form's Load event, for example:
private void frmScreenSaver_Load(object sender, System.EventArgs e)
{
...
Bounds = Screen.PrimaryScreen.Bounds;
...
}
We need to address one possibility though - the user may be using more than one screen. If that's true, and if we load the screensaver only on one screen, the whole point of using this screensaver is lost. Hence, we find out the number of screens available, and load the screensaver in each of them. To do this, we iterate through all the available screens in the Main method, where we repeatedly call the form's constructor passing the screen's index. The set of all screens available in the system is listed in the AllScreens property of the Screen class. Thus the code in the Main method invoking the screensaver will have to be modified as:
...
...
for (int i = Screen.AllScreens.GetLowerBound(0);
i <= Screen.AllScreens.GetUpperBound(0); i++)
System.Windows.Forms.Application.Run(new frmScreenSaver(i));
...
...
Note the change in the main screensaver form's constructor - it accepts an int parameter, which represents the screen's index onto which the screensaver is to be run. In response to this, we modify the form's class by adding an int member variable which will store the screen index. The form's constructor will then be modified to initialize this variable, like this:
public frmScreenSaver(int scrn)
{
...
ScreenNumber = scrn;
...
}
where ScreenNumber is the int member variable.
After this, the Load event handler will have to be modified to resize the form to the bounds of not the primary screen, but that of the screen represented by the index, like this:
private void frmScreenSaver_Load(object sender, System.EventArgs e)
{
...
Bounds = Screen.AllScreens[ScreenNumber].Bounds;
...
}
Once this is done, we have added multiple screen display functionality to our screensaver.
You will want to make the form topmost and hide the cursor. Just call the additional methods in the Load handler like this:
private void frmScreenSaver_Load(object sender, System.EventArgs e)
{
...
Bounds = Screen.AllScreens[ScreenNumber].Bounds;
Cursor.Hide();
TopMost = true;
...
}
Step 4: Handling events
Once you have loaded your screensaver, you have to handle events. This is because when the user uses the mouse or hits the keyboard, you probably want to do something, like ending the screensaver. To do this, you add event handlers to the KeyDown, MouseMove and MoveDown events of the screensaver form. In the event handlers, you could use the form's Close method to end the screensaver.
There is one point to note here. When an application is executed, the current mouse parameters are passed to it by Windows. This naturally triggers a mouse event, which would close the screensaver. To handle this, add a Point variable in the class, initialize it when the application executes, and check the mouse position and mouse clicks when mouse events occur. If there happens to be any change, then call Close. The code would look something like:
private void OnMouseMove(object sender,
System.Windows.Forms.MouseEventArgs e)
{
if (!MouseXY.IsEmpty)
{
if (MouseXY != new Point(e.X, e.Y))
Close();
}
MouseXY = new Point(e.X, e.Y);
}
where MouseXY is the Point variable we use to store the initial mouse position temporarily. Initially, MouseXY is null; and thus is assigned the current mouse position through this code. In the next call of this event handler, MouseXY is not empty; so we check whether the position has changed; if yes, close the form.
As mentioned earlier, you should handle both MouseMove and MouseDown events in a screensaver. But since both have the same arguments, you could use the same event handler for handling both events. Such a code would look like:
private void OnMouseEvent(object sender,
System.Windows.Forms.MouseEventArgs e)
{
if (!MouseXY.IsEmpty)
{
if (MouseXY != new Point(e.X, e.Y))
Close();
if (e.Clicks > 0)
Close();
}
MouseXY = new Point(e.X, e.Y);
}
where OnMouseEvent is the event handler which is called on mouse move as well as mouse down events.
Step 5: Finally...
The last step of developing a screensaver is short and sweet...rename the file to an .scr extension. :)
You are done!
Using the bundled code
The code provided just loads a black form. To change it and implement your own features, follow these simple steps:
- Change the code in the
Main method to handle different arguments
- Add a configuration form if necessary, and add the code to load it in the
Main method
- Modify the main screensaver form so as to display what you want it to
- Rename the extension from .exe to .scr.
- Go!
| You must Sign In to use this message board. |
|
|
 |
|
 |
You discuss the argument passed for preview, but the download source doesnt even have it. So, its not a true screensaver example.
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
 |
|
 |
Unfortunately the code is wrong written to support multiple monitors. I had to spend time to find out what to do to get it work. For a begginer it gives some good clues on things around screensaver development, but there is so much more to add into it.
Niklas Henricson
|
| Sign In·View Thread·PermaLink | 2.60/5 |
|
|
|
 |
|
|
 |
|
|
 |
|
 |
I don't have the ability to test this at the moment, but the problem will be with line 33 of ScreenSaverForm.cs:
this.Bounds = Screen.AllScreens[ScreenNumber].Bounds; Try changing that to:
Rectangle windowRect = Rectangle.Empty; Screen[] screens = Screen.AllScreens;
foreach(Screen screen in screens) windowRect = Rectangle.Union(windowRect, screen.Bounds);
this.Bounds = windowRect; I can't see why this would not work - there are some messages below that seem to go about invoking instances of the screensaver per monitor, but I don't understand why that would be done.
|
| Sign In·View Thread·PermaLink | 5.00/5 |
|
|
|
 |
|
 |
Hi, is thr any way to detect if a scrn savr is running/active or get notification when a scrn saver starts running?
|
| Sign In·View Thread·PermaLink | 1.00/5 |
|
|
|
 |
|
 |
I run your program. but you not place in desktop properties. I can't configure your screensaver. Help me.
|
| Sign In·View Thread·PermaLink | 2.00/5 |
|
|
|
 |
|
|
 |
|
 |
I used your article as the starting point to write my own screen saveer here at the office. Of course mine is absurd to the point of epilipsy as it just flashes random colors.
A man said to the universe: "Sir I exist!" "However," replied the Universe, "The fact has not created in me A sense of obligation."
-- Stephen Crane
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
 |
Hi..
I have built a windows project and i have written your code..every thing was working except one problem: When i move the mouse or click its button nothing happen. But when i press any keyboard key the application closed successfully.This is the code of the Form:
private void OnMouseEvent(object sender, System.Windows.Forms.MouseEventArgs e) { if (!MouseXY.IsEmpty) { if (MouseXY != new Point(e.X, e.Y)) Close(); if (e.Clicks > 0) Close(); } MouseXY = new Point(e.X, e.Y); }
Please any suggestion could be helpful.
"I am too late but i will never give up"
-- modified at 2:49 Friday 26th May, 2006
|
| Sign In·View Thread·PermaLink | 2.00/5 |
|
|
|
 |
|
 |
Hi..
I have built a windows project and i have written your code..every thing was working except one problem: When i move the mouse or click its button nothing happen. But when i press any keyboard key the application closed successfully.This is the code of the Form:
using System; using System.Drawing; using System.Windows.Forms;
namespace EPscr { /// <summary> /// Summary description for Form1. /// </summary> public class EPscrForm: System.Windows.Forms.Form { /// <summary> /// Required designer variable. /// </summary> private System.ComponentModel.Container components = null; private int ScreenNumber; private Point MouseXY; public EPscrForm(int scrn) { InitializeComponent(); ScreenNumber=scrn; } /// <summary> /// Clean up any resources being used. /// </summary> protected override void Dispose( bool disposing ) { if( disposing ) { if (components != null) { components.Dispose(); } } base.Dispose( disposing ); }
#region Windows Form Designer generated code /// <summary> /// Required method for Designer support - do not modify /// the contents of this method with the code editor. /// </summary> private void InitializeComponent() { // // EPscrForm // this.AutoScaleBaseSize = new System.Drawing.Size(5, 13); this.BackColor = System.Drawing.SystemColors.ControlText; this.ClientSize = new System.Drawing.Size(292, 266); this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.None; this.Name = "EPscrForm"; this.ShowInTaskbar = false; this.KeyDown += new System.Windows.Forms.KeyEventHandler(this.EPscrForm_KeyDown); this.MouseDown += new System.Windows.Forms.MouseEventHandler(this.OnMouseEvent); this.Load += new System.EventHandler(this.EPscrForm_Load); this.MouseMove += new System.Windows.Forms.MouseEventHandler(this.OnMouseEvent);
} #endregion
/// <summary> /// The main entry point for the application. /// </summary> /*[STAThread] static void Main() { Application.Run(new EPscrForm()); }*/
private void EPscrForm_Load(object sender, System.EventArgs e) { this.Bounds=Screen.AllScreens[ScreenNumber].Bounds; Cursor.Hide(); this.TopMost=true; }
private void OnMouseEvent(object sender, System.Windows.Forms.MouseEventArgs e) { if (!MouseXY.IsEmpty) { if (MouseXY != new Point(e.X, e.Y)) Close(); if (e.Clicks > 0) Close(); } MouseXY = new Point(e.X, e.Y); }
private void EPscrForm_KeyDown(object sender, System.Windows.Forms.KeyEventArgs e) { Close(); }
} }
And this is the code of the class:
using System; using System.Windows.Forms;
namespace EPscr { public class DotNETScreenSaver { [STAThread] static void Main(string[] args) { if (args.Length > 0) { if (args[0].ToLower().Trim().Substring(0,2) == "/c") { MessageBox.Show("This Screen Saver has no options you can set.", ".NET Screen Saver", MessageBoxButtons.OK, MessageBoxIcon.Exclamation); } else if (args[0].ToLower() == "/s") { for (int i = Screen.AllScreens.GetLowerBound(0); i <= Screen.AllScreens.GetUpperBound(0); i++) System.Windows.Forms.Application.Run(new EPscrForm(i)); } } else { for (int i = Screen.AllScreens.GetLowerBound(0); i <= Screen.AllScreens.GetUpperBound(0); i++) System.Windows.Forms.Application.Run(new EPscrForm(i)); } } } }
Please..Could you help me to fix this problem?
-- modified at 1:29 Tuesday 2nd May, 2006
|
| Sign In·View Thread·PermaLink | 2.00/5 |
|
|
|
 |
|
 |
I'd like to update the screen with some text, so I put a label on the form, created a timer and put this test code inside it:
lblCountdownText.Text = "XXX";
Nothing happens. Is this something that's difficult to add?
Thanks for any help.
|
| Sign In·View Thread·PermaLink | 1.00/5 |
|
|
|
 |
|
 |
Excellent article thanks. I have developed a screen saver in C# but am I right in thinking it cannot be run on a target machine unless that machine has the .Net framework? I'd like to not have to include the .Net redistributable files in a download for the screensaver if possible - but would rather not start again in c++.... thanks
|
| Sign In·View Thread·PermaLink | 2.00/5 |
|
|
|
 |
|
 |
Thanks for the comment!
You will require the .NET Runtime for executing .NET based applications
Also, including the framework runtime won't be a good idea - a 20mb file for just a very small app like a screensaver won't be really attractive...you know!
Having said that, this is fast becoming a non-issue - since .NET is getting popular and now comes pre-installed with WinXP SP2 (I think) and above.
Rakesh
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
 |
1. sanjana Aug 3, 2:39 pm show options
Newsgroups: microsoft.public.dotnet.languages.csharp From: "sanjana" - Find messages by this author Date: 3 Aug 2005 02:39:07 -0700 Local: Wed, Aug 3 2005 2:39 pm Subject: fire event if screensaver starts Reply | Reply to Author | Forward | Print | Individual Message | Show original | Report Abuse
hi i want to fire an event if the screen saver starts up i have done it using a timer tick event where i have used the api function to detect if screensaver has started..but this means continuous polling at each timer click..
private void tick(object sender, System.EventArgs e) { const int SPI_GETSCREENSAVERRUNNING = 114; int screenSaverRunning = -1; // is the screen saver running?
int ok = SystemParametersInfo(SPI_GETSCREENSAVERRUNNING, 0, ref screenSaverRunning, 0);
if (ok == 0) { Console.WriteLine("Call to SystemParametersInfo failed.");
} if (screenSaverRunning != 0) { // screen saver is running MessageBox.Show("screensaver runs");
} else { } }
is there a better way of doing something like no use of timer.. i want someting like-- if screensaver starts then a event gets fired and if screen saver stops another event gets fired..this wud avoid getting in the loop of timer tick event.. So my question is "is it possible to just fire events if screen saver starts or stops without using timer ??"
is there any system event in C#.net to do this??
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
 |
We all know the one, where one monitor hogs all the CPU cycles to the expense of the other monitor.
Here is a copy of my post I emailed incase email get bounced. I that to do a lot of work typing a help article only to discover that it bounced in email ....
http://www.codeproject.com/useritems/SwarmScreenSaver.asp
Feel tree to check out the article in the post above.
Basically, the issue involves a few mods ...
1. As normal, put the drawing routines or those that call your drawing class in the paint event or override the onpaint method of the saverform.
2. Create an array of your form objects, one form per screen.
3. Then call the show method on each form, this much you know about and here is where things begin to differ ...
4. Using Invalidate() method of the form, works fine for the primary screen, but fails to work for the secondary screen.
5. Instead, use the Refresh() method of the forms. This works an all the forms and invokes the paint object, so that you will see drawing activity on all monitors (Well at least two since most cards only support two outputs).
Feel free to download the code, I believe Iv'e given you credit where credit is due, for your screensaver template was quite helpful in solving the issues involved.
The project is written in C#, VS 2003, and includes source code only.
I wanted to let you know so you too will be able to create a multi-monitor screensaver. This particular program is capable of executing an array of up to 100 instances of my drawing program, though I think you would need a supercomputer that does not exist to run it.
It uses XML to save the configs, the xml is self-recovering and so as it tries to read the config, it detects missing config or corrupted config or out of dated config files and replaces them with a new default value one.
All functions are enabled except the welcome screen for reason I explain in the article, not wanting to lock some poor soul out of their system due to a but in the OS or my program.
I hope it is of great help to you. Before I sign off, I'm going to give you credit in a reply. I am pretty sure I've given you credit, but being a ditz as I am, I might have erased a few references.
I hope you enjoy the program and thanks for your post.
Oh, I almost FORGOT the most important thing!! After calling Refresh for multiple screens or Invalidate in the case of a single monitor system, the EntryPoint's Main method must call Application.DoEvents() to get the graphics to display.
Thanks again, let me know how you like it.[^]
Been there, done that, forgot why!
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
 |
Hi Garryfre,
I didn't get time to go thru your article completely; but my first impressions is = Really cool! . I see you have done extensive research in this topic - thanks for putting all your learnings in one place! More comments when I complete reading and going thru the code.
Maybe I would update my own article with learnings from your article...hmm, but I don't think that would happen anytime soon work is hectic nowadays.
Thanks again!
Rakesh
|
| Sign In·View Thread·PermaLink | 1.00/5 |
|
|
|
 |
|
 |
You are welcome. I am glad that I could help. Yeah, I wish I still had a job. The company that purchased the firm I worked for 9 years, decided to quit, so I have plenty of time to do the research and experimentation. Anyway, thanks again!
Been there, done that, forgot why!
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
 |
I found one more thing about this multi-monitor code. I found that the code was re-invoking the paint event of the forms before the prior paint event was finished. The result was an unholy frustrating InvalidOperationException that would not show up for anything between 20 seconds to several hours. For some extremely strange reason, the public variable I created and would set to true just before calling the invalidate but would never show up as false as it was supposed to, because I had code at the end of the paint even tto set this variable as false so my loop could tell it was finished.
I used invalidate on the primary screen and Refresh on the secondary screen and I would run the loop with a wait statement waiting for the variable to turn false on the primary screen and it would never ever turn false until I had executed Application.DoEvents() which defeated the entire purpose of the variable.
Another interesting thing was that Invalidate does invoke both screens to work but only inconsistantly and never Invalidate(Bounds).
When I used Refresh() on all screens intead of invalidate, things behaved as they should have. I also noticed that when in dev mode it would hang sometimes, but only in dev mode.
Anyway, I wrapped the code in locks and try statments like a mummy trying to prevent the unholy InvalidOperationException from occurring but the mummy died of suffication. LoL!
Anyway, I thought you might be able to use the update info. The new code is a bit simpler, and using the old code, might prove enfuriating albiet I suspect the same for the new. it seems that every time I think I fixed something, I'm reminded in a most annoying way, that I have new issues to fix or scream at. 
Been there, done that, forgot why!
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
 |
Good program, helped me with a few issues, mostly properties I didn't know about, such as Bounds...there is just so much to go through in dotNET.
I expanded on the idea by wrapping the forms in a threading model and a thread controller. This allows each form on a multi-monitor system to run independantly. Works good to, I have a simple random picture moving around the screens and the multithreading makes each screen look independant.
I was wondering though; how do you get a preview screen in the little computer image on the display properties page? I've looked for an answer to this question for a while now and have not had any luck. Sorry if that's a rookie question; this is my first screensaver after all.
Thanks;
"I am the center of my own universe!" -The shy narcissist xxxroaster_toaster9000@yahoo.ca (remove the xxx to email me)
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
 |
Hello. Well, if you look at Mini-Preview.cs all the code is there and at the entrypoint.cs. I basically create a graphics object using the handle to the preview window, and get the dimentions of this window in pixels by using GetClientRectangle. Also look first at Entrypoint.cs and note how I get the handle to the preview window. It's a pretty short example so it should be fairly easy to pick out.
I tried the threading model first, but for some reason, it was extremely slow compared to the way I did it, but it seems to work better with your graphics. I would expect that sometimes a threading model would be better depending on the drawing code.
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
 |
|
 |
This screensaver does not truly implement a mulitple monitor solution.
I did extensive debugging to determine this, and yet I still doubted myself because I'm rusty at my C# but I know about multithreading.
Take a look at the code snippet below as taken from the code ...
for (int i = Screen.AllScreens.GetLowerBound(0); i <= Screen.AllScreens.GetUpperBound(0); i++) System.Windows.Forms.Application.Run(new frmScreenSaver(i)); ...
Notice the Application Run statement.
Then I looked up the docs on Application.Run(Form) overload in VS net 2003 and I saw that the notes said that the Run method does NOT return until the dispose method of the form finishes!! In other words, it never gets to the second iteration of int i because it's waiting for the form to close.
I read in the wrox book Professional C# Edition 3 that the show method is assyncronous, in that unlike Run, this method returns at once.
Here is a code snipped from my project. Be aware that this is a bit of a Kludge for I had lots of difficulty over many hours and then I had to undo a checkout and undid my checkout of the screensaver form!! Bah!!!
Here is my snippett ...
case "ShowSwarm": // Start the forms. int screenCount = Screen.AllScreens.Length; // The first screen might not start at zero. Example 2 - 3
// Need to initialize the form array with a generic form in order to get it to create instances because I could not pass // a parameter here. I tried a public property but it just blew chunks and I don't know why. The screenCount will be 2 if // screens range from 2 to 3. Form[] sf = new Form[screenCount]; // The number of instances of screens attached to the system.
// I think a while would have been better, or somehow this can be made clearer. for (int i = Screen.AllScreens.GetLowerBound(0); i <= Screen.AllScreens.GetUpperBound(0); i++) // With one screen the lower // and upper bounds is the same but might not start at Zero to UpperBounds. { sf[i] = new ScreenSaverForm(i); sf[i].Show(); // The original code used application.Run(SaverForm) but run doesn't return until the form closes!! Bleah!! } // We need a machanism to wait for all the forms to be closed ie invisible. while (screenCount > 0) // Wait till all forms have closed due to mousmoves or keypresses in the forms. { // Otherwise it exits immediately for (int i = Screen.AllScreens.GetLowerBound(0); i <= Screen.AllScreens.GetUpperBound(0); i++) { Application.DoEvents(); if (sf[i].Visible == false) { screenCount--; // When this hits zero, all screens are closed and we can hit break and exit the app. } } } break; default: // If something strange is passed in, just ignore and exit. break; } // End select. .....................................
I have completed a screensaver in C# that is both simple, with the functions and files descriptive of what they do handles Preview and Config window, so that the preview or Config window closes when the screensaver setup windows dialog box vanishes. I intend to publish the source code here, when I have finished dressing it up, filtering out the kludge, and documenting the program extensively.
My hope for this program is that it will save folks a ton of trouble and grief trying to write a respectable screensaver without begging like mad for information and help, to no avail.
Been there, done that, forgot why!
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
 |
In my code above I mention that GetLowerBounds(0) might not start at zero. It always does. I am a bit puzzled as to why, this function even exists, except for clarity purposes. Here is my slightly revised code for clarity, tho I think it could be more clear....
case "ShowSwarm": // Start the forms. int screenCount = Screen.AllScreens.Length; // The reason I use Form[] is because I needed to intialize the array element count with a generic form instead of ScreenSaverForm due to constructor issues. Form[] sf = new Form[screenCount]; // The number of instances of screens attached to the system. int i = 0; while(i < screenCount) { sf[i] = new ScreenSaverForm(i); sf[i].Show(); // The original code used application.Run(SaverForm) but run doesn't return until the form closes!! Bleah!! i++; } // We need a machanism to wait for all the forms to be closed ie invisible. while (screenCount > 0) { Application.DoEvents(); if (sf[screenCount-1].Visible == false) { screenCount--; } } break; default: // If something strange is passed in, just ignore and exit. break; } // Switch end.
Been there, done that, forgot why!
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|