Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C#

Find the Pointer

5.00/5 (7 votes)
14 Nov 2016CPOL3 min read 16.8K   191  
Press either CTRL key to find out where the mouse pointer is...

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:

  1. Create the tray application
  2. Create a system wide keyboard/mouse hook so I could capture the CTRL key
  3. 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:

C#
public RodentTray() // This is where I subscribe to the Key/Mouse events and call 

InitializeComponent(); // Which just sets up my menu objects for the tray icon.

QuitMenuItem_Click(object sender, EventArgs e) // This unsubscribes the Key/Mouse hook and quits the application.

CreateTheBombForm() // This is where I create the form thats used to display my images

ShowBomb(Form bombForm) // This displays the form and shows the images. It disposes of everything after the last frame is displayed.

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
   {
      /// <summary>
      /// The main entry point for the application.
      /// </summary>
      [STAThread]
      static void Main()
      {
         // Make sure only one instance of Rodent is able to run
         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
   {

         // Detect a second copy of the application that is already running.
         // If the user tries to run a second copy of the application, 
         // the existing instance should kill itself. Something like that. 
         
         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();

         // Create all context menu items and add them to notification tray icon
         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;

         // Wire up the menu item
         quitMenuItem.Click += QuitMenuItem_Click;

         #endregion
      }

      private void QuitMenuItem_Click(object sender, EventArgs e)
      {
         // Time to go
         RodentIcon.Visible = false;
         RodentIcon.Dispose();
         Unsubscribe();
         Application.Exit();
      }
            
      public void CreateTheBombForm()
      {
         // Create the form which will display the Bomb
         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();
            
         // Give a little to the other guys
         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);

         // Load up the array with our frames
         for (int imgCount = 0; imgCount < frames.Count(); imgCount++)
         {
            ImageBomb.Images.Add(frames[imgCount]);
         }

         // Get a Graphics object from the form's handle.
         Graphics theGraphics = Graphics.FromHwnd(bombForm.Handle);

         // Show the frames
         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)
      {
         // Break the animated .gif into frames
         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

      // Many thanks to the folks at NuGet for the wonderful work
      // they have done on the keyboard and mouse hooks. 
      // Brilliant stuff

      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)
      {
         // Just track the mouse till we need the coordinates
         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.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)