既见君子丶云胡不喜 發表於 2022-5-11 14:12:00

C# 编写一个简单易用的 Windows 截屏增强工具

<p>半年前我开源了 DreamScene2 一个小而快并且功能强大的 Windows 动态桌面软件。有很多的人喜欢,这使我有了继续做开源的信心。这是我的第二个开源作品 ScreenshotEx 一个简单易用的 Windows 截屏增强工具。</p>
<p>欢迎 Star 和 Fork https://github.com/he55/ScreenshotEx</p>
<h2 id="前言">前言</h2>
<p>在使用 Windows 系统的截屏快捷键 <code>PrintScreen</code> 截屏时,如果需要把截屏保存到文件,需要先粘贴到画图工具然后另存为文件。以前我还没有觉得很麻烦,后来使用了 macOS 系统的截屏工具,我才知道原来一个小小的截屏工具也可以这么简单易用。于是参考 macOS 系统的截屏工具做了一个 Windows 版的。</p>
<h2 id="功能">功能</h2>
<ul>
<li>
<p>自动保存截屏到桌面</p>
<p><img src="https://img2022.cnblogs.com/blog/1136046/202205/1136046-20220510211757828-1081798859.gif"></p>
</li>
<li>
<p>点击截屏预览可以编辑截屏</p>
<p><img src="https://img2022.cnblogs.com/blog/1136046/202205/1136046-20220510211822963-458934995.gif"></p>
</li>
</ul>
<h2 id="实现原理">实现原理</h2>
<p>如果想在按下系统的截屏快捷键后做一些事情,能想到的方法应该就是如何监听键盘事件。WIN32 API 提供的 SetWindowsHookExA 钩子函数刚好可以实现这个需求,<code>idHook</code> 参数设置成 <code>WH_KEYBOARD_LL</code> 时是低等级键盘钩子可以捕获键盘消息。</p>
<p><code>SetWindowsHookExA</code> 函数定义</p>
<pre><code class="language-cpp">HHOOK SetWindowsHookExA(
int       idHook,    // 钩子类型
HOOKPROClpfn,      // 钩子处理函数
HINSTANCE hmod,      // 模块句柄
DWORD   dwThreadId // 线程Id
);
</code></pre>
<p>键盘处理函数定义</p>
<pre><code class="language-cpp">LRESULT CALLBACK LowLevelKeyboardProc(
_In_&nbsp;int &nbsp;&nbsp;&nbsp;nCode,
_In_&nbsp;WPARAM wParam, // 键盘消息
_In_&nbsp;LPARAM lParam // KBDLLHOOKSTRUCT 结构体指针
);
</code></pre>
<h2 id="代码">代码</h2>
<p><strong>C# PInvoke 定义</strong></p>
<pre><code class="language-cs">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;


public struct KBDLLHOOKSTRUCT
{
    public uint vkCode;
    public uint scanCode;
    public uint flags;
    public uint time;
    public UIntPtr dwExtraInfo;
}


public delegate IntPtr HookProc(int nCode, IntPtr wParam, ref KBDLLHOOKSTRUCT lParam);


public static extern IntPtr SetWindowsHookEx(int idHook, HookProc lpfn, IntPtr hmod, int dwThreadId);



public static extern bool UnhookWindowsHookEx(IntPtr hhk);


public static extern IntPtr CallNextHookEx(IntPtr hhk, int nCode, IntPtr wParam, ref KBDLLHOOKSTRUCT lParam);


public static extern IntPtr GetModuleHandle( string lpModuleName);
</code></pre>
<p><strong>注册键盘钩子</strong></p>
<p>需要注意:因为 <code>SetWindowsHookEx</code> 是非托管函数第二个参数是个委托类型,<code>GC</code> 不会记录非托管函数对 <code>.NET</code> 对象的引用。如果用临时变量保存委托出作用域就会被 <code>GC</code> 释放,当 <code>SetWindowsHookEx</code> 去调用已经被释放的委托就会报错。</p>
<p><code>SetWindowsHookEx</code> 函数第一个参数传 <code>WH_KEYBOARD_LL</code> 低等级键盘钩子、第二个参数传键盘消息处理函数的委托、第三个参数使用 <code>GetModuleHandle</code> 函数获取模块句柄、第四个参数传 0。</p>
<pre><code class="language-cs">HookProc _hookProc;
IntPtr _hhook;

void StartHook()
{
    _hookProc = new HookProc(LowLevelKeyboardProc); // 使用成员变量保存委托
    _hhook = SetWindowsHookEx(WH_KEYBOARD_LL, _hookProc, GetModuleHandle(null), 0); // 注册键盘钩子,保存返回值卸载钩子时用到。GetModuleHandle(null) 获取当前模块句柄
}
</code></pre>
<p><strong>键盘消息处理函数</strong></p>
<p>在键盘消息处理函数里面捕获 <code>PrintScreen</code> 按键消息,然后显示预览和保存图片逻辑</p>
<pre><code class="language-cs">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);
}
</code></pre>
<p><strong>保存图片</strong></p>
<p>从系统剪贴板获取图片</p>
<pre><code class="language-cs">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);
    }
}
</code></pre>
<p>完整代码 https://github.com/he55/ScreenshotEx</p><br><br>
来源:https://www.cnblogs.com/he55/p/16253321.html
頁: [1]
查看完整版本: C# 编写一个简单易用的 Windows 截屏增强工具