C# 編寫一個簡單易用的 Windows 截屏增強工具

来源:https://www.cnblogs.com/he55/archive/2022/05/11/16253321.html
-Advertisement-
Play Games

半年前我開源了 DreamScene2 一個小而快並且功能強大的 Windows 動態桌面軟體。有很多的人喜歡,這使我有了繼續做開源的信心。這是我的第二個開源作品 ScreenshotEx 一個簡單易用的 Windows 截屏增強工具。 歡迎 Star 和 Fork https://github.c ...


半年前我開源了 DreamScene2 一個小而快並且功能強大的 Windows 動態桌面軟體。有很多的人喜歡,這使我有了繼續做開源的信心。這是我的第二個開源作品 ScreenshotEx 一個簡單易用的 Windows 截屏增強工具。

歡迎 Star 和 Fork https://github.com/he55/ScreenshotEx

前言

在使用 Windows 系統的截屏快捷鍵 PrintScreen 截屏時,如果需要把截屏保存到文件,需要先粘貼到畫圖工具然後另存為文件。以前我還沒有覺得很麻煩,後來使用了 macOS 系統的截屏工具,我才知道原來一個小小的截屏工具也可以這麼簡單易用。於是參考 macOS 系統的截屏工具做了一個 Windows 版的。

功能

  • 自動保存截屏到桌面

    img

  • 點擊截屏預覽可以編輯截屏

    img

實現原理

如果想在按下系統的截屏快捷鍵後做一些事情,能想到的方法應該就是如何監聽鍵盤事件。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


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 📕併發基本概念以及實現、進程、線程基本概念 一、併發、進程、線程的基本概念和綜述 這些詳細概念具體去看os筆記; 1.1 併發 兩個或者更多的任務(獨立的活動)同時發生(進行):一個程式同時執行多個獨立的任務; 以往電腦,單核cpu(中央處理器):某一個時刻只能執行一個任務,由操作系統調度,每秒 ...
  • 來源:toutiao.com/i6698255904053133827 這是一位讀者帶回來的面試題 Nginx 是如何實現併發的?為什麼 Nginx 不使用多線程?Nginx常見的優化手段有哪些?502錯誤可能原因有哪些? 面試官心理分析 主要是看應聘人員的對NGINX的基本原理是否熟悉,因為大多數 ...
  • @ 一、前言 我們先來聊聊消息中間件: 消息中間件利用高效可靠的消息傳遞機制進行平臺無關的數據交流,並基於數據通信來進行分散式系統的集成。通過提供消息傳遞和消息排隊模型,它可以在分散式環境下擴展進程間的通信。(來自百度百科) 我們常見的中間件其實有很多種了,例如ActiveMQ、RabbitMQ、R ...
  • 介紹 powerjob提供了容器功能,用來做一些靈活的任務處理。這裡容器為 JVM 級容器,而不是操作系統級容器(Docker)。(至於為什麼取“容器”這個有歧義的名字是因為作者沒想出來更合適的稱呼,哈哈) 用途 有一些任務完全獨立於業務,代碼量也不大,既不希望耦合於原業務代碼,也不值得再搞一套新建 ...
  • 一、問題描述 1、項目需求要求使用PHP8.1.*版本 2、運行程式發現驗證碼不生效報錯如下: 二、錯誤描述 1、報錯信息得出:從浮點(數字)到整數的隱式轉換將失去精度 三、解決流程 1、找到報錯文件位置 vendor\topthink\think-captcha\src\Captcha.php l ...
  • 桑基圖,它的核心是對不同點之間,通過線來連接。線的粗細代表流量的大小。很多工具都能實現桑基 圖,比如:Excel、tableau,我們今天要用 Pyecharts 來繪製。 因為沒有用戶行為路徑相關的公開數據,所以本次實現可視化是根據泰坦尼克號,其生存與遇難的人的 數據,來分析流向路徑。學會思路,你 ...
  • 一個工作3年的小伙子,去面試被問到Spring裡面的問題。 這個問題比較簡單,但是他卻沒有回答上來。 雖然他可以通過搜索引擎找到答案,但是如果沒有理解,下次面試還是不會! 這個面試題是: “Spring中的Bean,作用域有哪些?” 對於這個問題,看看普通人和高手的回答。 普通人: 嗯。。。。。。。 ...
  • 你是否有遇到過這樣的情況,在開發過程中需要比較兩列數據,但使用文本比對工具的話他是按行基準比對的,我還得對每列數據先進行排序,但排序又去哪裡排, 想到 excel 可以排序 , 折騰下來,特別麻煩, 不知道為啥這麼一個小工具都沒有人提供, 這裡 sanri-tools-maven 提供了這個小工具, ...
一周排行
    -Advertisement-
    Play Games
  • 在一些複雜的業務表中間查詢數據,有時候操作會比較複雜一些,不過基於SqlSugar的相關操作,處理的代碼會比較簡單一些,以前我在隨筆《基於SqlSugar的開發框架循序漸進介紹(2)-- 基於中間表的查詢處理》介紹過基於主表和中間表的聯合查詢,而往往實際會比這個會複雜一些。本篇隨筆介紹聯合多個表進行... ...
  • 從按鈕、文本框到下拉框、列表框,WPF提供了一系列常用控制項,每個控制項都有自己獨特的特性和用途。通過靈活的佈局容器,如網格、堆棧面板和換行面板,我們可以將這些控制項組合在一起,實現複雜的界面佈局。而通過樣式和模板,我們可以輕鬆地定製控制項的外觀和行為,以符合我們的設計需求。本篇記錄WPF入門需要瞭解的樣式... ...
  • 以MySQL資料庫為例 # 一. 安裝 NuGet搜索Dapper.Lite並安裝最新版本。 ![](https://img2023.cnblogs.com/blog/174862/202306/174862-20230602155913303-757935399.jpg) NuGet搜索MySql ...
  • # 圖片介面JWT鑒權實現 # 前言 之前做了個返回圖片鏈接的介面,然後沒做授權,然後今天鍵盤到了,也是用JWT來做介面的許可權控制。 然後JTW網上已經有很多文章來說怎麼用了,這裡就不做多的解釋了,如果不懂的可以參考下列鏈接的 文章。 圖片介面文章:[還在愁個人博客沒有圖片放?](https://w ...
  • ![線程各屬性縱覽](https://img2023.cnblogs.com/blog/1220983/202306/1220983-20230603114109107-477345835.png) 如上圖所示,線程有四個屬性: - 線程ID - 線程名稱 - 守護線程 - 線程優先順序 ### 1. ...
  • 本次主要介紹golang中的標準庫`bytes`,基本上參考了 [位元組 | bytes](https://cloud.tencent.com/developer/section/1140520) 、[Golang標準庫——bytes](https://www.jianshu.com/p/e6f7f2 ...
  • 歡迎來到本篇文章!通過上一篇什麼是 Spring?為什麼學它?的學習,我們知道了 Spring 的基本概念,知道什麼是 Spring,以及為什麼學習 Spring。今天,這篇就來說說 Spring 中的核心概念之一 IoC。 ...
  • # 2022版本IDEA+Maven+Tomcat的第一個程式(傻瓜教學) ​ 作為學習Javaweb的一個重要環節,如何實現在IDEA中利用Maven工具創建一個Javaweb程式模版並連接Tomcat發佈是非常重要的。我比較愚鈍(小白),而且自身電腦先前運行過spring或maven的程式,系統 ...
  • 本篇專門扯一下有關 QCheckBox 組件的一個問題。老周不水字數,直接上程式,你看了就明白。 #include <QApplication> #include <QWidget> #include <QPushButton> #include <QCheckBox> #include <QVBo ...
  • # 1.列表數據元素排序 在創建的列表中,數據元素的排列順序常常是無法預測的。這雖然在大多數情況下都是不可避免的,但經常需要以特定的順序呈現信息。有時候希望保留列表數據元素最初的排列順序,而有時候又需要調整排列順序。python提供了很多列表數據元素排序的方式,可根據情況選用。 ## 1.永久性排序 ...