65.9K
CodeProject 正在变化。 阅读更多。
Home

寻找指针

starIconstarIconstarIconstarIconstarIcon

5.00/5 (7投票s)

2016年11月14日

CPOL

3分钟阅读

viewsIcon

17401

downloadIcon

215

按下任意 CTRL 键,找出鼠标指针的位置...

引言

是否经常在打开的众多应用程序中丢失鼠标指针?我知道 Microsoft 允许你在鼠标属性中显示鼠标指针的位置。只需选中“当我按下 CTRL 键时显示指针的位置”复选框即可。

这对我兄弟来说效果并不理想(他有一个 4K 显示器),所以我决定为他做点什么。

这比我想象的更具挑战性。

背景

我的目标是创建一个 Windows 托盘应用程序,它可以执行以下操作:

  1. 创建托盘应用程序
  2. 创建一个系统范围的键盘/鼠标钩子,以便我可以捕获 CTRL 键
  3. 在鼠标指针所在的位置显示爆炸的炸弹。

这就是我想要做的全部。没什么大不了的。哦,该应用程序名为 Rodent。

以下是主要方法

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.

你必须将以下内容添加到你的 using 语句中,并确保将其添加到解决方案资源管理器中的引用中

using Gma.System.MouseKeyHook;

Using the Code

经过多次错误的尝试后,我得到了这个。一个确保应用程序只有一个实例正在运行的类。我想我是在 Stack Overflow 上找到的这个。确保 Rodent 只能运行一个实例。

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();

这是应用程序调用的第一个类。

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();
         }
      }

因为我希望这是一个真正的托盘应用程序,所以我发现了一个痛苦的事实,你不应该允许 C# 在 Application.Run 中创建默认的 Form1,而是创建一个类,该类将成为你的应用程序的入口点,它是一个 ApplicationContext。在第一个代码片段中引用粗体文本。

我的下一个任务是找到一种方法来钩入系统级别的鼠标和键盘。我找到了一种非常好的方法来使用 NuGet 包来实现这一点。非常感谢 George Mamaladze 在这项艰巨任务中所做的工作。我尝试了许多 .DLL,它们总是失败,要么是因为我无法理解如何使用它们,要么它们实际上根本不像广告宣传的那样工作。你可以通过 Visual Studio 中的“工具”选项通过 NuGet 包管理器获取此包,然后选择“浏览”,键入“Mouse”,你应该找到正确的包。

我不确定如何添加图像:所以我将粘贴整个应用程序如下。

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


   }
}

希望这没问题。你可以下载整个项目并从 debug 文件夹运行 Rodent.exe。然后按任意 CTRL 键。

关注点

在开发这个应用程序的过程中,我学到了很多。让我困惑的一个主要问题是,播放 .mp4、gif 甚至 .AVI 文件不起作用,因为它们直到播放完毕才将系统返回给我。

这就是我创建以下方法的原因

getFrames(Rodent.Properties.Resources.SmallExplosion);

它将我嵌入的 .Gif 文件分解为帧,并允许我一次显示一张图像。在每个帧之间使用少量 .DoEvents(),我能够获得我需要的性能。

.ZIP 文件包含运行应用程序所需的一切,当然还有源代码。

© . All rights reserved.