Introduction

You may recall my previous
Halloween article which involved dripping animated blood down a victim's screen. For April Fools' Day, I wrote an application that uses the same basic framework, and provides an equal amount of annoyance for the intended victim.

This time around, the application will lay in wait until the assigned time and then run one of two (or both!) effects: desktop zoom in/out and desktop rotate.

Some of this article will be a bit of a rehash from the previous article, but for the sake of being complete, I will be describing the duplicated portions here.

The Form

We will be creating a full-screen, always-on-top, transparent form that will overlay the entire screen. This will be our display surface for the animation.

The form can be created as described by modifying the following properties:

BackColor – Fuchsia

FormBorderStyle – None

DoubleBuffered – True

Text - <empty string>

WindowState – Normal

ShowInTaskbar – False

TransparencyKey – Fuchsia

TopMost - True

This will create our full-screen, transparent form with no visible name, no icon in the task bar, no border that will remain on top of all other windows. With this in place, we can draw whatever we want to on to the form and it will be displayed over whatever
happens to be on the screen.

Multiple Monitors

One feature I did not implement in the previous Halloween application was support for systems with multiple monitors. This application will attempt to support a basic multi-mon configuration. When the animation starts, the application must cover the entire
display area across all monitors.

One can get information about every monitor attached to a system by using the
Screen.AllScreens property. So, to find the maximum "viewport" of the system, the application enumerates all monitors and finds the maximum width/height:

' full width/height of all monitors combinedPrivate fullSize As Rectangle
For i AsInteger = 0 To Screen.AllScreens.Length - 1
' find a rectangle that will encompass all monitors on the system' (assuming the primary monitor is on the left/top!)If Screen.AllScreens(i).Bounds.Left < fullSize.Left Then
fullSize.X = Screen.AllScreens(i).Bounds.Left
EndIfIf Screen.AllScreens(i).Bounds.Right > fullSize.Right Then
fullSize.Width = Screen.AllScreens(i).Bounds.Right
EndIfIf Screen.AllScreens(i).Bounds.Top < fullSize.Top Then
fullSize.Y = Screen.AllScreens(i).Bounds.Top
EndIfIf Screen.AllScreens(i).Bounds.Bottom > fullSize.Bottom Then
fullSize.Height = Screen.AllScreens(i).Bounds.Bottom
EndIfNext i

Screen Capture

When the animation starts, a screen capture of each monitor is taken and stored in an array. The animation acts upon these images to produce the rotating and zooming effect. Taking a full screen capture in .NET is quite easy:

C#

// an array of bitmaps, one per monitor on the systemprivate Bitmap[] screenBitmap;
for(int i = 0; i < Screen.AllScreens.Length; i++)
{
// grab the size of the current monitor
Rectangle region = Screen.AllScreens[i].Bounds;
// create a bitmap of that size
screenBitmap[i] = new Bitmap(region.Width, region.Height,
PixelFormat.Format32bppArgb);
// copy the current screen image to the bitmap for the current monitor
Graphics bitmapGraphics = Graphics.FromImage(screenBitmap[i]);
bitmapGraphics.CopyFromScreen(region.Left, region.Top, 0, 0, region.Size);
}

VB

' an array of bitmaps, one per monitor on the systemPrivate screenBitmap As Bitmap()
For i AsInteger = 0 To Screen.AllScreens.Length - 1
' grab the size of the current monitorDim region As Rectangle = Screen.AllScreens(i).Bounds
' create a bitmap of that size
screenBitmap(i) = New Bitmap(region.Width, region.Height,
PixelFormat.Format32bppArgb)
' copy the current screen image to the bitmap for the current monitorDim bitmapGraphics As Graphics = Graphics.FromImage(screenBitmap(i))
bitmapGraphics.CopyFromScreen(region.Left, region.Top, 0, 0, region.Size)
Next i

Drawing

In creating this application, I decided to stay entirely within the framework so deployment would be as simple as copying a single executable to your victim's machine. Therefore, I decided to use standard GDI+ calls for drawing, instead of Managed DirectX
or some other method.

Drawing is done through the use of a timer. The timer ticks once very 100ms and calls the
Invalidate method on the form. When invalidated, the form calls its
OnPaint handler, which is overridden.

The application currently supports two effects: zooming the desktop into and out of the screen, and rotation. Both effects are obtained using standard GDI+ drawing calls in .NET.

Two dimensional transforms can be easily done using methods from the System.Drawing.Drawing2D namespace. Rotation can be handled using the
Rotate and RotateAt methods, and scaling can be done using the
Scale method. The effects for this application will be done on a per-monitor basis using the screen captures taken with the above code:

The code above starts out by setting the background of the window to black. Then for each monitor, an affect is applied. If rotation is enabled, the bitmap is rotated by an ever-increasing angle parameter about the screen's center point. If scaling is
enabled, the image is scaled based on the absolute value of the cosine of an ever increasing value, starting at 0. As you may recall from high-school trig, cos(0) = 1. Starting here, will start our scaling factor at 1, which will provide no change. By increasing
the value passed to the cos method, we will get decreasing values to 0 after which we will get values which are negative. By taking the absolute value of these numbers, we get a series of decreasing/increasing numbers between 0 and 1.

With the scale and rotation worked out, the images are then translated to the center of the screen before being drawn, which is necessary due to the scaling procedure.

The final calculated matrix is passed to the Graphics.Transform property and the resulting image is finally drawn to the screen, after which the scale and rotation factors are incremented.

The Scheduler

The final piece of the puzzle was to allow a user to select the date and time that the animation would start on the victim's PC. I created a simple configuration dialog box that is displayed the first time the application is run. The dialog allows the person
setting up the application to choose the date and time the animation should be fired, as well as the effects to run.

These settings are saved using the Settings object of the project. This way, the user can put the application in the startup group, or create a registry key to load it on startup, so if the PC is rebooted before the desired time is reached, the application
will restart and wait for the time to occur.

Settings can be added by right-clicking the project in the Solution Explorer and choosing
Properties. Select the Settings tab and a new value can be entered. The one for this application looks like the following:

Now, on application startup, the application's Settings can be checked to determine if the configuration dialog needs to be shown, or just wait for the time specified.

When the application is started, a scheduler timer is created which ticks every minute. The
Tick method for this timer looks at the current time, and if it is greater than or equal to the specified time, it will disable the scheduler timer, maximize the hidden window, take the screen captures, and start the animation timer, which will automatically
start the animation.

C#

// called once per minute to check whether it's time to run the showprivatevoid tmrScheduler_Tick(object sender, EventArgs e)
{
// if the current time is greater than the time set by the userif(DateTime.Now >= Properties.Settings.Default.Time)
{
for(int i = 0; i < Screen.AllScreens.Length; i++)
{
// grab the size of the current monitor
Rectangle region = Screen.AllScreens[i].Bounds;
// create a bitmap of that size
screenBitmap[i] = new Bitmap(region.Width, region.Height, PixelFormat.Format32bppArgb);
// copy the current screen image to the bitmap for the current monitor
Graphics bitmapGraphics = Graphics.FromImage(screenBitmap[i]);
bitmapGraphics.CopyFromScreen(region.Left, region.Top, 0, 0, region.Size);
}
// bring up the windowthis.WindowState = FormWindowState.Normal;
// cover all monitors with one gigantic windowthis.Location = new Point(fullSize.Left, fullSize.Top);
this.Size = new Size(fullSize.Width, fullSize.Height);
// disable this timer
tmrScheduler.Enabled = false;
// enable the animation timer
tmrAnim.Enabled = true;
// bring it to the topthis.BringToFront();
}
}

VB

' called once per minute to check whether it's time to run the showPrivateSub tmrScheduler_Tick(ByVal sender AsObject, ByVal e As EventArgs) Handles tmrScheduler.Tick
' if the current time is greater than the time set by the userIf DateTime.Now >= My.Settings.Default.Time ThenFor i AsInteger = 0 To Screen.AllScreens.Length - 1
' grab the size of the current monitorDim region As Rectangle = Screen.AllScreens(i).Bounds
' create a bitmap of that size
screenBitmap(i) = New Bitmap(region.Width, region.Height, PixelFormat.Format32bppArgb)
' copy the current screen image to the bitmap for the current monitorDim bitmapGraphics As Graphics = Graphics.FromImage(screenBitmap(i))
bitmapGraphics.CopyFromScreen(region.Left, region.Top, 0, 0, region.Size)
Next i
' bring up the windowMe.WindowState = FormWindowState.Normal
' cover all monitors with one gigantic windowMe.Location = New Point(fullSize.Left, fullSize.Top)
Me.Size = New Size(fullSize.Width, fullSize.Height)
' disable this timer
tmrScheduler.Enabled = False' enable the animation timer
tmrAnim.Enabled = True' bring it to the topMe.BringToFront()
EndIfEndSub

Deployment

As mentioned earlier, one of the main goals of this project was to have an extremely easily deployable application. In order to achieve this, all code was kept inside the framework with no dependencies on things like Managed DirectX.

When you are ready to annoy your friend, simply copy the AprilFoolsDay.exe and AprilFoolsDay.exe.config files to his or her PC. (You may want to enlist someone as the “lookout” so you don't get caught while doing so!) Create a shortcut to it in the Startup
program group, or set it up to run via the registry using the following key for the logged in user:

HKCU\Software\Microsoft\Windows\CurrentVersion\Run

Simply create a new string key with any Name, and a Data value of the path to the executable.

Next, start the application once on their PC to setup the date and time for the show to begin. Once that is done, the application will remain running in the background. If the PC is restarted, and the application is setup to run at startup as described above,
it will start silently and remain running, waiting for the date and time specified.

If you need to reset the configuration for any reason, simply run the application from the command line with the
-config switch and it will display the configuration dialog box again.

Summary

And there we have it. A very simple application requiring very little code that will elicit quite a reaction from your victim. The code can be very easily modified to include new features and different animation effects. Give it a try!

Thanks

A special thank you to Mark Zaugg for testing on a few machines outside my house to ensure things were working as expected.

Bio

Though Brian is a recognized .NET expert with over 6 years experience developing .NET solutions, and over 9 years of professional experience architecting and developing solutions using Microsoft technologies and platforms, he has been "coding for fun" for
as long as he can remember. Outside the world of .NET and business applications, Brian enjoys developing both hardware and software projects in the areas of gaming, robotics, and whatever else strikes his fancy for the next ten minutes. He rarely passes up
an opportunity to dive into a C/C++ or assembly language project. You can reach Brian via his blog at
http://www.brianpeek.com/.