Introduction
Here’s the scenario. A co-worker sits down at his desk. After a few minutes, a message box pops up telling him that Windows encountered an error. Before he can respond, the box vanishes. He stresses, then figures it can be safely ignored. Then it returns. He goes to click the Cancel button, but the form jumps to a different location. He pursues it around his two monitors before it vanishes again, only to come back later.
Insert evil laugh here. But quietly, as you don't want him to know it was you who installed this small application on his machine.
This app was written using Visual Studio 2008, but should work with any version of the .NET Framework. It has been tested on Windows 7 64 bit and XP.
AppContext
The prank runs out of a descendant of ApplicationContext
, which lets it run without needing a form. If you are unfamiliar with this class or have never written notification area apps, you may wish to read an earlier article of mine, Create a System Tray Application in VB.NET[^]. Start with a standard Windows application, and add this class.
Public Class AppContext
Inherits ApplicationContext
#Region " Storage "
Private Frm As MsgForm
Private Rnd As Random
Private WithEvents Ico As NotifyIcon
Private WithEvents Menu As ContextMenuStrip
Private WithEvents mnuExit As ToolStripMenuItem
Private WithEvents Time As Timer
#End Region
#Region " Constructors "
Public Sub New()
Frm = New MsgForm
Rnd = New Random
Time = New Timer
Dim MaxHeight As Integer = 0
Dim Width As Integer = 0
Dim Boundary As Rectangle = Nothing
For Each S As Screen In Screen.AllScreens
Width += S.Bounds.Width
If S.Bounds.Height > MaxHeight Then MaxHeight = S.Bounds.Height
Next
mnuExit = New ToolStripMenuItem("Exit")
Menu = New ContextMenuStrip
Menu.Items.Add(mnuExit)
Ico = New NotifyIcon
Ico.Icon = My.Resources.MainIcon
Ico.ContextMenuStrip = Menu
Ico.Text = ""
Ico.Visible = True
Frm.Icon = My.Resources.MainIcon
Frm.Arena = New Rectangle(0, 0, Width - 1, MaxHeight - 1)
Time.Interval = Rnd.Next(30, 50) * 1000 Time.Enabled = True
End Sub
#End Region
#Region " Event handlers "
Private Sub AppContext_ThreadExit_
(ByVal sender As Object, ByVal e As System.EventArgs) _
Handles Me.ThreadExit
Ico.Visible = False
End Sub
Private Sub mnuExit_Click(ByVal sender As Object, ByVal e As System.EventArgs) _
Handles mnuExit.Click
Application.Exit()
End Sub
Private Sub Time_Tick(ByVal sender As Object, ByVal e As System.EventArgs) _
Handles Time.Tick
Time.Enabled = False
Frm.Display()
Time.Interval = Rnd.Next(30, 50) * 1000 Time.Enabled = True
End Sub
#End Region
End Class
The class AppContext
inherits from System.Windows.Forms.ApplicationContext
. Using this makes the app easier to scale, and offers us the ThreadExit
event where we can guarantee things will get cleaned up no matter how the application may end. To make it possible to shut down the app (come on, even that guy in Accounting should be given an out), we create a NotifyIcon
where we can access a menu.
To make this more fun, we allow for multiple monitors. Typically, monitors are arranged in a row, so our "arena" is the combined width of the monitors, and the height of the tallest. This defines the region where the form can jump.
We use a Random
object to set how often the form appears, which helps to keep them guessing. For demonstration purposes, I've set this to be pretty fast, every 30 to 50 seconds, but it might be more annoying if you use 300 to 900 seconds, which would be between five to fifteen minutes.
When the app is running, an icon will appear in the notification area (sometimes called the system tray.) You (or your users, if they get curious) can right click on the icon to pull up the context menu and select Exit. That ends the application thread, which in turn triggers the ThreadExit
event. This event gets called no matter how the thread ends: Windows is shutting down, the user went in through the Process Manager, and so on.
MsgForm
The prank form is also pretty basic.
Friend Class MsgForm
Private WithEvents Time As Timer
Private pArena As Rectangle
Private Rnd As Random
Private Sounds() As System.Media.SystemSound = { _
Media.SystemSounds.Asterisk, _
Media.SystemSounds.Beep, _
Media.SystemSounds.Exclamation, _
Media.SystemSounds.Hand, _
Media.SystemSounds.Question _
}
Public Property Arena() As Rectangle
Get
Return pArena
End Get
Set(ByVal value As Rectangle)
pArena = value
End Set
End Property
Public Sub New()
InitializeComponent()
pArena = Screen.PrimaryScreen.Bounds
Rnd = New Random
Time = New Timer
End Sub
Public Sub Display()
Me.CenterToScreen()
Time.Interval = Rnd.Next(10, 40) * 500
Sounds(Rnd.Next(0, Sounds.Length - 1)).Play()
Me.Show()
Time.Enabled = True
End Sub
Private Sub MsgForm_FormClosing_
(ByVal sender As Object, ByVal e As FormClosingEventArgs) _
Handles Me.FormClosing
If e.CloseReason = CloseReason.UserClosing Then
e.Cancel = True
End If
End Sub
Private Sub MsgForm_MouseEnter(ByVal sender As Object, ByVal e As EventArgs) _
Handles Me.MouseEnter
Me.Location = New Point(Rnd.Next(0, pArena.Width - Me.Width), _
Rnd.Next(0, pArena.Height - Me.Height))
End Sub
Private Sub Time_Tick(ByVal sender As Object, ByVal e As System.EventArgs) _
Handles Time.Tick
Time.Enabled = False
Me.Hide()
End Sub
End Class
The form has its own Timer
: when it expires, the timer is disabled and the form hidden. Note that the values are set using half second increments. As written, the form will vanish after 5 to 20 seconds.
The property Arena
gives the boundaries as to where the form can jump, and the array Sounds()
lets the form generate a random system sound every time it appears.
The form is activated from AppContext
by calling the its Display
method. This centers the form to the current monitor, sets the timer, plays a random sound, shows the form and starts the timer. When Time
goes off, the timer is disabled and the form hidden.
The MouseEnter
event is where things get interesting. When the user moves the mouse in to click on one of the buttons, the form jumps to a random location on the desktop. Hilarity ensues. If the user manages to reach the form's close button, the attempt to close the form gets canceled in the FormClosing
event so nothing happens, which adds just a bit more frustration.
Putting It Together
Because this is written in VB, there are a few extra steps that (I think) C# programmers do not need to do. The first is to write our entry point.
Public Module Launch
Public Sub Main()
Application.EnableVisualStyles()
Application.Run(New AppContext)
End Sub
End Module
Next, we have to tell the bootstrapper to use it instead of a starting form. This is done by going to the My Project interface, unchecking Enable application framework and selecting Sub Main
as the startup object. Disabling the application framework also disables the visual styles, which is why they get turned back on manually in Main
. Strictly speaking, we don't need to do this, but the form will not look right if we don't.
The last thing is to deploy the app. Assuming your chosen victim's workstation has the .NET framework already installed on it, it should just be a matter of compiling the code, copying the executable to somewhere handy, and running it.
For the Truly Evil
As written, this is an annoying but harmless prank. While I really hope you will keep it harmless, there are other ways you can make it much more annoying. One thought is to scan the user's machine for sound files, and play one at random when the form manifests. Or you could add a menu that lets you put the application thread to sleep
for a few hours before waking up again. Maybe instead of a system warning, the form could display complaints like "Is it warm in here or is my processor overheating?" or offer useful suggestions like "Screw this, let's go for a beer." The possibilities are limited only by your evil, twisted imagination.
Have fun, and if you come up with a particularly fiendish variation, please let me know!
History
- Version 1 - 2011-03-31 - Initial release