April Fools' Day Application
- Posted: Mar 29, 2007 at 3:03 PM
- 1,831 Views
- 4 Comments
Loading User Information from Channel 9
Something went wrong getting user information from Channel 9
Loading User Information from MSDN
Something went wrong getting user information from MSDN
Loading Visual Studio Achievements
Something went wrong getting the Visual Studio Achievements
| In this article, Brian Peek builds an application that will annoy your friends for April Fools' Day! | |
|
Difficulty: Easy
Time Required:
Less than 1 hour
Cost: Free
Hardware: None
Download: Download
|
|
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.
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.
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; }
' 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
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); }
' 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
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; } }
' 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 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(); } }
' 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
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:
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.
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!
A special thank you to Mark Zaugg for testing on a few machines outside my house to ensure things were working as expected.
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/.
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.
Follow the Discussion
Oops, something didn't work.
What does this mean?
Following an item on Channel 9 allows you to watch for new content and comments that you are interested in. You need to be signed in to Channel 9 to use this feature.What does this mean?
Following an item on Channel 9 allows you to watch for new content and comments that you are interested in and view them all on your notifications page.sign up for email notifications?
Haha I can Really Trick my Friends with this!
Thnx for the Hard Work , m working on it , lets see ...
@Hmmm what do you mean?
For some reason it does not like to let me use the names they declared for it
Remove this comment
Remove this thread
close