Introduction
Ever lose your mouse pointer amongst all the applications you have open? I know Microsoft gives you the ability to show where your mouse pointer is in the Mouse properties. Simply check the "Show the location of the pointer when I press the CTRL key" and there you have it.
This was not working as well as my brother would have liked (he has a 4K display), so I decided to do something about it for him.
It was a bigger challenge than I thought it would be.
Background
My goal was to create a windows tray application that would do the following:
- Create the tray application
- Create a system wide keyboard/mouse hook so I could capture the CTRL key
- Display a bomb exploding wherever the mouse pointer was.
Thats all I wanted to do. Not a big deal. Oh. The application is called Rodent.
Here are the major methods:
public RodentTray()
InitializeComponent();
QuitMenuItem_Click(object sender, EventArgs e)
CreateTheBombForm()
ShowBomb(Form bombForm)
You must add the following to your using's, and make sure you add it to your Refrences in the solution explorer:
using Gma.System.MouseKeyHook;
Using the Code
After numerous false starts here is what I came up with. A class that ensures only one instance of the application is running. I think I found this on Stack Overflow. Make sure only one instance of Rodent is able to run.
namespace Rodent
{
static class Program
{
[STAThread]
static void Main()
{
SingleInstance.SingleInst();
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new Rodent.Classes.RodentTray());
}
}
}
SingleInstance.SingleInst();
This is the first class called by the application.
public class SingleInstance
{
public static void SingleInst()
{
if (Process.GetProcessesByName
(Process.GetCurrentProcess().ProcessName).Length > 1)
Process.GetCurrentProcess().Kill();
}
}
As I wanted this to be a REAL tray application I found out the hard way that you should not allow C# to create the default Form1 in the Application.Run but create a class which will be the entry point to your application and its a ApplicationContext. Reference the bold text in the first code snipet.
My next task was to find a way to hook into the mouse and keyboard at the system level. I found a really great way to do this using a NuGet package. Many thanks to George Mamaladze for his work on this difficult task. I tried numerous .DLL's and they always fell short either through my inability to understand how to use them, or they really just did not work as advertised. You may get this package through the NuGet package manager through the Tools option in Visual Studio and select browse, type Mouse and you should be on the correct package.
I'm not sure how to add the image: so I'm going to paste the entrie application below.
using System;
using System.Windows.Forms;
using Gma.System.MouseKeyHook;
using System.Drawing;
using System.Drawing.Imaging;
using System.Linq;
namespace Rodent.Classes
{
class RodentTray : ApplicationContext
{
#region Global stuff
public NotifyIcon RodentIcon;
private IKeyboardMouseEvents m_Events;
public int Rodent_X = 0;
public int Rodent_Y = 0;
public ImageList ImageBomb = new ImageList();
#endregion
public RodentTray()
{
InitializeComponent();
RodentIcon.Icon = Rodent.Properties.Resources.Rodent;
RodentIcon.Visible = true;
Subscribe(Hook.GlobalEvents());
}
private void InitializeComponent()
{
#region Create contex menus
RodentIcon = new NotifyIcon();
MenuItem programMenuItem = new MenuItem("Rodent Release 1.0");
MenuItem quitMenuItem = new MenuItem("Close The Application");
ContextMenu contextMenu = new ContextMenu();
contextMenu.MenuItems.Add(programMenuItem);
contextMenu.MenuItems.Add("-");
contextMenu.MenuItems.Add(quitMenuItem);
RodentIcon.ContextMenu = contextMenu;
quitMenuItem.Click += QuitMenuItem_Click;
#endregion
}
private void QuitMenuItem_Click(object sender, EventArgs e)
{
RodentIcon.Visible = false;
RodentIcon.Dispose();
Unsubscribe();
Application.Exit();
}
public void CreateTheBombForm()
{
Form BombForm = new Form();
BombForm.AutoScaleMode = AutoScaleMode.Dpi;
BombForm.FormBorderStyle = System.Windows.Forms.FormBorderStyle.None;
BombForm.AllowTransparency = true;
BombForm.BackColor = System.Drawing.Color.Black;
BombForm.TransparencyKey = System.Drawing.Color.Black;
BombForm.StartPosition = System.Windows.Forms.FormStartPosition.Manual;
BombForm.ShowInTaskbar = false;
BombForm.ClientSize = new System.Drawing.Size(150, 204);
BombForm.TopMost = true;
BombForm.Activate();
Application.DoEvents();
BombForm.Top = Rodent_Y - 40;
BombForm.Left = Rodent_X - 40;
ShowBomb(BombForm);
}
private void ShowBomb(Form bombForm)
{
bombForm.Show();
ImageBomb.ImageSize = new Size(78, 121);
int numberOfFrames = Rodent.Properties.Resources.SmallExplosion.GetFrameCount(FrameDimension.Time);
Image[] frames = getFrames(Rodent.Properties.Resources.SmallExplosion);
for (int imgCount = 0; imgCount < frames.Count(); imgCount++)
{
ImageBomb.Images.Add(frames[imgCount]);
}
Graphics theGraphics = Graphics.FromHwnd(bombForm.Handle);
for (int imgCount = 0; imgCount < frames.Count(); imgCount++)
{
ImageBomb.Draw(theGraphics, new Point(1, 1), imgCount);
Application.DoEvents();
System.Threading.Thread.Sleep(20);
}
theGraphics.Dispose();
bombForm.Dispose();
}
Image[] getFrames(Image originalImg)
{
int numberOfFrames = originalImg.GetFrameCount(FrameDimension.Time);
Image[] frames = new Image[numberOfFrames];
for (int imageCount = 0; imageCount < numberOfFrames; imageCount++)
{
originalImg.SelectActiveFrame(FrameDimension.Time, imageCount);
frames[imageCount] = ((Image)originalImg.Clone());
}
return frames;
}
#region Keyboard and Mouse hooks
private void Subscribe(IKeyboardMouseEvents events)
{
m_Events = events;
m_Events.KeyUp += GlobalHookKeyPress;
m_Events.MouseMove += M_GlobalHook_MouseMove;
}
public void Unsubscribe()
{
if (m_Events == null) return;
m_Events.KeyUp -= GlobalHookKeyPress;
m_Events.MouseMove -= M_GlobalHook_MouseMove;
m_Events.Dispose();
}
private void M_GlobalHook_MouseMove(object sender, MouseEventArgs e)
{
Rodent_X = e.X;
Rodent_Y = e.Y;
}
private void GlobalHookKeyPress(object sender, KeyEventArgs e)
{
if(e.KeyCode == Keys.LControlKey || e.KeyCode == Keys.RControlKey)
{
CreateTheBombForm();
}
}
#endregion
}
}
Hope this is OK. You may download the entire project and run the Rodent.exe from the debug folder. Then press either CTRL key.
Points of Interest
I learned quite a bit developing this application. One major issue that had me baffled was playing either .mp4, gif, or even .AVI files would not work as they did not give the system back to me until they were finished playing.
This is why I created the following method:
getFrames(Rodent.Properties.Resources.SmallExplosion);
Which broke my embedded .Gif file into frames and allowed me to display the images one at a time. With a little .DoEvents() between each frame I was able to get the performance I needed.
The .ZIP file contains everything you need to run the application, and of course the source code.