创建一个显示鼠标位置的系统托盘应用

在Windows操作系统中,用户可以通过鼠标属性设置来显示鼠标指针的位置。然而,对于拥有4K显示屏的用户来说,这种显示方式可能不够直观。因此,决定为弟弟编写一个更直观的应用程序,名为“Rodent”,它能够在按下CTRL键时在鼠标指针位置显示爆炸效果。

这个任务比预想的要复杂得多。目标是创建一个Windows系统托盘应用程序,它能够:

  • 创建系统托盘应用程序
  • 创建一个系统范围的键盘/鼠标钩子,以便能够捕获CTRL键
  • 在鼠标指针位置显示爆炸效果

以下是实现这个功能的主要方法:

主要方法

RodentTray() - 这是程序的入口点,它订阅键盘和鼠标事件,并调用InitializeComponent()方法来设置托盘图标的菜单项。

QuitMenuItem_Click(object sender, EventArgs e) - 这个方法取消订阅键盘和鼠标钩子,并退出应用程序。

CreateTheBombForm() - 这个方法创建用于显示爆炸效果的窗体。

ShowBomb(Form bombForm) - 这个方法显示窗体并展示图像,播放完最后一帧后释放所有资源。

要使用这些代码,需要在项目中添加以下引用:

using Gma.System.MouseKeyHook;

以下是确保应用程序只运行一个实例的类:

public class SingleInstance { public static void SingleInst() { if (Process.GetProcessesByName(Process.GetCurrentProcess().ProcessName).Length > 1) { Process.GetCurrentProcess().Kill(); } } }

为了创建一个真正的系统托盘应用程序,了解到不应该让C#创建默认的Form1,而是创建一个类作为应用程序的入口点,并且它是一个ApplicationContext。

接下来,需要找到一种方法来在系统级别捕获鼠标和键盘。找到了一个非常好的解决方案,那就是使用NuGet包。非常感谢George Mamaladze为这项艰巨任务所做的工作。可以通过Visual Studio的NuGet包管理器来获取这个包。

代码实现

在多次尝试之后,创建了一个类,确保应用程序只运行一个实例。

namespace Rodent { static class Program { [STAThread] static void Main() { SingleInstance.SingleInst(); Application.EnableVisualStyles(); Application.SetCompatibleTextRenderingDefault(false); Application.Run(new Rodent.Classes.RodentTray()); } } }

以下是应用程序的主类:

class RodentTray : ApplicationContext { // 全局变量 public NotifyIcon RodentIcon; private IKeyboardMouseEvents m_Events; public int Rodent_X = 0; public int Rodent_Y = 0; public ImageList ImageBomb = new ImageList(); public RodentTray() { InitializeComponent(); RodentIcon.Icon = Rodent.Properties.Resources.Rodent; RodentIcon.Visible = true; Subscribe(Hook.GlobalEvents()); } private void InitializeComponent() { // 创建上下文菜单 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; } 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.Length; imgCount++) { ImageBomb.Images.Add(frames[imgCount]); } Graphics theGraphics = Graphics.FromHwnd(bombForm.Handle); for (int imgCount = 0; imgCount < frames.Length; 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; } 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(); } } }

在开发这个应用程序的过程中,学到了很多。一个让困惑的主要问题是播放.mp4、gif或.avi文件时,它们不会将系统控制权交还给,直到它们播放完毕。这就是创建以下方法的原因:

Image[] getFrames(Image originalImg) { // 将动画.gif分解成帧 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; }
沪ICP备2024098111号-1
上海秋旦网络科技中心:上海市奉贤区金大公路8218号1幢 联系电话:17898875485