Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

A complete C# Screensaver that does double-buffering on multiple monitor systems!

0.00/5 (No votes)
24 Jun 2005 2  
Example screensaver in source code. Does a mini-preview too!

Introduction

I wanted to write a screensaver. Yeah, I bet everyone goes through this stage, but this time I got the needed info so the purpose of this article is to impart to you the info that for so many years was missing or hidden or only available to a privileged few.

First of all I would like to thank Rakesh Rajan for his fine article that got the ball rolling, and I was able to fill in the gaps and complete a screensaver with all the features I really could possibly want. You can view his fine article - How to develop a screen saver in C#.

Hmmm, that's a long URL, I hope it fits! Thanks again!. Further credits to two others can be found in the source code.

Double-buffering

This is a technique to do flicker free, and faster drawing. It involves doing all your drawing on an off-screen bitmap. Because this bitmap is not on display the drawing routines run much faster because the drawing doesn't invoke a paint event. After all drawing is done, it is processed for display. There are extremely quick double-buffering that forms and other controls have built in, but it has its issues, which means you might be tortured by the InvalidOperationException error. This was a particularly nasty error to try to deal with because in my program, it drops right through the catch statements that I made for this error as if it did not match and what makes it worse is it's so intermittent, it might show up immediately, or might run for several hours before showing its ugly head. To rid yourself of this nefarious bug, use the Flush method on your Graphics object after you've done a drawing cycle . Example ... pev.Graphics.Flush(System.Drawing.Drawing2D.FlushIntention.Flush); and this will eliminate the error. The cause of the error is that unless you do use the above command, sometimes, the buffer is not drawn to the screen right and your code could end up doing another paint cycle while the buffer is being written, or usually while the buffer is being disposed, which causes a conflict where your code is trying to use the buffer, while it is unavailable! The GDI+ does not check for this. If you use the Flush as above, the buffer is written to the screen and refreshed and ready to use when you need it.

Update: 06/18/2005 - I have solved the above issue. It is quite an interesting find, as to why. See end of article for more info.

6/10/2005- Neat new stuff added. You gotta try this with seven swarms a screen!

I have just succeeded in getting this screensaver to work on multiple monitors in duel mode on a single system at the same time!! I am going to upload the new code, and then I'll work on the snippets, for that is quite a job. As far as I know this is the only screensaver that can do this. I tried many others, and none worked. I have explained what I had to do in EntryPoint.cs in the source files. I have been having some stability issues. Hopefully the changes I made on 6/13/2005 fix some of them.

6/16/2005- I have implemented event and delegate driven engine. I separated some of the code that was getting rather cluttered into separate classes, solved the stability issues and modified snippets.

See end of article!

After searching the internet for weeks, trying to find the info I needed to make my own screensavers and coming up with woefully incomplete examples that glossed over vital information, or examples that were enormous, yet devoid of meaningful comments so that by the time I found the answer, I had forgotten the question. Almost all the examples did not work or had bugs that prevented me from testing them. I determined that I would write a "Complete" screensaver and post it on the web so that others need not go through the torment I did trying to find the info needed. What I mean by putting "Complete" in quotes, is that the only feature I did not implement was bringing up the password dialog box when the program received an/a as an argument. I did not wish to have some bug or future change in Windows, to cause my screensaver to lock a user out of the system. Caveat: On NT 2000, if the computer has the feature to lock the computer turned on after X minutes of inactivity, and the computer is locked while the screensaver is running, when you touch your machine, the screensaver shuts down, but the welcome box from Windows asking for the password does not show. The fix is simple .. Hit Ctrl-Alt-Delete or Ctrl-C and the welcome screen comes up and you are good to go.

This article will still give you the info you need to make your own screensaver.

The problems I needed to solve were ....

  1. How to attach the Configuration dialog as a child of the Display Properties box from Windows.
  2. How to draw to the little mini-preview box on the above form.
  3. Where to put the drawing routines and how should I execute them.
  4. How to save my configurations to the registry, and just recently, how to save CONFIG stuff to an XML file instead of the registry.
  5. How to get a screensaver instance to draw to all active screens on a system!

Using the code

For your own use just replace clsInsects with your own class of drawing routines, and then modify the load event and the paint event in the main ScreenSaverForm.cs and the code in EntryPoint.cs, ScreenSaver.cs, MiniPreview.cs, XmlScreenSaverConfig.cs and the ConfigForm.cs to your liking and have fun!

This project used to be separated into a couple DLLs and an EXE. Now there are no DLLs, you just compile, rename the EXE as .scr and copy to the system32 directory and then choose the name as your screensaver and you are done or you can configure some nice options.

The code in StructsAndFunctions.cs could be used for your own programs, it does not depend on any of the other modules, but the Insects class and the other modules all depend on StructsAndFunctions.cs.

The ScreenSaverForm load event initializes the size of the form and my Insects' class instance which do all the drawing. Then a while loop in the EntryPoint classes Main method calls each form using an appropriate method to invoke the screens to draw and show the results.

Here is how I got the program to work with multiple monitors. The code is in Entrypoint.cs under the "/s" case statement.

I create an array of ScreenSaverForms which handles the full-screen drawing called sf. Then I go through the array with a while statement and call the show method of each ScreenSaverForm in the array, I then call the Application.DoEvents() when I get a signal from manualEvents that a delegate has been called by a form with drawings that are ready for display. I found that I had to cause the forms to draw the graphics using the Refresh() method on the forms and then call Application.DoEvents() to cause the drawing to appear on multiple monitors.

  1. If the system only has one monitor, I found that calling sf[i].Invalidate(sf[i].Bounds) method where i = the screen number, the primary screen worked fine and was faster when this method failed to cause the second monitor to show anything. The first monitor would hog all the CPU time at the complete expense of the second.
  2. I found that if I used the ScreenSaverForm's Refresh() method when there were multiple active screens, I was able to invoke drawing on both monitors and the results would show when I called Application.DoEvents(). I no longer use Invalidate as it caused synchronization issues with timing as I learned that events are on a separate thread, and Refresh() was the only way to get the truth, of the state of a variable I used to query if the form was ready for another call to PaintMe().
  3. I have my code for drawing in the screensaver forms one form per monitor, but I had issues with the program triggering the form to draw before it was finished and ready to draw again.

Update: I ended up writing some delegates and passing them into the forms and using these delegates to notify the calling class ScreenSaver when a form was ready for drawing via Application.DoEvents() and then invoking a new paint event via the form's Refresh() method.

Originally, I had written this to use threading, but I found that the worker threads were very slow compared to having all instances of the ScreenSaverForm running under the main thread. It was also good for avoiding thread locking issues. With one thread acting as if it were multiple threads, there was no chance of thread locking problems. Now, I do the drawing in the paint events, wherever possible.

One thing you will notice right off is that the source code is chock full of comments to the point that the code itself is nearly buried for two reasons...

  1. It was better than searching through mounds of incomprehensible code, only to discover that the feature I'm trying to emulate, isn't even in the sample at all or the sample is so broken, I can't get it to work, or finding that the code is so difficult to understand, that I can't implement or find the solution.
  2. The code contains a special type of comment that starts with /// that contains tags that are used by the IDE to generate a local web page where the user can look up info on virtually every object in the code by clicking on links and get a good idea of what the code is all about. Within a folder created by extracting this example you will find a folder called CodeCommentReport that contains Solution_SwarmScreenSaver.HTM. This is the file you should open with your browser to see this neat feature. Also see GettingStarted.txt for info on installing the screensaver.

Most of the comments is for reason #2, but I wanted to make sure the code was understood. I try to make few assumptions that the reader knows something.

Problems that had to be solved ...

  1. Code comment HTML was not working correctly. To find the fix go to this link or follow the procedure outlined below ...

    Build Comment Web Pages in VS 2003 and Windows XP SP2. If you use the documentation created by Build Comment Web Pages in Visual Studio 2003, you will experience problems viewing the documentation after upgrading to Windows XP SP2. Currently, you can download the release candidate for testing purposes here: The service pack includes numerous fixes to improve the overall security of Windows and Internet Explorer. Unfortunately, one fix disallowed a tag that was generated by the Build Comment Web Pages feature.

    Workarounds

    The easiest workaround is to use Visual Studio to do a Find and Replace to change the tag in the documentation to one that is accepted by Internet Explorer.

    1. Go to Edit/Find and Replace/Replace in Files.
    2. In the Find what: type: <!-- saved from url=(0007)http:// -->
    3. In the Replace with type: <!-- saved from url=(0014)about:internet -->
    4. Ensure Regular Expressions is not checked.
    5. In the look in type the directory where the web pages are stored.
    6. Click Replace All to execute the search.

    Background

    The tag is that the problem here is called the Mark of the Web. IE adds the Mark of the Web to web pages that are saved from the internet. This way IE can detect that the content was originally from the internet zone and it should be treated as such. In this instance, the page was not saved from the internet but Visual Studio used http:// to force it to the internet zone. In Windows XP SP2, the IE team did work to tighten up the items which were permissible and http:// was accidentally excluded. The IE team assures me that the original mark of the web that is generated by VS 2003 does not represent a security risk.

    If you have any questions, please let me know.

    -Sean Laberee

    Posted on Friday, July 16th, 2004 5:07 PM by VSEditor.

    Important namespaces classes and filenames....

    Namespace CommonCode contains structure definitions that are used in various projects in the solution. StructsAndFunctions.cs contains these structs and a class called CommonFunctions that contains functions random number generation and color adjustment routines so the swarm isn't too dark to be visible. These are used by the drawing routines in clsInsects. The color adjustment routines are not totally accurate according to the site, but it helped a lot. Thanks to George Shepherd's site.

    XmlSwarmScreenSaverConfig.cs contains a self-recovering XML CONFIG reader/writer that is used to save your screensaver settings. The saver tries to read the configuration which is named like SaverName.scr.config.xml. You must rename the resulting .exe extension to .scr and copy the two DLLs that result from compiling this source code into the System32 directory.

    If the reader fails to find the file, or finds the structure of the file varies because I added a feature, it creates a new one containing default values. This makes configuration saving extremely transparent and easy to utilize. Feel free to use this module for saving configuration info in your own apps.

    Namespace SwarmScreenSaver contains the EntryPoint class that is where the program begins execution, and the definitions of the CONFIG and the ScreenSaverForm which is the form used to display drawing in full-screen mode. There is also the MiniPreview class that handles drawing to the mini-preview window. The ScreenSaver class is used to run the screensaver in full-screen mode and handles shutting down of the app when done.

    Namespace Insects contains my code for the clsInsects that is the class that does the drawing to the graphics object it receives.

  2. How to make a window of my application a child of the Windows screensaver dialog box by using the number passed in when Windows indicated the user wanted to configure the screensaver.

    When the user clicks the settings button Windows starts up the program with a commandline like this ... /c:######## where # is supposed to be a digit in a 10 base number that is the handle for the dialog box shown above. I've modified this so that the user can type swarm /c to bring up the CONFIG box.

    The reason this was desired was if the user closed down the dialog box, my program was supposed to close down too. Setting up this Child-Parent relationship proved impossible in C# because newer versions of windows implements security that does not allow such relationships unless both Windows were within the same application! To solve this issue for the CONFIG form was to drop a timer control on the form and set it to check for the visibility of the Display Properties window every tenth of a second and close if it was gone.

    The API calls used below reside in a class in StructsAndFunctions called by the rather unimaginative name of CommonFunctions. First I declare the API calls I need and then I declare wrappers for them, because they could not be called directly but only via these wrappers.

    Watch out!!, here comes the code!

    Declares an API call to determine if a window is visible. hWnd is a handle to the window being checked for visibility. Returns true, if the window is visible, else false.

    [DllImport("user32.DLL",EntryPoint="IsWindowVisible")]
      private static extern bool IsWindowVisible(IntPtr hWnd);

    Declare the API call to get the size and location of the client window.

    hWnd is a handle to the window being addressed. Struct containing Size and Location to be filled with the client area size and location as pixels in RECT rect instance passed in. Returns true if success, otherwise false.

    [DllImport("user32.dll")]
      private static extern bool GetClientRect(IntPtr hWnd, ref RECT rect);

    Wrapper to call the IsWindowVisible API call. hWnd = Handle to the Desktop Properties window. Returns true if visible, else false.

    public bool IsWindowVisibleApi(IntPtr hWnd)
    {
        return IsWindowVisible(hWnd);
    }

    Wrapper to call GetClientRect API functions in user32.dll to get size of the client area of a window. hWnd is a handle to the desktop properties dialog box. rect is an instance of a RECT struct to be filled with the location and size of the client area. Returns true if successful, false if unsuccessful.

    public bool GetClientRectApi(IntPtr hWnd, ref RECT rect)
    {
       return GetClientRect(hWnd, ref rect);
    }

    Here is a sample snippet from my CONFIG form.

    namespace SwarmScreenSaver
    { 
     public class ConfigForm : System.Windows.Forms.Form 
     {   //... Some code inserted by the form creater.  
    
      // Put the declaration of cParentWindowHandle 
    
      // in your class declaration 
    
      // and above the class constructor.
    
      // the c prefix is to remind me that this 
    
      // is a handle to the config window.
    
      private static IntPtr cParentWindowHandle = 
                                      new IntPtr(0);
      // The class instance cf contains 
    
      // API call wrappers other
    
      // functions we will use.
    
      CommonCode.CommonFunctions cf = new CommonFunctions ();

    Here is the constructor. Insert the code below the InitializeComponants call in the constructor of your configuration form. I modified this code below to work when CONFIG is brought up by the running swarm with /c as a command line.

    In the configure mode, the handle starts at the third character - Example: /c:345678.

    public ConfigForm(int IntArg)
    {
      InitializeComponent();
      // The line above is inserted by the form generator VS.

    ***** Start of the code you'll need to insert.

    If no handle, skip this. User started CONFIG using /c on command line. I use a parsing routine in the EntryPoint class to get the handle as a number that is passed to the rest of the program as needed.

    if (IntArg != 0) 
    {
      cParentWindowHandle = (IntPtr) IntArg; 
      // start the timer to checking if the parent 
    
      // window is closed if needed. 
    
      CheckToCloseTimer.Enabled = true;
    } // Constructor end. --------------------------

    Here is what handles closing the CONFIG form when needed....

    // Here is my event handler for the timer ...
    
    private void CheckToCloseTimer_Tick(object sender, 
                                      System.EventArgs e)
    {
    if (cf.IsWindowVisible(cParentWindowHandle) == false) 
     {
        // Turn off the timer.
    
        CheckToCloseTimer.Enabled = false; 
        Close(); // Close the form.
    
       } // End if.
    
      } // end timer eventhandler.  
    
     } // End class definition.
    
    } // end namespace SwarmScreenSaver.

    That is all you need for controlling when to close the CONFIG form in response to the Display Properties box closing. Remember to look closely at the methods in the code for retrieving, verifying and saving configuration data.

    Caveat: I wanted to actually track the location of the Windows Display Properties dialog box, but when I moved it, the above code would think it was invisible and close my CONFIG window.

  3. When the screensaver received /p:####### this meant that my screensaver was to draw to the small mini-preview window in the Display Properties dialog. We need to get the size of the little black box we draw the mini-preview in. To do this we need a RECT structure to pass in.

    Here is my definition of a Rectangle from the CommonCode namespace. Notice!! This is not really in this file but is elsewhere and is shown here for clarity. This RECT definition is in StructsAndFunctions class. rect is sent to the API call GetClientRect via GetClientRectApi to get the size of the mini-preview window in pixels....

    public struct RECT 
    { 
    public int left;  
    public int top;  
    public int right;  
    public int bottom;
    public RECT(int l, int t, int r, int b)  
      {   
       left = l;   
       top = t;   
       right = r;   
       bottom = b;  
      }  // End struct constructor.
    
    } // End struct.

    Here is the start of code for the EntryPoint class.

    using CommonCode.XmlConfig; 
    namespace SwarmScreenSaver {

    [STAThread] before the Main is no longer needed since the new 1.1 framework.

    public class Entrypoint
    {
        // This parses the command line into a prefix 
    
        // and ArgHandle int. This = 0 if no handle.
    
        private static void ParseArgsToPrefixAndArgInt(string[] args, 
                             out string argPrefix, out int argHandle)
        {
            string curArg; // The current argument we are processing.
    
            // we need to remove this trash.
    
            char[] SpacesOrColons = {' ', ':'}; 
            switch(args.Length)
            {
                
                case 0:   // Nothing on command line, so 
    
                          // just start the screensaver.
    
                    argPrefix = "/s"; // Start in full sized screensaver 
    
                                      // mode by default.
    
                    argHandle = 0;    // No preview window to watch, 
    
                                      // therefore no handle.
    
                    break;
                case 1:   // One argument. Usually means a config command.
    
                    curArg = args[0];
                    argPrefix = curArg.Substring(0,2); // Get the prefix.
    
                    // Drop the slash /? part.
    
                    curArg = curArg.Replace(argPrefix,""); 
                    curArg = curArg.Trim(SpacesOrColons); // Remove trash.
    
                    // if nothing more return 0. else handle.
    
                    argHandle = curArg == "" ? 0 : int.Parse(curArg); 
                    break;
                case 2:   // If two arguments, it is probably a mini-preview.
    
                    argPrefix = args[0].Substring(0,2); // Get the prefix.
    
                    // Return the handle as an int.
    
                    argHandle = int.Parse(args[1].ToString()); 
                    break;
                default:   // If we don't put this here, compiler has a 
    
                           // fit about unassigned variables.
    
                    argHandle = 0;
                    argPrefix = ""; 
                    break;
            } // End of switch 
    
        } // End of method.

    This is the starting point for the program. It gets or creates the configuration info on a read attempt, from swarm.scr.config.xml and contains a case statement that handles the various command line states and requests.

    static void Main(string[] args) {
    
        // Variables to hold configuration and 
    
        // parsed command line results.
    
        int baseVelocity;
        int beeCount; // Number of bees per swarm.
    
        // Number of seconds a swarm remains a 
    
        // particular color.
    
        int colorCycleSeconds; 
        // if true, overrides above and swarm is 
    
        // multi-colored bees.
    
        bool glitterOn; 
        // Number of swarms on each screen.
    
        int swarmsPerScreen; 
        // /s for full screen mode, /p for preview 
    
        // mode, /c for config mode.
    
        string argPrefix; 
        int argHandle; // Will be 0 if no number found.
    
        XmlConfigSaver xm = 
              new CommonCode.XmlConfig.XmlConfigSaver();
        // read xml config ini file, if missing, create 
    
        // and return default values.
    
        xm.readConfigXml(out baseVelocity, out beeCount, 
                   out colorCycleSeconds, out glitterOn, 
                   out swarmsPerScreen);
        
        // We are done with the xml config class for now.
    
        xm = null; 
        if (args.Length > 2) 
        {
            MessageBox.Show("Too many arguments " + 
                                 "on the command line.");
            return;
        }
        // This is a routine to get the ArgPrefix and 
    
        // an int for the window
    
        // handle or zero if there is no handle.
    
        ParseArgsToPrefixAndArgInt(args, out argPrefix, 
                                          out argHandle);
        switch (argPrefix)
        { // A full screen preview is desired. 
    
            case "/p": 
            // No handle. Full preview desired.
    
            if (argHandle == 0) goto case "/s";  
            else
            {
                // Create a mini preview.
    
                MiniPreview mpTemp = new MiniPreview(); 
                mpTemp.DoMiniPreview(argHandle, baseVelocity, 
                         beeCount, colorCycleSeconds,glitterOn);
                // This does not execute until preview is closed.
    
                mpTemp = null; 
            }
            // break out of the main loop and exit 
    
            // program. End of MiniPreview section.
    
            break;
    1. The Display Properties dialog box would appear to be invisible for a number of seconds before a call to IsWindowVisible API would return true. I had to write in a loop that would wait up to 30 seconds for the window to show. I could probably get by with only 4 seconds, but it doesn't matter, I don't have to wait too long. Here is the code for solving that problem. As in the CONFIG form, put this code in your .cs file that contains your static void Main(String[] args) method call. In my case, my class is EntryPoint in EntryPoint.cs. Here is the code below, the code addressing the solution to this is encased in Asterisks. Look for the italics below in the next code section.

      Here is the code within the MiniPreview class ...

      using System;
      using CommonCode; // Useful commonly used 
      
                        // functions such as API calls.
      
      using System.Windows.Forms; // So we can show 
      
                                  // a messagebox.
      
      using System.Drawing;
      using Insects; // My drawing class.
      
      using System.Threading; // To slow the preview 
      
                              // mode to normal speed.
      
      
      namespace SwarmScreenSaver
      {
           public class MiniPreview
           {
               public void DoMiniPreview(int argHandle, 
                   int baseVelocity, int beeCount,
                   int colorCycleSeconds, bool glitterOn)
               {
                  // Pointer to windows Display Properties window.
      
                       IntPtr ParentWindowHandle = new IntPtr(0);
                  
                  // Get the pointer to Windows Display 
      
                  // Properties dialog box.
      
                    ParentWindowHandle = (IntPtr) argHandle;
                    RECT rect = new RECT();
                  
                  /* The Using construct makes sure all 
                  resources are disposed if errors. */
                  // This is the graphics obect for the 
      
                  // mini-preview window from the OS.
      
                  
                     using(Graphics PreviewGraphic = 
                          Graphics.FromHwnd(ParentWindowHandle))
                     {
                          CommonFunctions cf = new CommonFunctions();
                          // Get the dimensions and location 
      
                          // of the preview window.
      
                          cf.GetClientRectApi(ParentWindowHandle, 
                                                        ref rect);
                          // We need to wait for the preview 
      
                          // window to return visible.
      
                          // The parent window is never ready when 
      
                          // preview is chosen
      
                          // before we try to get the visible state for 
      
                          // the first time or so.
      
                          // So we must wait. I limit my waiting 
      
                          // to 30 seconds.
      
                         
                          
                          DateTime dt30Seconds = 
                                  DateTime.Now.AddSeconds(30);
                          while (cf.IsWindowVisibleApi(
                                          ParentWindowHandle) == false)
                          {
                               // If times out, exit program.
      
                               if (DateTime.Now > dt30Seconds) return; 
                               // Respond to windows events.
      
                               Application.DoEvents(); 
                          }
                      
                          // Create a bitmap for double buffering.
      
                          Bitmap OffScreenBitmap = 
                              new Bitmap(rect.right - rect.left,
                              rect.bottom - rect.top, PreviewGraphic);
                      
                          // Create a Graphics object 
      
                          // for OffScreenBitmap.
      
                          Graphics OffScreenBitmapGraphic = 
                                  Graphics.FromImage(OffScreenBitmap);
                      
                          /* Insects is a class that contains 
                          an instance of a Wasp,
                          a Swarm and routines for drawing 
                          to the output graphic. */
                          clsInsects insects = 
                                 new clsInsects(baseVelocity, 
                                 beeCount, colorCycleSeconds,glitterOn);
                          // Do some intialization, that was not 
      
                          // allowed in the constructor.
      
                          // The 3 means the wasp can't get closer 
      
                          // to the edge than 3 pixels.
      
                          insects.initSwarm(3, rect.right - rect.left, 
                                                 rect.bottom - rect.top);
                      
                          // Now that the window is visible ...
      
                          while (cf.IsWindowVisibleApi(
                                               ParentWindowHandle) == true)
                          {
                              // Since the drawing routine 
      
                              // expects a black background.
      
                              OffScreenBitmapGraphic.Clear(Color.Black);
                              // Execute an iteration of drawing the 
      
                              // wasp and a swarm of bees.
      
                              insects.DrawWaspThenSwarm(
                                                OffScreenBitmapGraphic);
                              // Slow down the mini-preview.
      
                              Thread.Sleep(50); 
                              try
                              {
                                // Draw the image created.
      
                                PreviewGraphic.DrawImage(OffScreenBitmap,
                                               0,0,OffScreenBitmap.Width,
                                                 OffScreenBitmap.Height);
                              }
                              // the most likely reason we get an 
      
                              // exception here is because
      
                              catch 
                              {   
                                  // the user hits cancel button while 
      
                                  // drawing to mini-preview.
      
                                    break; // Either way we must get out 
      
                                           // of the program.
      
                              }
                          
                              // We don't need to clear the 
      
                              // bitmap, because 
      
                              // windows does this for us here.
      
                              Application.DoEvents(); // Wait for events 
      
                                                      // to be processed.
      
                          } // End of while.
      
                          // CLOSING DOWN preview.
      
                          insects.StopNow = true;
                          cf = null;
                          OffScreenBitmap.Dispose();
                           OffScreenBitmapGraphic.Dispose();
                           // We are done, trash this.
      
                           PreviewGraphic.Dispose(); 
                     } // End of using statement.
      
                } // End of while preview window visible.
      
           } // End of definition of MiniPreview.
      
      } // End of namespace SwarmSaver for MiniPreview class.

      Here is the code for handling the full screen screensaver mode in EntryPoint ...

      case "/s": // Start screensaver.
      
              ScreenSaver screenSaver = new ScreenSaver();
              screenSaver.RunMeTillShutdown( baseVelocity, 
                              beeCount, colorCycleSeconds, 
                              glitterOn, swarmsPerScreen);
              screenSaver = null; // This doesn't execute 
      
                                  // until shutdown is done. 
      
              break; // Exit program we are done.
      
              default: // If something strange is 
      
                       // passed in, just ignore and exit.
      
              break;
          } // Switch end.
      
      } // End of try statement.

      Now, I just use this catch below to display any unhandled exceptions:

      catch(System.Exception e)
      {
           MessageBox.Show("ScreenSaver:" + e.Message + 
                           " Src:" + e.Source.ToString());
      }

      Now here is the code within class ScreenSaver that handles the full screen mode. Pay special attention to the areas in bold. I will be addressing these at the end of the article under updates:

      using System;
      using System.Windows.Forms;
      using System.Diagnostics; // For logging to error log.
      
      
      namespace SwarmScreenSaver
      {
           // Declare the DonePaitingDelegate 
      
           // signature. Used by the screensaver
      
           // forms to notify this class that 
      
           // painting is done.
      
          
           public delegate void DonePaintingDelegate(int screenNumber);
          
           // Declares the signature of the Shutdown Delegate.
      
           // Used to create actual delegate instances later.
      
          
           // Called when saver shutting down.
      
           public delegate void ShutDownDelegate(); 
          
           public class ScreenSaver
           { 
               // Actual delegate instances.
      
               
               // Assigned to a method later. 
      
               public DonePaintingDelegate DonePaintingDel; 
               public ShutDownDelegate ShutDownDel;
              
               int screenCount; // We exit when this becomes = 0. 
      
                                // We assign it later.
      
              
               // When true, we shut down all 
      
               // screens and set screenCount = 0;
      
               
               // Set to true by a
      
               // delegate call from a form. 
      
               bool shuttingDown = false; 
              
               // This will contain one ScreenSaverForm 
      
               // per active screen.
      
               ScreenSaverForm [] sf;
              // An array of event objects, these are 
      
              // special objects that provide
      
              // cross thread signaling. The while loop 
      
              // below goes to sleep until
      
              // the one of the events in this array is 
      
              // set. Instead of gobbling up
      
              // cpu cycles like a hungry animal by 
      
              // loop while waiting for an event
      
              // to occur, these nice manualEvents provide 
      
              // a way to tell the loop thread
      
              // to stop looping and wait 
      
              // till there is an event to respond to!
      
              // The last element in this array is for 
      
              // signaling that a shutdown event
      
              // has occurred, and the main thread 
      
              // wakes up and the program exits. 
      
              // The other elements are objects for 
      
              // signaling that a form is finished
      
              // drawing and is ready to display.
      
              ManualResetEvent[] manualEvents;
              
              // This constructor creates the 
      
              // manualEvents array instances, sets the 
      
              // length to hold the paint events for 
      
              // the screens plus a shutdown event.
      
              // Finally, set all events has being uncalled.
      
              public ScreenSaver()
               {
                   manualEvents = new 
                        ManualResetEvent[Screen.AllScreens.Length+1];
                   for (int i = 0; i <= Screen.AllScreens.Length; i++)
                   {
                       manualEvents[i] = new ManualResetEvent(false);
                   }
               }
              
               // This is called by ScreenSaverForm 
      
               // via the delegate when done painting.
      
               
               // Keep delegates simple.
      
               public void DonePainting(int screenNumber) 
               {
                   lock(ScreenDonePainting)
                   {
                       ScreenDonePainting[screenNumber] = true;
                       // Signal main thread to wake up 
      
                       // and process this event.
      
                       manualEvents[screenNumber].Set(); 
                   }
               }
              
               // This is called by ScreenSaverForm 
      
               // when it's shutting down.
      
               public void ShutDown()
               {
                  // Lock needs a reference object. ;-/
      
                  lock(manualEvents) 
                  {
                    shuttingDown = true;
                    // Last element is for ShutDown 
      
                    // event so signal the shut down.
      
                 manualEvents[Screen.AllScreens.Length].Set();
                  }
               }
               
               public void RunMeTillShutdown(int baseVelocity, 
                          int beeCount, int colorCycleSeconds, 
                          bool glitterOn, int swarmsPerScreen)
               {
                  DonePaintingDel = 
                        new DonePaintingDelegate(DonePainting);
                  ShutDownDel = new ShutDownDelegate(ShutDown);
                  // Get count of
      
                  // screens on system.
      
                  screenCount = Screen.AllScreens.Length;  
                                                          
                  
                  // One form for each screen found just 
      
                  // to get the array to create instances. 
      
                  // We need to create instances to fill out 
      
                  // the array before we assign the.
      
                  // ScreenSaverForm instances we will use 
      
                  // otherwise when I try to assign using
      
                  // the ScreenSaverForm constructors, I get 
      
                  // assignment to null object errors.
      
                  
                  sf = new ScreenSaverForm[screenCount]; 
                  
                  int i = 0; // Loop through the array assigning 
      
                             // to the array the instances.
      
                  for (i = 0; i < screenCount;i++)
                  {   
                      // Replace the forms in the array 
      
                      // with the Real ScreenSaverForms.
      
                      // Send the screenNo and config values 
      
                      // to the ScreenSaverForm constructor.
      
                      sf[i] = new ScreenSaverForm(i,DonePaintingDel, 
                                          ShutDownDel, baseVelocity, 
                                          beeCount, colorCycleSeconds, 
                                          glitterOn, swarmsPerScreen); 
                      sf[i].Show(); // This just shows the form but it 
      
                                    // doesn't start the drawing.
      
                      // This evokes the paint event which
      
                      // starts the form working.
      
                      sf[i].Refresh();  
                                       
                  } 
                  // When all forms shut down,
      
                  //we will exit program.
      
                  while (screenCount > 0)  
                                             
                  { 
                      // Don't waste CPU cycles, looping, 
      
                      // instead go to sleep
      
                      // until any event in the array is 
      
                      // set, then process it.
      
                      
                      // Thread sleeps while waiting.
      
                      WaitHandle.WaitAny(manualEvents); 
                      // If a screen is shut down, shut down the rest.
      
                      if (shuttingDown == true) 
                      {
                          // Note that if we are shutting down, we don't 
      
                          // need to reset manualEvents.
      
                          for (i = 0; i < Screen.AllScreens.Length; i++)
                          {
                              // Don't try to shut down a form that 
      
                              // is already down.
      
                              if ((sf[i].Visible == true)) 
                              {
                                  // Calls on the form to close itself.
      
                                  sf[i].CloseMe(); 
                                  // Now this while loop will exit.
      
                                  screenCount = 0; 
                                  Application.DoEvents();
                              }
                          }
                          // Loop on up and then loop will exit.
      
                          continue; 
                      } // End of if shutting down.
      
                      else // Not shutting down so display and 
      
                           // paint any forms needing painting.
      
                      {
                      try 
                        { 
                          // For each screen check to see if it is 
      
                          // done painting and if so, display by
      
                          // calling Application.DoEvents() then 
      
                          // invoke another paint event.
      
                          for (i = 0; i < Screen.AllScreens.Length; i++)
                          {
                             // Does screen need 
      
                             // showing and a repaint?
      
                             if (ScreenDonePainting[i]) 
                             {
                                // Set the event as having been 
      
                                // processed so we will wait again.
      
                                                         
                                
                                //instead of wasting
      
                                //CPU cycles looping.
      
                                manualEvents[i].Reset();
                                Application.DoEvents();
                                sf[i].Refresh(); // Invoke a paint event.
      
                             }// End if.
      
                          } // End for.
      
                        } // End try
      
                      catch( InvalidOperationException e)
                        {
                           EventLog.WriteEntry("SwarmScreenSaver" + 
                                               e.Source, e.Message, 
                                               EventLogEntryType.Error);
                        } // End catch.
      
                      } // End of else not shutting down.
      
                  } // End of While.
      
              } // End of RunTillShutdown.
      
          } // End of class ScreenSaver definition.
      
      } // End of namespace.
  4. The random color chosen was often too dark to see against a black background.

    The code for adjusting the brightness of the color is in the CommonFunctions class in the StructsAndFunctions.cs file. It is huge and this article is getting too big and unwieldy as it is, with me jumping back and forth, when I remember or if I remember I've forgotten something vital!

  5. Stutter and flicker. I solved this via double-buffering and moving the drawing calls to the paint event.

    Remember that I have my drawing routines in the ScreenSaverForm_Paint event, but I invoke this from outside the form in a while loop in ScreenSaver.RunTillShutdown() method calling ScreenSaverForm.Refresh() and then Application.DoEvents() to get the results to show on the screens when one of the elements in manualEvents is set, indicating that the form is done painting.

    The original code had a lot of shorts to represent points on the screen, and this makes sense, so I tried to save memory by using shorts, but all the random number generation routines available to me, returned ints, and all the explicit conversion I had to do, slowed the program down, so I got rid of the shorts and made everything ints.

    Here is the relevant snippets in the ScreenSaverForm.cs ....

    This goes inside the declaration of the class but before the constructor ... Contains the mouse position. If it moves, all instances of ScreenSaverForm are closed and the app is ended.

    private Point MouseXY; 
    private int ScreenNumber; 
    // Will hold the index to the current 
    
    // screen in AllScreens array.
    
    
    private int SwarmCount; // Number of swarms 
    
                            // desired per screen.
    
    clsInsects[] insects; // One insects instance per 
    
                          // on-screen swarm on this form.
    
    // Declare the delegate variables to hold 
    
    // the methods to call via delegates.
    
    DonePaintingDelegate DonePaintingDel;
    ShutDownDelegate ShuttingDownDel; 
    // stopNow starts out as false, when it 
    
    // becomes true due to mouse or key 
    
    // actions, the form closes. 
    
    private bool stopNow = false;

    The paint event calls the drawing routines as long as stopNow is false and then calls Invalidate which in turn evokes the paint event again until time to quit. No timer is needed.

    Constructor for this form, initializes any components:

    public ScreenSaverForm(int scrn, 
                 int baseVelocity, int beeCount,
                 int colorCycleSeconds, 
                 bool glitterOn, int swarmCount)
    {
        InitializeComponent();
        SwarmCount = swarmCount;
        DonePaintingDel = donePaintingDel;
        ShuttingDownDel = shutDownDel;
        // Size an array of insect 
    
        // classes to draw a swarm. 
    
        insects = new clsInsects [SwarmCount]; 
        // Create an insects instance for 
    
        // each swarm user wants on the screen. 
    
        // Initialize the array of insects class 
    
        for (int i = 0; i < SwarmCount; i++) 
            insects[i] = new clsInsects(baseVelocity, beeCount, 
               // Save the Screen Number this
    
               // form addresses.
    
               colorCycleSeconds, glitterOn);  
                                               
            ScreenNumber = scrn;

    The stuff below tells the form that it will be doing all drawing in the paint event and to use it's own double-buffering routines built in, UserPaint says the FORM code will do the repainting rather than having the OS decide when to do so and Opaque says we will control when to redraw the background which since it is always set to black with double-buffering, this never needs to be done. This is quite an interesting piece of code. By specifying all these variables including DoubleBuffer we are telling the form to use its own special Win32 low level bitmap routines that are extremely fast. There is a side effect to this though. This special buffer used by the form has all bits set to 0 which is a black background and contains nothing from any previous calls to the drawing routines. This has the side-effect of causing any drawing that happened in the previous call to the drawing routines to be done away with. Consequently, you will notice that a lot of code involved in erasing previous positions is commented out because there is nothing to erase. All your drawing routines need to do is draw and don't worry about trying to erase stuff like lines by drawing a black line over it, it is all taken care of for You. The caveat is that I had an option to cause the swarm objects to leave a trail. This was interesting, but the speed increase I got over using the form's built in superior double buffering was just too good to pass up despite the side-effect. I might figure out a way to fix this anyway. I'll look into this later.

    In the code below, all the enums below represent ONE but in a 32 bit int. The enums are OR'd with the others, and then finally with the value true, which has the net effect of setting all the bits that these values represent to 1.

      SetStyle // Had to remove form's double-buffering. It's too buggy!
    
      (
       ControlStyles.AllPaintingInWmPaint |   
       ControlStyles.UserPaint |
       ControlStyles.DoubleBuffer |
       ControlStyles.Opaque, true); } } // End of constructor.

    Here is the load event handler, it initializes my buffers and the Insects crawling class and then calls invalidate, which starts the paint event, that does the drawing then calls itself. What it calls itself, I do not know. Hehe, sorry, I love silly humor.

    private void ScreenSaverForm_Load(object sender, System.EventArgs e)
    {
      // Set the form to the size of this screen.
    
      Bounds = Screen.AllScreens[ScreenNumber].Bounds;
      Cursor.Hide();
      TopMost = true;
      for (int i = 0; i < SwarmCount; i++) // Initialize all desired swarms.
    
        insects[i].initSwarm( 50, Bounds.Width, Bounds.Height);
    }

    Notice the disposal of our Graphic and Bitmap objects below ...

    // We must dispose our bitmaps and graphics objects on exiting.
    
    protected override void Dispose( bool disposing )
    {
       if( disposing ) // No componants to dispose of.
    
       {
          if (components != null) 
          {
            components.Dispose();
          }
       }
       // ScreenGraphic.Dispose(); // not needed.
    
       // ScreenBitmap.Dispose(); // not needed.
    
       base.Dispose( disposing );
    }

    The ScreenSaver.RunMeTillShutdown method uses a while loop to call Application.DoEvents() which causes the screens to show on the monitors when a ScreenSaverForm is done painting then it calls ScreenSaverForm.Refresh() to evoke a paint event. Only refresh works on all screens on a system.

    When the user moved, clicks a mouse or presses a button, the corresponding event calls.

    ShuttingDownDel() is the delegate that sets ScreenSaver.ShuttingDown = true. Then the ScreenSaver.RunMeTillShutDown calls the method below to close all the forms.

    Here is the CloseMe method of the ScreenSaverForm ...

    public void CloseMe()
    {
     stopNow = true;
     for (int i = 0; i < SwarmCount; i++)
         insects[i].stopNow = true; // Tell drawing processes to stop.
    
     Close();
    }

    There are standard mouse and keyboard routines that set calls CloseMe() if the user moves or clicks the mouse or presses a key.

    Finally, we have the paint event itself ....

    protected override void ScreenSaverForm_OnPaint(PaintEventArgs e)
    {
      // Are we staying open?
    
      if (stopNow == false) // Don't paint if we need to exit.
    
      {
        try
        { // Draw each swarm in turn while stopNow == false.
    
          for (int i = 0;( i < SwarmCount) && (stopNow == false); i++)
          {
            insects[i].DrawWaspThenSwarm(e.Graphics);
            SuspendLayout();
            e.Graphics.Flush(
                System.Drawing.Drawing2D.FlushIntention.Flush);
            ResumeLayout(); 
          } 
        } 
        catch (InvalidOperationException ev) 
        // This wont catch the error caused by 
    
        // the form's native double-buffering. 
    
        // I don't know why. 
    
        // The drawing is done very fast because 
    
        // it's off screen and doesn't require 
    
        // rendering every change. This is why 
    
        // double-buffering is flicker free. 
    
        { 
          EventLog.WriteEntry("SwarmScreenSaver" + 
                            ev.Source,ev.Message, 
                            EventLogEntryType.Error); 
          // Ignore this error if it occurs but log it. 
    
        } // End catch. 
    
      } // End of If stopNow == false. 
    
      DonePaintingDel(ScreenNumber); 
      // Call delegate to inform. 
    
      // ScreenSaver that this form is 
    
      // ready to show and refresh(). 
    
    } // End of Paint event handler.

Well, that's about it. There are some try catch stuff here that I did not put in the snippets.

There are some Lock statements in the Insects classes. One locks the e.Graphics object passed in as well as drawing functions from being accessed by other objects until the instance is finished with drawing.

There is a lot more code in one more module, that would be clsInsects.cs but if I were to comment on that, this article would be twice the size!

Points of interest

Don't forget to look at the HTML with your browser. This is found in CodeCommentReport\Solution_SwarmScreenSaver.HTM

History

Discovered that running instances of ScreenSaverForm on the main thread via the invalidate and having the drawing code in the paint event which in turn used to call Invalidate() to draw the next frame was the best way to go than on worker threads, or timer events. Even better yet is the messaging and the WaitHandle.WaitForAny() method spoken about below.

  • I found that I did not have to clear the source bitmap every time I finished drawing if I did not use anti-aliasing and this was before I did double buffering! In order for my code to erase a line, I would actually draw a black line over the old line. Well, it seems that the GDI+ DrawImage is smart enough not to count a line that is of the same color as the background as something to copy, but only draws what is visible and skips all else. This becomes evident if you choose a different color for the erasing, the drawing slows down.
  • I found that the form's double-buffering stuff absolutely screams!! The program now draws faster than ever! This auto-double-buffering does not carry the contents of the screen across calls to paint events so you do not have to draw over old stuff to simulate erasing if you are using this type of double-buffering.
  • I could wrap imports from DLL's into wrappers! This sure makes things easy, to have all your special code in one place. By special is the code you find yourself needing to execute a lot but isn't part of a mission critical section of code.

I moved the API calls into the CommonFunctions class and wrote wrappers for these functions because I could not call these directly via an instance of the class. The wrapper is the same name as the API call but with an API at the end so ...

Code like ...

if (IsWindowVisible(ParentWindow) == true) becomes ....
if (cf.IsWindowVisibleApi(ParentWindow) == true ...

Got this reply quoted in italics below from rgesswien on an exception in checking processes. Use at your own risk, however, I'm going to remove the check for multiple instances because as far as I can tell, I don't even need it. I've seen a few screensavers with it and a few without it, but in my testing I found that my program was terminated by Windows anyway without me needing that code.

FWIW: If you get an InvalidOperationException on GetCurrentProcess in the function IsInstanceAlreadyRunning() here is the fix. (At least for me.) Regedit to HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\PerfProc\Performance Change the value of "Disable Performance Counters" from 1 to 0. (This error is all over the web but I could only find one thread in a newsgroup that explained how to fix the problem.)

I no longer use IsInstanceAlreadyRunning() because my OS terminates the program when going between CONFIG, mini-preview and full-preview. I also got a note about the CONFIG screen not being usable by the user entering /c, but assuming it was brought up by the Display Properties window. I will fix this.

Major changes! I've at the advice of an authority on GDI, I have moved my paint routines except the one for the preview, into a paint event in ScreensaverForm.

I read that it was a very bad idea to do drawing to the screen outside of an event, so there is no more SaverLoop. The drawing stuff is initialized in the load event, but all drawing occurs within the paint cycle.

6/2/2005 Issues ...

I have now included the saving of CONFIG info to an XML file.

  1. I finally was able to get my system's two monitors working in duel view mode. I have found that I used to get the same issues as everyone else who has tried the multiple monitor approach in that one monitor gets the screensaving but the second monitor, that I started the saver on gets no processing for the screensaver. Further comments below regarding multiple screen screensavers.
  2. I looked at all the screensavers on CodeProject, and according to the replies, every one of them has the same issue. Given that cloning and spanning seem to work, but duel view has the issue, and only one monitor is useful in this mode or assign apps to run on a particular monitor. I have solved this issue as mentioned above.

Latest change as of 06/04/2005

Configuration is now saved as an XML file and it's a lot simpler. Your CONFIG file will be created if it doesn't' exist, and will be the name of the executing file + .config.xml. Example. Swarm.scr creates Swarm.scr.config.xml. The changes to the code were minor, so I won't change the snippets above, because the concept is the same except minor changes and re-doing the snippets is a nightmare so I've just uploaded a new source file zip. If you wish, just look at the code and code comments web pages included to see the changes. Notice the extra file XmlSwarmScreenSaverConfig.cs.

I have also added a call to a parsing routine in the entrypoint.cs' case statement to more clearly parse the command line, so you can better see what is happening.

06/07/2005

Just cleaned up some horrible grammar in the article and references to SaverLoop that no longer exists. Isn't it funny how trying to get rid of spelling and grammar errors is like trying to vacuum up all the sand out of a shag carpet that has been washed up on a beach?

06/10/2005 - New things added ...

  1. Added some code to the insects class which does my drawing, so that it will stop immediately if the form it is drawing on is in the process of closing. I hope this might solve the un-catchable InvalidOperationException I was getting.
  2. Added the ability of the saver to handle multiple swarms and wasp groups on the same screen!! It really looks neat, having several swarms dancing about. I found that the swarms would start out in about the same area and then diverge, with some swarms going off on their own, and then strangely, all the swarms moving in sync as in a dance! It was very nice to watch! I accomplished this by changing the single instance of the insects class to an array. I added a configuration option where you can specify between 1 and 100 swarms, so if you feel like going nuts, go for it, give that computer a REAL stress test!
  3. Added code to the XML CONFIG saver reading method, so that if it discovers that a field is missing, in this case, the new SwarmsPerScreen field or is corrupted in some other way, it self recovers by recreating a new configuration file with default values and so no worries about forgetting to remove the old CONFIG file.
  4. Discovered that the insects class was using a STATIC instance of the CommonFunctions class which is used to derive random numbers, color adjustment and other functions, and this could very well explain why it failed to run on multiple monitors. Later, I may uncomment out the multiple monitor code and give it a try, but not just yet. Obviously, if one is having multiple objects addressing a single instance, this can be a bit of a problem.

Enjoy!, I really like these dancing swarms!!

6/11/2005

Hopefully fixed issue with object in use exception. It seems the issue was caused by my process trying to access the Graphics object in the paint event before the screen was done painting. I could have put in some sort of waiting between calls, but I wanted to have the thing run as fast as possible, so I simply put a lock(e.Graphics) statement on the Graphics object passed in the paint event argument e. This seems to have cleared it up, but as with all intermittent bugs it has a life of its own, thus proving the truth of my suspicion that C# is an upgrade of Murphy's C++!

I have just succeeded in getting this screensaver to work on multiple monitors in duel mode on a single system at the same time!!

6/15/2005

In an ongoing effort to solve the InvalidOperationException event, "Object in use elsewhere" issue, I have utilized graphics paths instead of drawing one line at a time, I build the graphics and then draw the graphics path. I use the Graphics object's CloseFigure() method which starts a new subfigure which causes each bee to be considered as a separate subfigure otherwise there would be a line connecting the bees together. The effect was like a wad of paper in model form. It was interesting to look at.

I use delegates to inform the ScreenSaver class when a form is shutting down and another to inform when it's done painting, and set a boolean array so it knows which forms are ready to draw. The boolean array isn't necessary because I discovered that if one form needs a refresh, the other did too by the time it got to the refresh.

I find it interesting that the Application.DoEvents() method and program events are tied together, in other words, there is no way to control what gets stopped. If I don't call DoEvents regularly, the form events stop. I can't just arbitrarily call Application.Events() without the forms getting a repaint before it's ready. The paint event draws the graphics, but it will never appear on the monitor until Application.DoEvents() is called which of course effect all screens!

I have separated the mini-preview and FullScreen Preview/Screensaver code into MiniPreview.cs and ScreenSaver.cs.

Update late 06/18/2005

Found a method of eliminating wasting CPU cycles just running a loop waiting for an event instead of waiting for the event gracefully. One thing I know about Windows, is that it is message driven, and when I started implementing the event driven ScreenSaver class, I looked for a way to wait for an event instead of wasting CPU cycles.

I found a method called WaitHandle.WaitForAny(Array[]). This method takes an array of ManualResetEvents or AutoResetEvents and can be used to stop the current thread wait till any of the event instances in the array is set via manualEvent.Set();. By using this one statement and then using a manualEvent.Reset() when a ScreenSaverForm.Refresh() was about to be called, I was able to cause the thread to sleep and wait for the events instead of gobbling CPU cycles like a school of hungry piranha and overburdening the computer.

By using the WaitForAny method as mentioned above, I was able to eliminate a source of much un-necessary burdening on the system. The code changes I did are in bold in the snippets above.

Update for 8:PM PST 06/19/2005

Due to the tenacity of the InvalidOperationEvent issue, I have added a CONFIG option to toggle the style based double buffering in favor of my manual double-buffering code, which has never generated this bug, but is much slower. Caveat: For some reason, when I execute the program within the development environment and a new option is found that is not in the CONFIG file the IDE hangs. If I run the app outside the development environment or delete the old CONFIG file, it does not hang.

I've uploaded the new source code. I have left the snippets the same because there were very few changes that really mattered.

I think I have found what is the cause of the InvalidOperationException event...

The manual double buffering never crashes. In this kind of buffering there is no double buffer between the Graphics object, and the display, and so there is no guess work about when this buffer is written to the screen because I get complete control over this via the code.

The control's built in double buffering is automatic in that the control has a back buffer that is written at a time decided by the runtime unless you call the flush method mentioned earlier. In this scenario the control has to guess when it's time to copy the back buffer to the display and also guess when it's time to write the buffer, and it tends to choose poorly. The control under double-buffering via the setstyle was deciding to copy the buffer to the screen and dispose it just as my program tries to write to this buffer!

This would explain the delay time where I have seven swarms on the screen, but one only appears every 4 seconds, till they are all on-screen.

Updates 06/20/2005

Looks like, the above theory is possibly true. I got a reply from Bob Powell stating that he suspected there was some sort of bug with auto-double buffering, but it's not confirmed, except that the horror bug has been solved. It's been running for about 15 hours without crashing so the issue is solved after I did the change below ...

Changes ...

I removed a lot of whining and commentary about the error I spoke of. I allowed my judgment to be affected by my frustration and ranted about it. While I'm sure, it was interesting to me, it was probably boring to you. I apologize.

Important change 06/24/2005

I have found the cause of the InvalidOperationException error. I was drawing outside of the buffer, forcing it to dispose and resize! Yep, it was my fault! I had assumed that the Graphics object I get in paint event arguments would have its clipping region set to the size of the form. I was wrong. By default it appears to be infinite. When drawing is done with double-buffering and this drawing happens out of bounds, the backbuffer in auto-double buffering will destroy itself and resize. This was causing the exception. I tried setting it via the bounds of the form but when the computer is running in multi-screen, the bounds and the clip bounds in the Paint event args for the secondary monitor have an offset in the X and Y values. Here is what I have done about this ..

  • Although using Graphics.SetClip(e.ClipRectangle) seems to work, I do not trust it. Instead I put the code that was originally in the swarm class that will skip drawing of any graphic unless all of it is completely in the screen dimensions as specified in width and height and that no part of the graphic is in a negative pixel location. I might have to do some adjustments aside from this due to pixel offset, but at least I'm on the right track.
  • I removed the flush statement as I don't need it now.
  • Important - I found out that when a form is locked and I try to resize it in code, it causes an endless stream of paint commands trying and failing to resize the form. My code would ignore these but they would be sent again and again until I unlocked the form.
  • Added several tests and a paintStatus variable to track the status of all paint events done. My code now resides in the paint event instead of OnPaint.
  • Setting paintEventArgs's pev.Graphics.SetClip(Bounds) was preventing the secondary monitor from drawing. This does work here but I don't trust it ... pev.Graphics.SetClip(pev.ClipRectangle);

I may get to changing the snippets later, but the source code is something you will want to re-download if you are using this for your own savers.

Well, that's it ...

Enjoy!

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here