Create a Screen Saver Using C# – Part 1

Operating System, Windows Forms

In this two-part article series I am going to show you how to create a Windows screen saver using C#.

A screen saver is not much more than a normal Windows Form with no border and some logic to display something on the form. For it to be a Windows screen saver the compiled application must end with the .scr extension and it must also accept command line parameters, since Windows sends a parameter telling the screen saver to either preview, run, or display its settings. And that’s basically it – there is really not much to creating a screen saver, but I will explain it all in detail throughout this article series anyway. But first let’s design our screen saver.

Designing the Screen Saver

Our screen saver will be displaying random shapes with random sizes and random colours and random levels of transparency (that’s a lot of randomness… :)). It will look something like this:

Screen Saver

Do you like it? If you do, continue reading because we are about to create this very screen saver. If you are in a hurry and don’t have time to read this article (or are too lazy to read :P) you can always just download it from the link at the end of this article series.

Screen Saver Settings

We are going to make this screen saver customizable so the user will be able to change the following options:

  1. Allowed shapes
    • All – draw all shapes
    • Circles – draw only circles
    • Ellipses – draw only ellipses
    • Rectangles – draw only rectangles
    • Squares – draw only squares
  2. Draw speed
    • Slow – draw a shape every 500ms
    • Normal – draw a shape every 200ms
    • Fast – draw a shape every 100ms
    • Custom – draw a shape according to the custom draw speed
  3. Custom draw speed: this will be a value set by the user in milliseconds (ms)
  4. Use transparency: this will indicate whether the shapes are drawn solid or else with a random transparency level.

The Settings Class

Now that we defined the settings for our screen saver we can create a class which represents them, and also which saves and loads them from disk. To save and load the settings we are going to use XML Serialization to serialize and deserialize the class. I am not going to go into the details of how XML Serialization works because I wrote two articles on that subject which you can find here Basic XML Serialization in C# and here XML Serialization of Collections.

To start off let’s create a class called Settings in our Windows Forms Application project. It contains all the details discussed above and it looks like this:

using System;
using System.IO;
using System.Xml.Serialization;
using System.Xml;
using System.Windows.Forms;

namespace ScreenSaver
{
    public class Settings
    {
        #region Enumerations

        public enum Shapes
        {
            Squares,
            Rectangles,
            Circles,
            Ellipses,
            All
        }

        public enum Speed
        {
            Slow,
            Normal,
            Fast,
            Custom
        }

        #endregion

        #region Private Members

        private Shapes allowedShapes;
        private Speed drawSpeed;
        private int customSpeed;
        private bool useTransparency;
        private string settingsPath = Application.UserAppDataPath + "\\screensaver.xml";

        #endregion

        #region Public Properties

        public Shapes AllowedShapes
        {
            get { return this.allowedShapes; }
            set { this.allowedShapes = value; }
        }

        public Speed DrawSpeed
        {
            get { return this.drawSpeed; }
            set { this.drawSpeed = value; }
        }

        public int CustomSpeed
        {
            get { return this.customSpeed; }
            set { this.customSpeed = value; }
        }

        public bool UseTransparency
        {
            get { return this.useTransparency; }
            set { this.useTransparency = value; }
        }

        #endregion

        #region Constructor

        public Settings()
        {
            this.allowedShapes = Shapes.All;
            this.customSpeed = 0;
            this.drawSpeed = Speed.Normal;
            this.useTransparency = true;
        }

        #endregion

        #region Methods

        public void LoadSettings()
        {
            try
            {
                // Create an instance of the Settings class
                Settings settings = new Settings();

                if (File.Exists(this.settingsPath))
                {
                    // Create an instance of System.Xml.Serialization.XmlSerializer
                    XmlSerializer serializer = new XmlSerializer(typeof(Settings));

                    // Create an instance of System.IO.StreamReader 
                    // to point to the settings file on disk
                    StreamReader textReader = new StreamReader(this.settingsPath);

                    // Create an instance of System.Xml.XmlTextReader
                    // to read from the StreamReader
                    XmlTextReader xmlReader = new XmlTextReader(textReader);

                    if (serializer.CanDeserialize(xmlReader))
                    {
                        // Assign the deserialized object to the new settings object
                        settings = ((Settings)serializer.Deserialize(xmlReader));

                        this.allowedShapes = settings.AllowedShapes;
                        this.drawSpeed = settings.DrawSpeed;
                        this.customSpeed = settings.CustomSpeed;
                        this.useTransparency = settings.UseTransparency;
                    }
                    else
                    {
                        // Save a new settings file
                        this.SaveSettings();
                    }

                    // Close the XmlTextReader
                    xmlReader.Close();
                    // Close the XmlTextReader
                    textReader.Close();
                }
                else
                {
                    // Save a new settings file
                    this.SaveSettings();
                }
            }
            catch (Exception ex)
            {
                MessageBox.Show(string.Format("Error retrieving deserialized settings! {0}", ex.Message), "Dave on C# Screen Saver", MessageBoxButtons.OK, MessageBoxIcon.Error);
            }
        }

        public void SaveSettings()
        {
            try
            {
                // Create an instance of the Settings class
                Settings settings = new Settings();

                settings.AllowedShapes = this.allowedShapes;
                settings.DrawSpeed = this.drawSpeed;

                if (settings.DrawSpeed == Settings.Speed.Custom)
                    settings.CustomSpeed = this.customSpeed;
                else
                    settings.CustomSpeed = 0;

                settings.UseTransparency = this.useTransparency;

                // Create an instance of System.Xml.Serialization.XmlSerializer
                XmlSerializer serializer = new XmlSerializer(settings.GetType());

                // Create an instance of System.IO.TextWriter
                // to save the serialized object to disk
                TextWriter textWriter = new StreamWriter(this.settingsPath);

                // Serialize the settings object
                serializer.Serialize(textWriter, settings);

                // Close the TextWriter
                textWriter.Close();
            }
            catch (Exception ex)
            {
                MessageBox.Show(string.Format("Error saving serialized settings! {0}", ex.Message), "Dave on C# Screen Saver", MessageBoxButtons.OK, MessageBoxIcon.Error);
            }
        }

        #endregion
    }
}

As you can see, the Settings class contains two enumerations, one for the shapes and one for the draw speed. The class also contains a public property for each of the requirements we discussed earlier. Towards the end of the class are two methods – LoadSettings and SaveSettings. These two methods take care of serializing and deserializing the settings.

Also note that the settings file is being saved to Application.UserAppDataPath + "\\screensaver.xml". The Application.UserAppDataPath property gets the path for the application data of a user, which in my case looks like this: C:\Users\David\AppData\Roaming\Dave on C-Sharp\Screensaver\1.0.0.0\.

This is what the serialized Settings class contains:

<?xml version="1.0" encoding="utf-8"?>
<Settings xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
  <AllowedShapes>All</AllowedShapes>
  <DrawSpeed>Normal</DrawSpeed>
  <CustomSpeed>0</CustomSpeed>
  <UseTransparency>true</UseTransparency>
</Settings>

Accessing the Settings

Now that we have a class for loading and saving the screen saver’s settings we need to create a way for the user to edit these settings. The best way to do this is by creating a form which displays the settings and allows the user to save any changes. Basically we need something that looks like this:

Settings Form

The code required for this form is really simple – all that needs to be done is the displaying of the loaded settings in the form’s controls, and the saving of the settings back to disk. We already created the load and save methods for reading and writing to the xml settings file, so now all we need to do is call those methods and set the form’s control values.

Below is the LoadSettings() method which uses the Settings class to populate the form’s controls.

private void LoadSettings()
{
    try
    {
        // Create an instance of the Settings class
        Settings settings = new Settings();

        // Load the settings
        settings.LoadSettings();

        // Set the selected item in the combo 
        switch (settings.AllowedShapes)
        {
            case Settings.Shapes.All:
                cboShape.SelectedItem = "All";
                break;
            case Settings.Shapes.Circles:
                cboShape.SelectedItem = "Circles";
                break;
            case Settings.Shapes.Ellipses:
                cboShape.SelectedItem = "Ellipses";
                break;
            case Settings.Shapes.Rectangles:
                cboShape.SelectedItem = "Rectangles";
                break;
            case Settings.Shapes.Squares:
                cboShape.SelectedItem = "Squares";
                break;
            default:
                cboShape.SelectedItem = "All";
                break;
        }

        // Set the selected item in the combo 
        switch (settings.DrawSpeed)
        {
            case Settings.Speed.Custom:
                cboSpeed.SelectedItem = "Custom";
                break;
            case Settings.Speed.Fast:
                cboSpeed.SelectedItem = "Fast";
                break;
            case Settings.Speed.Normal:
                cboSpeed.SelectedItem = "Normal";
                break;
            case Settings.Speed.Slow:
                cboSpeed.SelectedItem = "Slow";
                break;
            default:
                cboSpeed.SelectedItem = "Normal";
                break;
        }

        // Set the remaining settings
        mtxtCustomSpeed.Text = settings.CustomSpeed.ToString();
        chkUseTransparency.Checked = settings.UseTransparency;
    }
    catch (Exception ex)
    {
        MessageBox.Show(string.Format("Error loading settings! {0}", ex.Message), "Dave on C# Screen Saver", MessageBoxButtons.OK, MessageBoxIcon.Error);
    }
}

Next is the SaveSettings() method which also uses the Settings class, but this time to write the settings to disk.

private void SaveSettings()
{
    try
    {
        // Create an instance of the Settings class
        Settings settings = new Settings();

        // Set the enum according to the selected value in the combo
        switch (cboShape.SelectedItem.ToString().ToLower())
        {
            case "all":
                settings.AllowedShapes = Settings.Shapes.All;
                break;
            case "circles":
                settings.AllowedShapes = Settings.Shapes.Circles;
                break;
            case "ellipses":
                settings.AllowedShapes = Settings.Shapes.Ellipses;
                break;
            case "rectangles":
                settings.AllowedShapes = Settings.Shapes.Rectangles;
                break;
            case "squares":
                settings.AllowedShapes = Settings.Shapes.Squares;
                break;
            default:
                settings.AllowedShapes = Settings.Shapes.All;
                break;
        }

        // Set the enum according to the selected value in the combo
        switch (cboSpeed.SelectedItem.ToString().ToLower())
        {
            case "custom":
                settings.DrawSpeed = Settings.Speed.Custom;
                break;
            case "fast":
                settings.DrawSpeed = Settings.Speed.Fast;
                break;
            case "normal":
                settings.DrawSpeed = Settings.Speed.Normal;
                break;
            case "slow":
                settings.DrawSpeed = Settings.Speed.Slow;
                break;
            default:
                settings.DrawSpeed = Settings.Speed.Normal;
                break;
        }

        // If the selecte draw speed is set to "Custom" then
        // save the custom speed else save 0.
        if (settings.DrawSpeed == Settings.Speed.Custom)
        {
            int customSpeed = 0;
            int.TryParse(mtxtCustomSpeed.Text, out customSpeed);
            settings.CustomSpeed = customSpeed;
        }
        else
        {
            settings.CustomSpeed = 0;
        }

        settings.UseTransparency = chkUseTransparency.Checked;

        // Save the settings
        settings.SaveSettings();
    }
    catch (Exception ex)
    {
        MessageBox.Show(string.Format("Error saving settings! {0}", ex.Message), "Dave on C# Screen Saver", MessageBoxButtons.OK, MessageBoxIcon.Error);
    }
}

And that is the end of part one of this article series. Stay tuned for part two where we will be creating the screen saver’s drawing method and we will hook everything up. You will also be able to download the whole source code listing in part two.

Dave

5 comments… add one
  • Preethi Link Reply

    That’s great for a beginner like me ! thanx for sharing ur work..

  • Mack Link Reply

    Heeeeeyyy Dave, Nyc work buddy. Thanks a lot. I think ama gonna do great things with this… Thanks thousand times.
    God Bless indefinately

  • hasan Link Reply

    very good

Leave a Comment