博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
C# 使用WinApi操作剪切板Clipboard
阅读量:6251 次
发布时间:2019-06-22

本文共 13530 字,大约阅读时间需要 45 分钟。

前言:

最近正好写一个程序,需要操作剪切板

功能很简单,只需要从剪切板内读取字符串,然后清空剪切板,然后再把字符串导入剪切板

我想当然的使用我最拿手的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 
3 #include
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)        {            if (!OpenClipboard(IntPtr.Zero))         {
        SetText(text);         return;         } 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{    ///     /// 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.    ///     public const int CF_TEXT = 1;    ///     /// A handle to a bitmap (
HBITMAP
). ///
public const int CF_BITMAP = 2; /// /// Handle to a metafile picture format as defined by the
METAFILEPICT
structure. When passing a ///
CF_METAFILEPICT
handle by means of DDE, the application responsible for deleting
hMem
should /// also free the metafile referred to by the
CF_METAFILEPICT
handle. ///
public const int CF_METAFILEPICT = 3; /// /// Microsoft Symbolic Link (SYLK) format. /// public const int CF_SYLK = 4; /// /// Software Arts' Data Interchange Format. /// public const int CF_DIF = 5; /// /// Tagged-image file format. /// public const int CF_TIFF = 6; /// /// 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. /// public const int CF_OEMTEXT = 7; /// /// A memory object containing a
BITMAPINFO
structure followed by the bitmap bits. ///
public const int CF_DIB = 8; /// /// 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
(logical color palette) format, the application should use the ///
SelectPalette
and
RealizePalette
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
CF_PALETTE
format. ///
public const int CF_PALETTE = 9; /// /// Data for the pen extensions to the Microsoft Windows for Pen Computing. /// public const int CF_PENDATA = 10; /// /// Represents audio data more complex than can be represented in a CF_WAVE standard wave format. /// public const int CF_RIFF = 11; /// /// Represents audio data in one of the standard wave formats, such as 11 kHz or 22 kHz PCM. /// public const int CF_WAVE = 12; /// /// Unicode text format. Each line ends with a carriage return/linefeed (CR-LF) combination. A null character /// signals the end of the data. /// public const int CF_UNICODETEXT = 13; /// /// A handle to an enhanced metafile (
HENHMETAFILE
). ///
public const int CF_ENHMETAFILE = 14; /// /// A handle to type
HDROP
that identifies a list of files. An application can retrieve information /// about the files by passing the handle to the
DragQueryFile
function. ///
public const int CF_HDROP = 15; /// /// The data is a handle to the locale identifier associated with text in the clipboard. When you close the /// clipboard, if it contains
CF_TEXT
data but no
CF_LOCALE
data, the system automatically sets /// the
CF_LOCALE
format to the current input language. You can use the
CF_LOCALE
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
CF_LOCALE
to implicitly convert from ///
to
. Therefore, the correct code page table is used for /// the conversion. ///
public const int CF_LOCALE = 16; /// /// A memory object containing a
BITMAPV5HEADER
structure followed by the bitmap color space /// information and the bitmap bits. ///
public const int CF_DIBV5 = 17; /// /// Owner-display format. The clipboard owner must display and update the clipboard viewer window, and receive /// the
,
, ///
,
, and ///
messages. The
hMem
parameter must be
null
. ///
public const int CF_OWNERDISPLAY = 0x0080; /// /// Text display format associated with a private format. The
hMem
parameter must be a handle to data /// that can be displayed in text format in lieu of the privately formatted data. ///
public const int CF_DSPTEXT = 0x0081; /// /// Bitmap display format associated with a private format. The
hMem
parameter must be a handle to /// data that can be displayed in bitmap format in lieu of the privately formatted data. ///
public const int CF_DSPBITMAP = 0x0082; /// /// Metafile-picture display format associated with a private format. The
hMem
parameter must be a /// handle to data that can be displayed in metafile-picture format in lieu of the privately formatted data. ///
public const int CF_DSPMETAFILEPICT = 0x0083; /// /// Enhanced metafile display format associated with a private format. The
hMem
parameter must be a /// handle to data that can be displayed in enhanced metafile format in lieu of the privately formatted data. ///
public const int CF_DSPENHMETAFILE = 0x008E; /// /// Start of a range of integer values for application-defined GDI object clipboard formats. The end of the /// range is
. Handles associated with clipboard formats in this range are not /// automatically deleted using the
GlobalFree
function when the clipboard is emptied. Also, when using /// values in this range, the
hMem
parameter is not a handle to a GDI object, but is a handle allocated /// by the
GlobalAlloc
function with the
GMEM_MOVEABLE
flag. ///
public const int CF_GDIOBJFIRST = 0x0300; /// /// See
. ///
public const int CF_GDIOBJLAST = 0x03FF; /// /// Start of a range of integer values for private clipboard formats. The range ends with ///
. Handles associated with private clipboard formats are not freed /// automatically; the clipboard owner must free such handles, typically in response to the ///
message. ///
public const int CF_PRIVATEFIRST = 0x0200; /// /// See
. ///
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类里面的方法,不然可能会出错,毕竟这个类就是专门为非托管代码而设立的)

接下来是新的发现

在研究MSDN上面关于剪切板的API的时候,发现了一个函数

bool AddClipboardFormatListener(HWND hwnd);

根据描述来讲,是添加一个剪切板的监控,在剪切板有任何变动的时候,通知你所指定的句柄的窗口,我一想,这不就是我所需要的么,有了这么一个API以后,其实我上面所展示的,使用死循环轮询剪切板的方法就变得很傻逼,而且也很容易出错了,于是,基于这个新发现的API,我重新更改了全部的程序逻辑,反而比原先的实现更加简单了。

首先我们需要一个新的窗口或者控件来接收Windows消息更新后所发来的消息,只要New 一个form就行

public Form2()        {            InitializeComponent();            AddClipboardFormatListener(this.Handle);        }

然后我们在初始化组件的命令后面,把使用添加剪切板监听的API把当前窗口的句柄发给系统,这样系统在接收到剪切板改变的命令后,会把消息发给当前窗口

然后我们需要复写WndProc方法

protected override void WndProc(ref Message m)        {            if (m.Msg == 0x031D && Onice)            {
var Temp = ClipboardControl.GetText(ClipboardFormat.CF_UNICODETEXT); if (!string.IsNullOrEmpty(Temp)) { ClipboardControl.SetText(Temp); Onice = false; } } else if (!Onice) { Onice = true; } else { base.WndProc(ref m); } }    private bool Onice = true;

首先WndProc如果是Form类下面一个专门用来接收系统发送过来的消息的方法

然后关于m.Msg == 0x031D的0x031D在WinAPI定义上的意义是WM_CLIPBOARDUPDATE ,也就是剪切板更新事件,这个通过查找MSDN能够找到

下面没有特别奇怪的函数,就是有一点需要注意,我们这里设置了剪切板数据,相当于进行了一次更新,所以会在这一瞬间再次产生剪切板更新事件,然后又会通知这个方法,然后就会形成死循环,我在这里用了一个布尔判断来通过布尔状态决定是否截取剪切板,不只有有没有更好的办法来实现

以上

转载于:https://www.cnblogs.com/ACDIV/p/9114472.html

你可能感兴趣的文章
12月10日站立会议
查看>>
Nginx入门(2)反向代理和负载均衡
查看>>
MySQL库表状态查询
查看>>
【鲁班学院】干货分享!《面试必备之Mysql索引底层原理分析》
查看>>
快捷键
查看>>
第十一周项目0-是春哥啊
查看>>
poi做一个简单的EXCAL
查看>>
几种查询emacs帮助的办法
查看>>
MariaDB 数据库
查看>>
Python_基础_(模块,time,random,os,sys,json,shelve,xml,序列化反序列化)
查看>>
Redis几个认识误区
查看>>
异常:Project configuration is not up-to-date with pom.xml解决方案
查看>>
HDU2647 拓扑排序
查看>>
ThinkPHP/---微信支付PC流程
查看>>
JavaScript 05
查看>>
python 多线程编程之threading模块(Thread类)创建线程的三种方法
查看>>
实验三
查看>>
水仙花数
查看>>
常用正则表达式
查看>>
P3308 [SDOI2014]LIS(最小割+退流)
查看>>