Blogs

Tutorial: MQII Screen Saver (C#/Windows)

Posted on 13 April 2017

The following is a tutorial on how to make the screen saver I released last month. It will demonstrate how I develop with only notepad and a few techniques I used in Messiah Quest II. You can organize your files how you wish as you perform these tasks, but since I just threw this app together, I have all code in one script file and all files in one folder. The main benefit of the tutorial is that you get to make your own copy of the screen saver that your PC will trust.

First, you will need the image files. The following links will show you the images you can download.

MQII.ico

Stars.png

Background.png

The other two files you will create yourself (unless you break apart the code). For every C# file you make, just create a new text document and make sure the extension is .cs. For example: Program.cs.

Let’s discuss the absolute minimum requirements for a screen saver. The purpose of a screen saver is to keep pixels active so images don’t get burned onto the screen. To satisfy this purpose, our app must take up the whole screen and make sure all pixels change. Another requirement is the app must close upon any activity. Now let’s write some code! Our first class will be frmScreensaver.

using System;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Reflection;
using System.Windows.Forms;

namespace MQIIScreensaver
{
    public class frmScreensaver : Form
    {

    }
}

The important part is to inherit from the Form class in System.Windows.Forms. This will let us start with a default window to draw our images on. Let’s add some fields which we will use later in our methods. Add these within the class brackets.

bool initialized;
Timer timer;
Bitmap stars;
RectangleF starsArea;
Bitmap background;
RectangleF backgroundArea;

The initialized variable will be used at the start of the app. It will ignore the first second of any activity so the app won’t close before it is able to do anything. The Timer object will tick several times a second and let us update the position of the images we draw to create animation. The Bitmap objects will store the images we load, and the RectangleF variables will store where the images are being drawn on the window. RectangleF uses the float data type which can store decimals as opposed to Rectangle which uses the int data type which only stores whole numbers. Next, we will write our event handlers, which are methods called upon certain events. Add these at the bottom of the class after the fields (and later the constructor).

void activeInput(object sender, EventArgs e)
{
    Application.Exit();
}

This event handler complies with the SystemEventHandler delegate. It requires a void return type, an object parameter, and an EventArgs parameter. Then it can be subscribed to the Form object’s Click event as well as many other events. Most of the Windows Forms events use the SystemEventHandler delegate. We will subscribe this event to a few events when the user touches the mouse or keyboard. When those events happen, the application will close.

Our next event will start like this and it will subscribe to the Timer object’s Tick event.

void timer_Tick(object sender, EventArgs e)
{

}

So this method will be called every time the timer ticks, which will be 20 times a second. Let’s fill it out. First we must call the Form’s Invalidate method:

Invalidate();

This will ultimately raise the Form’s Paint event and the images will be redrawn. Next, we will add in the code which delays user input from closing the app. It is mainly for testing purposes, because the application will most likely close immediately when you double click to launch the app. When we initialize our timer, we will set it to tick every 1000 milliseconds (1 second), so this method will not be called until 1 second after the app starts.

if (!initialized)
{
    Click += activeInput;
    KeyPress += activeInput;
    MouseMove += activeInput;
    timer.Interval = 50;
    initialized = true;
}

After a second has passed the method is called and the initialized variable will start at false. This block will be executed, and it subscribes to the Form’s Click, KeyPress, and MouseMove events using the += operand. Then, it increases the tick rate by setting the timer’s interval to 50 milliseconds. Finally, initialized is set to true so the block won’t execute again.

Now we can start doing some serious work. Since our images will use the RectangleF variables to calculate where to draw the images on the screen, we must update the properties of those variables. First you should know how the coordinate system works. X is horizontal location, and Y is vertical location. Point (0, 0) is the top-left corner of the window. Rectangles have an identical coordinate system. Point (X, Y) is the top-left corner of the rectangle, and it’s the number of pixels away from the top-left corner of the window. Rectangles also have Width and Height properties which is the number of pixels it expands from its top-left corner. The biggest issue is that origins are the top-left instead of the bottom left like other coordinate systems. Remember: as Y increases, the lower on the window you are. Let’s start updating our rectangles!

starsArea.X -= 2;
backgroundArea.X -= 1;

Pretty simple! These will simply move the stars left by 2 pixels and the background left by 1 pixel. This will make the distant background move slightly faster than the near background, creating the illusion of slowly spinning. Now this will slide the images off the screen, but to make it seemless, each image will be drawn twice. Once the first image is entirely off the screen, we will reset the position to 0. This will allow the images to scroll endlessly without disappearing off our window.

if (starsArea.X <= -starsArea.Width)
{
    starsArea.X = 0;
}
if (backgroundArea.X <= -backgroundArea.Width)
{
    backgroundArea.X = 0;
}

That is all the code for our tick event handler, so let’s make the paint event handler and finally draw our images.

void frmScreensaver_Paint(object sender, PaintEventArgs e)
{

}

The PaintEventArgs object is important to drawing, because it stores the Graphics object which will allow us to draw images.

// This line will improve the images when they are scaled. Because our images are pixel art, we don’t want our colors to blur.
e.Graphics.InterpolationMode = InterpolationMode.NearestNeighbor;
// Draw the stars image
e.Graphics.DrawImage(stars, starsArea);
// Draw the stars image again on the right end of the first one
e.Graphics.DrawImage(stars, new RectangleF(starsArea.X + starsArea.Width - 1, starsArea.Y, starsArea.Width, starsArea.Height));
// Do the same with the background
e.Graphics.DrawImage(background, backgroundArea);
e.Graphics.DrawImage(background, new RectangleF(backgroundArea.X + backgroundArea.Width - 2, backgroundArea.Y, backgroundArea.Width, backgroundArea.Height));

That finishes our event handlers. Now let’s write the constructor for our form and get everything set up to use what we made.

public frmScreensaver() : base()
{

}

It is important to include base(), because that will call the constructor of the base class, which is the Form. Next, we will set up some properties of the form to make it full screen.

BackColor = Color.Black;
FormBorderStyle = FormBorderStyle.None;
WindowState = FormWindowState.Maximized;

This will essentially blackout the screen. It will hide the border, show the window on top of the toolbar, and make it cover the entire screen.

SetStyle(ControlStyles.AllPaintingInWmPaint, true);
SetStyle(ControlStyles.OptimizedDoubleBuffer, true);

These two lines will improve the buffering when drawing images on the window. Without these lines, you will get a nasty flicker as the images begin to animate. When we call the Invalidate method, it also clears what we’ve drawn on the previous frame before it draws the next frame. The flicker is that split second when everything is cleared and redrawn. With better buffering, the window is ready to draw the next frame before it clears everything. In the end, these lines are very important when drawing images like this. Let’s finish setting up the form.

Cursor.Hide();
Paint += frmScreensaver_Paint;

Now the mouse pointer will be hidden while the screen saver is running, and our paint event handler will be called when the window is invalidated. Although our window is set to be maximized, the drawing surface isn’t actually the entire screen. So, we will add these lines to make the whole screen our drawing surface.

var screen = Screen.FromControl(this).Bounds;
ClientSize = new Size(screen.Width, screen.Height);

First we get a handle of the screen on which our app is located, and then we use the bounds of the screen to set the form’s ClientSize. The ClientSize is the actual width and height of the window.

Now for a trick I used for Messiah Quest II, I embedded the images into a single file. We will also be embedding the images used by this app in the executable itself. In order to load those images, we use the following code.

// Get a handle on the assembly including its embedded resources
var assembly = Assembly.GetExecutingAssembly();
// Load the stars image from the resource stream
stars = new Bitmap(assembly.GetManifestResourceStream("Stars.png"));
// Calculate the scaling factor for the stars, because the stars will take up the entire width of the screen
var starsFactor = (float)(Width) / (float)(stars.Width);
// Create the rectangle where the stars will be drawn, and make sure height is scaled with width
starsArea = new RectangleF(0, 0, Width, stars.Height * starsFactor);
// Do the same with the background
background = new Bitmap(assembly.GetManifestResourceStream("Background.png"));
// The background’s width will be 3 times the width of the screen
var backgroundFactor = (float)(Width * 3) / (float)(background.Width);
var backgroundHeight = (float)(background.Height) * (float)(backgroundFactor);
// The background should be drawn at the bottom of the screen
backgroundArea = new RectangleF(0, Height - backgroundHeight + 50, Width * 3, backgroundHeight);

Last, but not least, we need to set up the timer!

timer = new Timer();
timer.Interval = 1000;
timer.Tick += timer_Tick;
timer.Start();

Now we have our window ready, our images loaded, our rectangles scaled and initialized, and our timer will start ticking once the form is created. That leaves one last thing: the entry point of our app’s execution. Every app needs a Main method which we will write in a new class: Program.

class Program
{
    static void Main()
    {
        Application.Run(new frmScreensaver());
    }
}

All of our C# is written, and we are ready to compile. Because I did this in notepad, I have to use the command prompt to compile the code into an executable. The best way to do so is to write a batch file. This will be a simple list of commands used to compile the app, and it will execute when we double click it. So, create another text document and make sure it has the .bat extension. Example: compile.bat.

@echo off
title Compiling MQII Screen Saver
echo Compiling MQII Screen Saver.scr...
\Windows\Microsoft.NET\Framework\v4.0.30319\csc /nologo /out:"MQII Screen Saver.scr" /t:winexe /win32icon:MQII.ico /res:Stars.png /res:Background.png *.cs
echo Completing Process...
pause

The first line hides the directory that is always displayed where the cursor is located. Next, it changes the title on the window to “Compiling MQII Screen Saver.” The echo command simply displays the following text as a message. The next line is the important part. CSC is the C# compiler, and it is by default located at C:\Windows \Microsoft.NET \Framework \v4.0.30319\. Then there are a bunch of switches with additional arguments for the CSC. /nologo hides a bunch of text whenever you use CSC. /out is the file that is created. /t:winexe makes the file a windows executable. /win32icon specifies the icon file to use. Each /res specifies a file to embed as a resource for the app. The final bit specifies which source code files to use. I use *.cs to specify all files with the .cs extension. The pause command pauses the execution and waits for user input. This prevents the window from closing without user confirmation, and it allows you to see any errors or warning messages from the compiler. Once this is written, double click it to execute the command batch. If all is well, MQII Screen Saver.scr should appear in your folder. With the .scr extension, you can right-click the executable to install the screen saver and set it to activate when your PC is idle. Since the images are embedded in the executable, you can move it anywhere and it will still run! Enjoy your new screen saver!

You can view all C# code assembled here: Program.cs