C# 使用WinApi操作剪切板Clipboard

来源:https://www.cnblogs.com/ACDIV/archive/2018/05/31/9114472.html
-Advertisement-
Play Games

前言: 最近正好寫一個程式,需要操作剪切板 功能很簡單,只需要從剪切板內讀取字元串,然後清空剪切板,然後再把字元串導入剪切板 我想當然的使用我最拿手的C#來完成這項工作,原因無他,因為.Net框架封裝了能實現這種功能的方法 然後就有瞭如下代碼 1 string Temp = ""; 2 while ...


前言:

最近正好寫一個程式,需要操作剪切板

功能很簡單,只需要從剪切板內讀取字元串,然後清空剪切板,然後再把字元串導入剪切板

我想當然的使用我最拿手的C#來完成這項工作,原因無他,因為.Net框架封裝了能實現這種功能的方法

然後就有瞭如下代碼

 1             string Temp = "";
 2             while (true)
 3             {
 4                 string Tex = Clipboard.GetText().ToString();
 5                 if (!string.IsNullOrWhiteSpace(Tex) && Temp != Tex)
 6                 {
 7                     Clipboard.Clear();
 8                     Clipboard.SetDataObject(Tex, false);
 9                     Temp = Tex;
10                 }
11                 Thread.Sleep(1);
12             }
View Code

這段代碼,也是網頁上廣泛流傳的,使用.Net框架操作系統剪切板的方法,當然這個方法在某些情況下很管用

不過在我這確發生了點問題,主要的問題有兩點

首先,我對剪切板的操作需求有實時性,也就是,操作人員複製的一瞬間就應該截取到剪切板的數據,處理完後再放入剪切板

結果

 Clipboard.SetDataObject(Tex, false);

沒想到上面這條設置剪切板的指令竟然會卡焦點視窗的線程,比如說,我在A軟體執行了一次複製操作,如果使用了上述代碼,那麼A軟體強制線程堵塞大概幾百毫秒的樣子,反正很影響體驗,我推測是因為該命令會鎖定記憶體導致的

那怎麼辦,本著死馬當活馬醫的態度,我專門為該指令啟用了一個線程

       Task.Factory.StartNew(()=> 
                    {
                        Clipboard.Clear();
                        Clipboard.SetDataObject(Text, false);
                    });

使用了線程以後,因為操作滯後(線程啟動會延遲一會兒,並不實時)了,所以上述問題似乎解決了,但是沒想到出現了新的問題

 string Tex = Clipboard.GetText().ToString();

上述從剪切板獲得字元串的指令,在默寫情況下,會卡滯住,然後程式在一分鐘之後,因為超時而被系統吊銷

emmmmm,在經過幾番努力之後,我終於意識到,雖然.Net封裝了不少操作系統API的方法,使得一些IO操作變簡單不少,但是帶來的問題也是同樣大的,在遇到無法解決的問題的時候,會有點束手無策

於是不得已,我只能放棄使用過C#完成該項功能,想著幸好功能簡單,而且操作WinAPI其實最好的還是使用C++來寫,於是我用C++復現了上述功能

 1 #include "stdafx.h"
 2 #include <windows.h>
 3 #include <iostream>
 4 using namespace std;
 5 #pragma comment(linker,"/subsystem:windows /entry:mainCRTStartup")
 6 
 7 int main(int argc, _TCHAR* argv[])
 8 {
 9     HANDLE THandle = GlobalAlloc(GMEM_FIXED, 1000);//分配記憶體
10     char* Temp = (char*)THandle;//鎖定記憶體,返回申請記憶體的首地址
11     while (true)
12     {
13         HWND hWnd = NULL;
14         OpenClipboard(hWnd);//打開剪切板
15         if (IsClipboardFormatAvailable(CF_TEXT))
16         {
17             HANDLE h = GetClipboardData(CF_TEXT);//獲取剪切板數據
18             char* p = (char*)GlobalLock(h);
19             GlobalUnlock(h);
20             if (strcmp(Temp, p))
21             {
22                 EmptyClipboard();//清空剪切板
23                 HANDLE hHandle = GlobalAlloc(GMEM_FIXED, 1000);//分配記憶體
24                 char* pData = (char*)GlobalLock(hHandle);//鎖定記憶體,返回申請記憶體的首地址
25                 strcpy(pData, p);
26                 strcpy(Temp, p);
27                 SetClipboardData(CF_TEXT, hHandle);//設置剪切板數據
28                 GlobalUnlock(hHandle);//解除鎖定
29             }
30         }
31         CloseClipboard();//關閉剪切板
32         Sleep(500);
33     }
34     return 0;
35 }
View Code

不愧是C++,使用上述代碼後,完美實現我需要的功能,而且不管是主程式,還是我寫的這個程式,都不會出現卡滯或者不工作的情況了,真是可喜可賀。

那麼本教程就到此為止。

 

 

以下是正文

想著,既然我能用C++調用WinAPI完美實現我需要的功能,而且C#也能調用非托管的代碼來執行WinAPI,那麼我不是可以把上面C++寫的代碼移植到C#裡面執行?說乾就乾

首先,C#調用WinAPI需要先申明

        [DllImport("User32")]
        internal static extern bool OpenClipboard(IntPtr hWndNewOwner);

        [DllImport("User32")]
        internal static extern bool CloseClipboard();

        [DllImport("User32")]
        internal static extern bool EmptyClipboard();

        [DllImport("User32")]
        internal static extern bool IsClipboardFormatAvailable(int format);

        [DllImport("User32")]
        internal static extern IntPtr GetClipboardData(int uFormat);

        [DllImport("User32", CharSet = CharSet.Unicode)]
        internal static extern IntPtr SetClipboardData(int uFormat, IntPtr hMem);

操作剪切板需要調用的API大致就上面這些

有了API以後,我們還需要自己手動封裝方法

     internal static void SetText(string text)
        {
            OpenClipboard(IntPtr.Zero);
            EmptyClipboard();
            SetClipboardData(13, Marshal.StringToHGlobalUni(text));
            CloseClipboard();
        }

        internal static string GetText(int format)
        {
            string value = string.Empty;
            OpenClipboard(IntPtr.Zero);
            if (IsClipboardFormatAvailable(format))
            {
                IntPtr ptr = NativeMethods.GetClipboardData(format);
                if (ptr != IntPtr.Zero)
                {
                    value = Marshal.PtrToStringUni(ptr);
                }
            }
            CloseClipboard();
            return value;
        }

我們也就用到兩個方法,從剪切板獲得文本和設置文本到剪切板,哦關於SetClipboardData的第一個參數13是怎麼來的問題,其實這個剪切板的格式參數,下麵有一張表,就是自從這裡來的

public static class ClipboardFormat
{
    /// <summary>
    /// Text format. Each line ends with a carriage return/linefeed (CR-LF) combination. A null character signals
    /// the end of the data. Use this format for ANSI text.
    /// </summary>
    public const int CF_TEXT = 1;

    /// <summary>
    /// A handle to a bitmap (<c>HBITMAP</c>).
    /// </summary>
    public const int CF_BITMAP = 2;

    /// <summary>
    /// Handle to a metafile picture format as defined by the <c>METAFILEPICT</c> structure. When passing a
    /// <c>CF_METAFILEPICT</c> handle by means of DDE, the application responsible for deleting <c>hMem</c> should
    /// also free the metafile referred to by the <c>CF_METAFILEPICT</c> handle.
    /// </summary>
    public const int CF_METAFILEPICT = 3;

    /// <summary>
    /// Microsoft Symbolic Link (SYLK) format.
    /// </summary>
    public const int CF_SYLK = 4;

    /// <summary>
    /// Software Arts' Data Interchange Format.
    /// </summary>
    public const int CF_DIF = 5;

    /// <summary>
    /// Tagged-image file format.
    /// </summary>
    public const int CF_TIFF = 6;

    /// <summary>
    /// Text format containing characters in the OEM character set. Each line ends with a carriage return/linefeed
    /// (CR-LF) combination. A null character signals the end of the data.
    /// </summary>
    public const int CF_OEMTEXT = 7;

    /// <summary>
    /// A memory object containing a <c>BITMAPINFO</c> structure followed by the bitmap bits.
    /// </summary>
    public const int CF_DIB = 8;

    /// <summary>
    /// Handle to a color palette. Whenever an application places data in the clipboard that depends on or assumes
    /// a color palette, it should place the palette on the clipboard as well. If the clipboard contains data in
    /// the <see cref="CF_PALETTE"/> (logical color palette) format, the application should use the
    /// <c>SelectPalette</c> and <c>RealizePalette</c> functions to realize (compare) any other data in the
    /// clipboard against that logical palette. When displaying clipboard data, the clipboard always uses as its
    /// current palette any object on the clipboard that is in the <c>CF_PALETTE</c> format.
    /// </summary>
    public const int CF_PALETTE = 9;

    /// <summary>
    /// Data for the pen extensions to the Microsoft Windows for Pen Computing.
    /// </summary>
    public const int CF_PENDATA = 10;

    /// <summary>
    /// Represents audio data more complex than can be represented in a CF_WAVE standard wave format.
    /// </summary>
    public const int CF_RIFF = 11;

    /// <summary>
    /// Represents audio data in one of the standard wave formats, such as 11 kHz or 22 kHz PCM.
    /// </summary>
    public const int CF_WAVE = 12;

    /// <summary>
    /// Unicode text format. Each line ends with a carriage return/linefeed (CR-LF) combination. A null character
    /// signals the end of the data.
    /// </summary>
    public const int CF_UNICODETEXT = 13;

    /// <summary>
    /// A handle to an enhanced metafile (<c>HENHMETAFILE</c>).
    /// </summary>
    public const int CF_ENHMETAFILE = 14;

    /// <summary>
    /// A handle to type <c>HDROP</c> that identifies a list of files. An application can retrieve information
    /// about the files by passing the handle to the <c>DragQueryFile</c> function.
    /// </summary>
    public const int CF_HDROP = 15;

    /// <summary>
    /// The data is a handle to the locale identifier associated with text in the clipboard. When you close the
    /// clipboard, if it contains <c>CF_TEXT</c> data but no <c>CF_LOCALE</c> data, the system automatically sets
    /// the <c>CF_LOCALE</c> format to the current input language. You can use the <c>CF_LOCALE</c> format to
    /// associate a different locale with the clipboard text.
    /// An application that pastes text from the clipboard can retrieve this format to determine which character
    /// set was used to generate the text.
    /// Note that the clipboard does not support plain text in multiple character sets. To achieve this, use a
    /// formatted text data type such as RTF instead. 
    /// The system uses the code page associated with <c>CF_LOCALE</c> to implicitly convert from
    /// <see cref="CF_TEXT"/> to <see cref="CF_UNICODETEXT"/>. Therefore, the correct code page table is used for
    /// the conversion.
    /// </summary>
    public const int CF_LOCALE = 16;

    /// <summary>
    /// A memory object containing a <c>BITMAPV5HEADER</c> structure followed by the bitmap color space
    /// information and the bitmap bits.
    /// </summary>
    public const int CF_DIBV5 = 17;

    /// <summary>
    /// Owner-display format. The clipboard owner must display and update the clipboard viewer window, and receive
    /// the <see cref="ClipboardMessages.WM_ASKCBFORMATNAME"/>, <see cref="ClipboardMessages.WM_HSCROLLCLIPBOARD"/>,
    /// <see cref="ClipboardMessages.WM_PAINTCLIPBOARD"/>, <see cref="ClipboardMessages.WM_SIZECLIPBOARD"/>, and
    /// <see cref="ClipboardMessages.WM_VSCROLLCLIPBOARD"/> messages. The <c>hMem</c> parameter must be <c>null</c>.
    /// </summary>
    public const int CF_OWNERDISPLAY = 0x0080;

    /// <summary>
    /// Text display format associated with a private format. The <c>hMem</c> parameter must be a handle to data
    /// that can be displayed in text format in lieu of the privately formatted data.
    /// </summary>
    public const int CF_DSPTEXT = 0x0081;

    /// <summary>
    /// Bitmap display format associated with a private format. The <c>hMem</c> parameter must be a handle to
    /// data that can be displayed in bitmap format in lieu of the privately formatted data.
    /// </summary>
    public const int CF_DSPBITMAP = 0x0082;

    /// <summary>
    /// Metafile-picture display format associated with a private format. The <c>hMem</c> parameter must be a
    /// handle to data that can be displayed in metafile-picture format in lieu of the privately formatted data.
    /// </summary>
    public const int CF_DSPMETAFILEPICT = 0x0083;

    /// <summary>
    /// Enhanced metafile display format associated with a private format. The <c>hMem</c> parameter must be a
    /// handle to data that can be displayed in enhanced metafile format in lieu of the privately formatted data.
    /// </summary>
    public const int CF_DSPENHMETAFILE = 0x008E;

    /// <summary>
    /// Start of a range of integer values for application-defined GDI object clipboard formats. The end of the
    /// range is <see cref="CF_GDIOBJLAST"/>. Handles associated with clipboard formats in this range are not
    /// automatically deleted using the <c>GlobalFree</c> function when the clipboard is emptied. Also, when using
    /// values in this range, the <c>hMem</c> parameter is not a handle to a GDI object, but is a handle allocated
    /// by the <c>GlobalAlloc</c> function with the <c>GMEM_MOVEABLE</c> flag.
    /// </summary>
    public const int CF_GDIOBJFIRST = 0x0300;

    /// <summary>
    /// See <see cref="CF_GDIOBJFIRST"/>.
    /// </summary>
    public const int CF_GDIOBJLAST = 0x03FF;

    /// <summary>
    /// Start of a range of integer values for private clipboard formats. The range ends with
    /// <see cref="CF_PRIVATELAST"/>. Handles associated with private clipboard formats are not freed
    /// automatically; the clipboard owner must free such handles, typically in response to the
    /// <see cref="ClipboardMessages.WM_DESTROYCLIPBOARD"/> message.
    /// </summary>
    public const int CF_PRIVATEFIRST = 0x0200;

    /// <summary>
    /// See <see cref="CF_PRIVATEFIRST"/>.
    /// </summary>
    public const int CF_PRIVATELAST = 0x02FF;
}
View Code

在C++裡面是不用指定數字的,只需要用CF_UNICODETEXT就行,不過.Net裡面應該沒有對應的索引表,所以只能手動輸入(我這裡是為了說明用才專門用數字,自己代碼那是索引的枚舉類)

上面兩個工作做完以後,就能實現功能了,功能代碼如下

                   var LastS = string.Empty;
                   while (!CancelInfoClipboard.IsCancellationRequested)
                   {
                       var Temp = ClipboardControl.GetText(ClipboardFormat.CF_UNICODETEXT);
                       if (!string.IsNullOrEmpty(Temp) && Temp != LastS)
                       {
                           ClipboardControl.SetText(Temp);
                           LastS = Temp;
                       }
                       Thread.Sleep(50);
                   }

是不是和最開始展示的調用.Net框架的方法一模一樣(笑),不過使用底層API實現的功能,就沒有那麼多亂七八糟的Bug了,自己也很清楚到底實現了啥功能,同時也收穫了不少新知識(主要是非托管代碼調用的時候的註意事項什麼的,還有,向非托管代碼傳遞數據的時候,最好多用Marshal類裡面的方法,不然可能會出錯,畢竟這個類就是專門為非托管代碼而設立的)

以上

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

-Advertisement-
Play Games
更多相關文章
  • 前言 近年,Spring Cloud儼然已經成為微服務開發的主流技術棧,在國內開發者社區非常火爆。我近年一直在一線互聯網公司(攜程,拍拍貸等)開展微服務架構實踐,根據我個人的一線實踐經驗和我平時對Spring Cloud的調研,我認為Spring Cloud技術棧中的有些組件離生產級開發尚有一定距離 ...
  • 最近完全被python的各種版本,安裝包,工具什麼的弄瘋了,感覺與python相關的東西太多了,一時間讓人分辨不出來到底什麼是乾什麼的。於是,我瘋狂的查閱各種資料,才逐漸慢慢理解了與python相關的各種工具和包。下麵,將我的困惑記錄下來,希望可以和有同樣煩惱的朋友分享! 1. conda、pip和 ...
  • instanceof 用於檢測指定對象是否是某個類(本類、父類、子類、介面)的實例。Java中的instanceof也稱為類型比較運算符,因為它將類型與實例進行比較。 返回true或false。 如果對任何具有null值的變數應用instanceof運算符,則返回false。用法:Boolean r ...
  • 類的定義及使用 一,類的定義 二,記憶體分析 類是引用類型,創建時會申請分配記憶體 堆記憶體:保存每一個對象的屬性,需要new關鍵字才可以創建 棧記憶體:保存一塊堆記憶體的地址 1,當使用Book bk = new Book();時,記憶體情況如下: 為屬性賦值之後,如下 2.分步創建對象,實例化 Book m ...
  • 廢話不多說,上節說的是數據類型,本篇講講數據運算。 在算式“1+2”中,“1”和“2”被稱為操作數,“+”被稱為運算符 Python語言支持以下運算符 算術運算符 比較(關係)運算符 賦值運算符 邏輯運算符 位運算符 成員運算符 身份運算符 0x00. 算術運算符 以下假設變數a為10,變數b為20 ...
  • 這幾天項目用到水晶報表做報表列印,沒有前輩指導,都自己摸著石頭過河,真是痛並快樂著。其實水晶報表還是挺好用的,對初學者也並不難(我就是初學者)。昨天遇到一個問題:無效索引 ……開始以為是報表設置的問題,把另一個正常的報表複製一個副本,在副本上修改,還是不行。報表裡的SQL也能預覽數據,折騰了很久,把 ...
  • 今天,我們很高興可以發佈 ASP.NET Core 2.1.0!這是我們 .NET平臺下開源的、跨平臺的 Web 框架的最新版本,現在已準備好供生產使用。今天就[開始使用](https://www.microsoft.com/net/learn/apps/web/get-started) ASP.N... ...
  • 我們很高興可以發佈 .NET Core 2.1。這次更新包括對性能的改進,對運行時和工具的改進。還包含一種以 NuGet 包的形式部署工具的新方法。我們添加了一個名為 [`Span ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...