Entries:
Comments:
Posts:

Loading User Information from Channel 9

Something went wrong getting user information from Channel 9

Latest Achievement:

Loading User Information from MSDN

Something went wrong getting user information from MSDN

Visual Studio Achievements

Latest Achievement:

Loading Visual Studio Achievements

Something went wrong getting the Visual Studio Achievements

April Fools' Day Application

In this article, Brian Peek builds an application that will annoy your friends for April Fools' Day!
ASPSOFT, Inc.

Difficulty: Easy
Time Required: Less than 1 hour
Cost: Free
Software: Visual Basic or Visual C# Express Editions
Hardware: None
Download: Download
 

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:

C#

// full width/height of all monitors combined
private Rectangle fullSize;

for(int i = 0; i < Screen.AllScreens.Length; i++)
{
    // 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)
        fullSize.X = Screen.AllScreens[i].Bounds.Left;
    if(Screen.AllScreens[i].Bounds.Right > fullSize.Right)
        fullSize.Width = Screen.AllScreens[i].Bounds.Right;
    if(Screen.AllScreens[i].Bounds.Top < fullSize.Top)
        fullSize.Y = Screen.AllScreens[i].Bounds.Top;
    if(Screen.AllScreens[i].Bounds.Bottom > fullSize.Bottom)
        fullSize.Height = Screen.AllScreens[i].Bounds.Bottom;
}
VB
' full width/height of all monitors combined
Private fullSize As Rectangle

For i As Integer = 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
    End If
    If Screen.AllScreens(i).Bounds.Right > fullSize.Right Then
        fullSize.Width = Screen.AllScreens(i).Bounds.Right
    End If
    If Screen.AllScreens(i).Bounds.Top < fullSize.Top Then
        fullSize.Y = Screen.AllScreens(i).Bounds.Top
    End If
    If Screen.AllScreens(i).Bounds.Bottom > fullSize.Bottom Then
        fullSize.Height = Screen.AllScreens(i).Bounds.Bottom
    End If
Next 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 system
private 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 system
Private screenBitmap As Bitmap()

For i As Integer = 0 To Screen.AllScreens.Length - 1
    ' grab the size of the current monitor
    Dim 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 monitor
    Dim 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:

C#

// rotation angle
private float angle = 0.0f;

// scale indexer
private float scaleIndex = 0.0f;

// black background
this.BackColor = Color.Black;

// for each monitor, draw the effect
for(int i = 0; i < screenBitmap.Length; i++)
{
    // grab the size of the current monitor
    Rectangle region = Screen.AllScreens[i].Bounds;

    if(screenBitmap[i] != null)
    {
        Matrix m = new Matrix();

        // rotate the bitmap about the center
        if(Properties.Settings.Default.Rotate)
            m.RotateAt(angle, new Point((Screen.AllScreens[i].Bounds.Width / 2), (Screen.AllScreens[i].Bounds.Height / 2)));

        // scale the image based on the absolute value of cos (gives us a nice in/out effect with minimal effort
        if(Properties.Settings.Default.Zoom)
        {
            scale = Math.Abs((float)Math.Cos(scaleIndex));
            m.Scale(scale, scale);
        }

        // center the image no matter what its size is
        m.Translate(Screen.AllScreens[i].Bounds.Left + (Screen.AllScreens[i].Bounds.Width * (1.0f - scale)/2), Screen.AllScreens[i].Bounds.Top + (Screen.AllScreens[i].Bounds.Height * (1.0f - scale)/2), MatrixOrder.Append);

        // assign our transformation matrix
        e.Graphics.Transform = m;

        // draw it
        e.Graphics.DrawImage(screenBitmap[i], 0, 0);

        // increment our values
        if(Properties.Settings.Default.Zoom)
            scaleIndex += 0.07f;

        if(Properties.Settings.Default.Rotate)
            angle += 2.0f;
    }
}
VB
' rotation angle
Private angle As Single = 0.0f

' scale indexer
Private scaleIndex As Single = 0.0

' black background
Me.BackColor = Color.Black

' for each monitor, draw the effect
For i As Integer = 0 To screenBitmap.Length - 1
    ' grab the size of the current monitor
    Dim region As Rectangle = Screen.AllScreens(i).Bounds

    If Not screenBitmap(i) Is Nothing Then
        Dim m As Matrix = New Matrix()

        ' rotate the bitmap about the center
        If My.Settings.Default.Rotate Then
            m.RotateAt(angle, New Point((Screen.AllScreens(i).Bounds.Width / 2), (Screen.AllScreens(i).Bounds.Height / 2)))
        End If

        ' scale the image based on the absolute value of cos (gives us a nice in/out effect with minimal effort
        If My.Settings.Default.Zoom Then
            scale = Math.Abs(CSng(Math.Cos(scaleIndex)))
            m.Scale(scale, scale)
        End If

        ' center the image no matter what its size is
        m.Translate(Screen.AllScreens(i).Bounds.Left + (Screen.AllScreens(i).Bounds.Width * (1.0f - scale)/2), Screen.AllScreens(i).Bounds.Top + (Screen.AllScreens(i).Bounds.Height * (1.0f - scale)/2), MatrixOrder.Append)

        ' assign our transformation matrix
        e.Graphics.Transform = m

        ' draw it
        e.Graphics.DrawImage(screenBitmap(i), 0, 0)

        ' increment our values
        If My.Settings.Default.Zoom Then
            scaleIndex += 0.07f
        End If

        If My.Settings.Default.Rotate Then
            angle += 2.0f
        End If
    End If
Next i
 

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 show
private void tmrScheduler_Tick(object sender, EventArgs e)
{
    // if the current time is greater than the time set by the user
    if(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 window
        this.WindowState = FormWindowState.Normal;

        // cover all monitors with one gigantic window
        this.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 top
        this.BringToFront();
    }
}
VB
' called once per minute to check whether it's time to run the show
Private Sub tmrScheduler_Tick(ByVal sender As Object, ByVal e As EventArgs) Handles tmrScheduler.Tick
    ' if the current time is greater than the time set by the user
    If DateTime.Now >= My.Settings.Default.Time Then
        For i As Integer = 0 To Screen.AllScreens.Length - 1
            ' grab the size of the current monitor
            Dim 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 monitor
            Dim bitmapGraphics As Graphics = Graphics.FromImage(screenBitmap(i))
            bitmapGraphics.CopyFromScreen(region.Left, region.Top, 0, 0, region.Size)
        Next i

        ' bring up the window
        Me.WindowState = FormWindowState.Normal

        ' cover all monitors with one gigantic window
        Me.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 top
        Me.BringToFront()
    End If
End Sub
 

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/.

Tags:

Follow the Discussion

Remove this comment

Remove this thread

close

Comments Closed

Comments have been closed since this content was published more than 30 days ago, but if you'd like to continue the conversation, please create a new thread in our Forums,
or Contact Us and let us know.