C# 编写一个简单易用的 Windows 截屏增强工具
半年前我开源了 DreamScene2 一个小而快并且功能强大的 Windows 动态桌面软件。有很多的人喜欢,这使我有了继续做开源的信心。这是我的第二个开源作品 ScreenshotEx 一个简单易用的 Windows 截屏增强工具。
欢迎 Star 和 Fork https://github.com/he55/ScreenshotEx
前言
在使用 Windows 系统的截屏快捷键 PrintScreen 截屏时,如果需要把截屏保存到文件,需要先粘贴到画图工具然后另存为文件。以前我还没有觉得很麻烦,后来使用了 macOS 系统的截屏工具,我才知道原来一个小小的截屏工具也可以这么简单易用。于是参考 macOS 系统的截屏工具做了一个 Windows 版的。
功能
自动保存截屏到桌面

点击截屏预览可以编辑截屏

实现原理
如果想在按下系统的截屏快捷键后做一些事情,能想到的方法应该就是如何监听键盘事件。WIN32 API 提供的 SetWindowsHookExA 钩子函数刚好可以实现这个需求,idHook 参数设置成 WH_KEYBOARD_LL 时是低等级键盘钩子可以捕获键盘消息。
SetWindowsHookExA 函数定义
HHOOK SetWindowsHookExA(
[in] int idHook, // 钩子类型
[in] HOOKPROC lpfn, // 钩子处理函数
[in] HINSTANCE hmod, // 模块句柄
[in] DWORD dwThreadId // 线程Id
);
键盘处理函数定义
LRESULT CALLBACK LowLevelKeyboardProc(
_In_ int nCode,
_In_ WPARAM wParam, // 键盘消息
_In_ LPARAM lParam // KBDLLHOOKSTRUCT 结构体指针
);
代码
C# PInvoke 定义
const int HC_ACTION = 0;
const int WH_KEYBOARD_LL = 13;
const int WM_KEYUP = 0x0101;
const int WM_SYSKEYUP = 0x0105;
const int VK_SNAPSHOT = 0x2C;
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
public struct KBDLLHOOKSTRUCT
{
public uint vkCode;
public uint scanCode;
public uint flags;
public uint time;
public UIntPtr dwExtraInfo;
}
[UnmanagedFunctionPointer(CallingConvention.Winapi)]
public delegate IntPtr HookProc(int nCode, IntPtr wParam, ref KBDLLHOOKSTRUCT lParam);
[DllImport("User32.dll", SetLastError = true, CharSet = CharSet.Auto)]
public static extern IntPtr SetWindowsHookEx(int idHook, HookProc lpfn, IntPtr hmod, int dwThreadId);
[DllImport("User32.dll", SetLastError = true, ExactSpelling = true)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool UnhookWindowsHookEx(IntPtr hhk);
[DllImport("User32.dll", SetLastError = false, ExactSpelling = true)]
public static extern IntPtr CallNextHookEx(IntPtr hhk, int nCode, IntPtr wParam, ref KBDLLHOOKSTRUCT lParam);
[DllImport("Kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)]
public static extern IntPtr GetModuleHandle([Optional] string lpModuleName);
注册键盘钩子
需要注意:因为 SetWindowsHookEx 是非托管函数第二个参数是个委托类型,GC 不会记录非托管函数对 .NET 对象的引用。如果用临时变量保存委托出作用域就会被 GC 释放,当 SetWindowsHookEx 去调用已经被释放的委托就会报错。
SetWindowsHookEx 函数第一个参数传 WH_KEYBOARD_LL 低等级键盘钩子、第二个参数传键盘消息处理函数的委托、第三个参数使用 GetModuleHandle 函数获取模块句柄、第四个参数传 0。
HookProc _hookProc;
IntPtr _hhook;
void StartHook()
{
_hookProc = new HookProc(LowLevelKeyboardProc); // 使用成员变量保存委托
_hhook = SetWindowsHookEx(WH_KEYBOARD_LL, _hookProc, GetModuleHandle(null), 0); // 注册键盘钩子,保存返回值卸载钩子时用到。GetModuleHandle(null) 获取当前模块句柄
}
键盘消息处理函数
在键盘消息处理函数里面捕获 PrintScreen 按键消息,然后显示预览和保存图片逻辑
IntPtr LowLevelKeyboardProc(int nCode, IntPtr wParam, ref KBDLLHOOKSTRUCT lParam)
{
if (nCode == HC_ACTION)
{
if (lParam.vkCode == VK_SNAPSHOT) // 捕获 PrintScreen 按键消息
{
if ((int)wParam == WM_KEYUP || (int)wParam == WM_SYSKEYUP) // 按键释放时保存图片
SaveImage();
else
_previewWindow.SetHide();
}
}
return CallNextHookEx(_hhook, nCode, wParam, ref lParam);
}
保存图片
从系统剪贴板获取图片
void SaveImage()
{
if (Clipboard.ContainsImage())
{
if (!Directory.Exists(_settings.SavePath))
Directory.CreateDirectory(_settings.SavePath);
string ext = "png";
ImageFormat imageFormat = ImageFormat.Png;
switch (_settings.SaveExtension)
{
case 0:
imageFormat = ImageFormat.Png;
ext = "png";
break;
case 1:
imageFormat = ImageFormat.Jpeg;
ext = "jpg";
break;
case 2:
imageFormat = ImageFormat.Bmp;
ext = "bmp";
break;
}
if (_settings.SaveName == 0)
{
string name = DateTime.Now.ToString("yyyy-MM-dd HH.mm.ss");
_saveFilePath = Path.Combine(_settings.SavePath, $"{PrefixName} {name}.{ext}");
}
else
{
do
{
_saveFilePath = Path.Combine(_settings.SavePath, $"{PrefixName} {_nameIndex}.{ext}");
_nameIndex++;
} while (File.Exists(_saveFilePath));
}
Image image = Clipboard.GetImage();
image.Save(_saveFilePath, imageFormat);
if (_settings.IsPlaySound)
_soundPlayer.Play();
if (_settings.IsShowPreview)
_previewWindow.SetImage(_saveFilePath);
}
}
完整代码 https://github.com/he55/ScreenshotEx
C# 编写一个简单易用的 Windows 截屏增强工具的更多相关文章
- 编写一个简单的C++程序
编写一个简单的C++程序 每个C++程序都包含一个或多个函数(function),其中一个必须命名为main.操作系统通过调用main来运行C++程序.下面是一个非常简单的main函数,它什么也不干, ...
- 一个简单易用的容器管理平台-Humpback
什么是Humpback? 在回答这个问题前,我们得先了解下什么的 Docker(哦,现在叫 Moby,文中还是继续称 Docker). 在 Docker-百度百科 中,对 Docker 已经解释得很清 ...
- 编写一个简单的Web Server
编写一个简单的Web Server其实是轻而易举的.如果我们只是想托管一些HTML页面,我们可以这么实现: 在VS2013中创建一个C# 控制台程序 编写一个字符串扩展方法类,主要用于在URL中截取文 ...
- 使用CEF(二)— 基于VS2019编写一个简单CEF样例
使用CEF(二)- 基于VS2019编写一个简单CEF样例 在这一节中,本人将会在Windows下使用VS2019创建一个空白的C++Windows Desktop Application项目,逐步进 ...
- 分享一个简单易用的RPC开源项目—Tatala
http://zijan.iteye.com/blog/2041894 这个项目最早(2008年)是用于一个网络游戏的Cache Server,以及一个电子商务的Web Session服务.后来不断增 ...
- 使用Java编写一个简单的Web的监控系统cpu利用率,cpu温度,总内存大小
原文:http://www.jb51.net/article/75002.htm 这篇文章主要介绍了使用Java编写一个简单的Web的监控系统的例子,并且将重要信息转为XML通过网页前端显示,非常之实 ...
- javascript编写一个简单的编译器(理解抽象语法树AST)
javascript编写一个简单的编译器(理解抽象语法树AST) 编译器 是一种接收一段代码,然后把它转成一些其他一种机制.我们现在来做一个在一张纸上画出一条线,那么我们画出一条线需要定义的条件如下: ...
- Java入门篇(一)——如何编写一个简单的Java程序
最近准备花费很长一段时间写一些关于Java的从入门到进阶再到项目开发的教程,希望对初学Java的朋友们有所帮助,更快的融入Java的学习之中. 主要内容包括JavaSE.JavaEE的基础知识以及如何 ...
- 用 Go 编写一个简单的 WebSocket 推送服务
用 Go 编写一个简单的 WebSocket 推送服务 本文中代码可以在 github.com/alfred-zhong/wserver 获取. 背景 最近拿到需求要在网页上展示报警信息.以往报警信息 ...
随机推荐
- Python - numpy.clip()函数
np.clip( a, a_min, a_max, out=None): 部分参数解释: 该函数的作用是将数组a中的所有数限定到范围a_min和a_max中.a:输入矩阵:a_min:被限定的最小值, ...
- 时间工具类之"获取相差天数"
一.时间工具类DateUtils之"获取相差天数" 1 /** 2 * 相差天数 3 * 4 * <p>TODO 方法功能描述 5 * 6 * @param start ...
- 位运算符 按位与 &——整数n的二进制数中1的个数
整数n的二进制数中1的个数 编写一个函数,输入是一个整数,返回其二进制表达式中数字位数为 '1' 的个数 代码如下: int func(int n)//char ch { int count = 0; ...
- 二、cadence焊盘与封装制作操作步骤详细说明
一.焊盘制作 1.打开Pad Designer软件,新建文件--设置保存路径和焊盘名称(规范命名) 2.Parameters--设置单位--过孔类型--是否镀金 3.Layers--single la ...
- 每天坚持一个CSS——社会人
每天一个CSS-社会人 实现效果 想法 之前看到一篇博客,使用python绘制出了小猪佩奇,所以自己想试一试,采用纯html + CSS绘制出低配版的小猪佩奇. 实现思路 使用上一篇,圆与边框实现.最 ...
- webSocket原理探索
本文概述 Web Sockets的目标是在一个单独的持久连接上提供全双工.双向通信.在Javascript创建了Web Socket之后,会有一个HTTP请求发送到浏览器以发起连接.在取得服务器响应后 ...
- 前端网络安全——前端XSS
XSS攻击:Cross Site Scripting(跨站脚本攻击) XSS攻击原理:程序+数据=结果,如果数据中包含了一部分程序,那么结果就会执行不属于站点的程序. XSS攻击能干什么?能注入Scr ...
- vue里面v-for显示红色波浪线
vue里面使用v-for代码显示红色的波浪线,解决办法: before: <div v-for="tmsgs in msg.message"></div> ...
- 《头号玩家》AI电影调研报告(一)
观<头号玩家>AI电影调研报告 一. 前言 有一部电影,上映开始就能让世界各不同年龄段.身处不同文化的人在一瞬间达到心意相通:其中的一些镜头,让影迷.游戏迷.ACG爱好者等拥有截然不同兴趣 ...
- Hadoop-集群运行
目录 步骤一.NameNode 格式化 步骤二.启动 NameNode 步骤三.启动 SecondaryNameNode 步骤四.slave 启动 DataNode 步骤五.查看 HDFS 的报告 步 ...