QQ 截图启动器

看雪论坛 0xEEEE逆向调用QQ截图NT与WeChatOCR这篇文章中发布了调用新版 QQ 截图功能的软件 QQScreenShotNT-Plus,虽然已经设置了 QQScreenshot.exe 的路径,但是每次启动这个软件都要提示是否重新自动获取,在设置中去掉“启动提示”依然无效。在那篇文章中作者说 QQScreenShotNT-Plus 是在 QQImpl 基础上实现的,但是作者没有发布对应的源代码,因此我准备基于 Windows Forms 技术复刻相关的功能。因为是第一次使用 .NET 相关技术开发 Windows 系统的应用软件,所以会详细地记录整个开发过程。源代码在 QQScreenShotLauncher

创建解决方案

这部分内容参考项目和解决方案简介。启动 Visual Studio 2022,在 Visual Studio 2022 对话框选择 Create a new project

Create a new project 对话框的在搜索框输入 solution,选择语言平台项目类型,然后选择 Blank Solution

Configure your new project 对话框输入解决方案名称 QQScreenShotLauncher,保存位置 D:\projects\dotnet\

创建 Git 仓库

这部分内容参考从 Visual Studio 创建 Git 存储库。在 Git 变更选择 Create Git Repository

Create a Git repository 对话框选择 Local onlyLocal path.gitignore templateLicense template 三项使用默认值,勾选 Add a README.md 复选框

创建项目

这部分内容参考项目和解决方案简介。在解决方案资源管理器中,右键单击 QQScreenShotLauncher 解决方案,依次选择 AddNew Project

在搜索框输入 Windows Forms App,选择语言平台项目类型,然后选择 Windows Forms App

Configure your new project 对话框输入项目名称 NTLauncher,保存位置保持默认值

Additional information 对话框选择框架版本 .NET 8.0 (Long Term Support)

添加图标资源

这部分参考管理应用程序资源。在解决方案资源管理器中,右键单击 NTLauncher 项目,选择 Properties

选中 Resources > General,点击 Create or open assembly resources 链接

Resource Explorer 点击加号按钮

Add a new resource 对话框,Type 下拉框选择 Icon,其他保持默认,然后点击 Add existing file 按钮

在文件选择对话框中选择 NTLauncher.ico(这个文件来自 0xEEEE逆向调用QQ截图NT与WeChatOCR 提供的 QQScreenShotNT-Lite.zip 压缩包)

Add a new resource 对话框,Neutral value 就是刚刚选择的文件,Store as 下拉框选择 System.Drawing.Icon (Windows Forms),其他保持默认

设置应用程序图标

这部分参考指定应用程序图标 (Visual Basic, C#)。在解决方案资源管理器中,右键单击 NTLauncher 项目,选择 Properties

选中 Application > Win32 Resources,点击 Browse 按钮

在文件选择对话框中选择 NTLauncher.ico,注意选择文件所在的目录,这个文件是上一步添加图标资源添加到项目的 Resources 目录的

文件选择完成后 Icon 项的值为 Resources\NTLauncher.ico

添加系统托盘和右键菜单

这部分主要参考了以下资料

  1. NotifyIcon 组件概述(Windows 窗体)
  2. 如何使用 Windows 窗体 (Windows Forms) NotifyIcon 组件向任务栏添加应用程序图标
  3. 如何:将快捷菜单与 Windows 窗体 NotifyIcon 组件相关联
  4. 如何将 ContextMenuStrip 与控件关联
  5. 如何:向 ContextMenuStrip 添加菜单项
  6. NotifyIcon 类
  7. ContextMenuStrip 类
  8. winform实现最小化至系统托盘
  9. C#实现 Winform 程序在系统托盘显示图标 & 开机自启动
  10. WinForm窗体隐藏任务栏图标和系统托盘显示图标

添加 NotifyIcon 和 ContextMenuStrip 控件

Toolbox 搜索 NotifyIcon,将 NotifyIcon 控件拖入 Form1

ContextMenuStrip 控件进行类似的操作,控件添加完成后

配置 NotifyIcon 图标

选中 notifyIcon1 控件,在 Properties 找到 Appearance > Icon,点击右侧 按钮

在文件选择对话框中选择 NTLauncher.ico,注意选择文件所在的目录,这个文件是在添加图标资源添加到项目的 Resources 目录的

配置 NotifyIcon 右键菜单

选中 notifyIcon1 控件,在 Properties 找到 Behavior > ContextMenuStrip,在右侧下来选项中选择 contextMenuStrip1

配置 NotifyIcon 其他属性

选中 notifyIcon1 控件,配置 Appearance > TextNTLauncher,配置 Behavior > VisibleTrue

notifyIcon1Visible 配合 Form1ShowInTaskbarWindowState 就可以实现启动应用程序只显示托盘图标的功能。

添加右键菜单项

选中 contextMenuStrip1 控件,点击右上角三角形图标,在弹出的 ContextMenuStrip Tasks 中点击 Edit Items

Items Collection Editor 对话框选中 contextMenuStrip1 成员,选择 MenuItem,点击 Add 按钮添加菜单项

Items Collection Editor 对话框选中 contextMenuStrip1 成员,选择 Separator,点击 Add 按钮添加分割线

我们将添加 7 个菜单项,3 条分割线,通过右侧的功能按钮排序删除

选中 toolStripMenuItem1,修改 Appearance > Text 的值为关于

对其他菜单项进行类似操作,编辑完成后的 Form1

使应用程序只显示托盘图标

选中 Form1 控件,设置 Layout > WindowStateMinimized,设置 Window Style > ShowInTaskbarFalse

添加托盘退出事件

选中 contextMenuStrip1 控件,然后选中退出菜单项,配置 Action > ClicktoolStripMenuItem7_Click

上面的操作会自动在 Form1.cs 中创建函数 toolStripMenuItem7_Click,其实现为

1
2
3
4
private void toolStripMenuItem7_Click(object sender, EventArgs e)
{
Application.Exit();
}

添加设置对话框

这部分内容主要参考了以下资料

  1. C# Winform同一子窗体只允许打开一次
  2. WinForm窗体应用——父窗体每次只打开一个子窗体的方法
  3. c# WinForm 点击出现弹出多个窗体, 怎么才能只显示一个窗体。解决方案
  4. C# WinForm 点击按钮显示唯一窗体
  5. C#窗体程序(winform)禁止最小化、最大化,或去掉关闭按钮
  6. WinForm 设置窗体启动位置在活动屏幕右下角

新增设置对话框

NTLauncher 项目上单击右键,在弹出菜单中依次选择 Add > New Items

Add New Item 对话框中选中 Installed > C# Items,在右侧选中 Form (Windows Forms),注意下方 Name 的值 Form2.cs

点击设置菜单项显示设置对话框

Form1 选中 contextMenuStrip1 控件,然后选中设置菜单项,配置 Action > ClicktoolStripMenuItem2_Click

上面的操作会自动在 Form1.cs 中创建函数 toolStripMenuItem2_Click,其实现为

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
private Form2? form2 = null;

private void toolStripMenuItem2_Click(object sender, EventArgs e)
{
if (form2 == null || form2.IsDisposed)
{
form2 = new Form2();
form2.FormClosed += (s, _) =>
{
form2?.Dispose(); // 显式释放资源
form2 = null; // 强制置空
};
form2.ShowDialog();
}
else
{
form2.Activate();
}
}

在这个实现里增加了一个变量 form2 来表示打开的设置对话,对话框关闭时把该变量置空,这保证多次点击设置菜单只打开一个对话框。

配置设置对话框样式

选中 Form2,设置 Appearance > Text设置,设置 Window Style > MaximizeBoxFalse,设置 Window Style > MinimizeBoxFalse,设置 Window Style > ShowIconFalse,设置 Window Style > ShowInTaskbarFalse

设置对话框启动位置在屏幕右下角

选中 Form2,设置 Layout > StartPositionManual

Form2.cs 中重新计算对话框的位置

1
2
3
4
5
6
7
8
9
public Form2()
{
InitializeComponent();

Screen screen = Screen.FromPoint(new Point(Cursor.Position.X, Cursor.Position.Y));
int x = screen.WorkingArea.X + screen.WorkingArea.Width - this.Width;
int y = screen.WorkingArea.Y + screen.WorkingArea.Height - this.Height;
this.Location = new Point(x, y);
}

绘制对话框

这部分内容主要参考了以下资料

  1. 教程:使用 .NET 创建 Windows 窗体应用
  2. 教程:创建数学测验 WinForms 应用
  3. 控件的位置和布局(Windows 窗体 .NET)
  4. 演示:使用捕捉线排列 Windows 窗体上的控件
  5. 如何:在 Windows 窗体上对齐多个控件
  6. Winform禁止调整大小

主要用到的控件为 GroupBoxCheckBoxLabelTextBoxButton,成品图如下所示

选中 Form2,设置 Appearance > FormBorderStyleFixedSingle,禁止改变窗口大小