前言:

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

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

我想当然的使用我最拿手的C#来完成这项工作,原因无他,因为.Net框架封装了能实现这种功能的方法

然后就有了如下代码

             string Temp = "";
while (true)
{
string Tex = Clipboard.GetText().ToString();
if (!string.IsNullOrWhiteSpace(Tex) && Temp != Tex)
{
Clipboard.Clear();
Clipboard.SetDataObject(Tex, false);
Temp = Tex;
}
Thread.Sleep();
}

这段代码,也是网页上广泛流传的,使用.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++复现了上述功能

 #include "stdafx.h"
#include <windows.h>
#include <iostream>
using namespace std;
#pragma comment(linker,"/subsystem:windows /entry:mainCRTStartup") int main(int argc, _TCHAR* argv[])
{
HANDLE THandle = GlobalAlloc(GMEM_FIXED, );//分配内存
char* Temp = (char*)THandle;//锁定内存,返回申请内存的首地址
while (true)
{
HWND hWnd = NULL;
OpenClipboard(hWnd);//打开剪切板
if (IsClipboardFormatAvailable(CF_TEXT))
{
HANDLE h = GetClipboardData(CF_TEXT);//获取剪切板数据
char* p = (char*)GlobalLock(h);
GlobalUnlock(h);
if (strcmp(Temp, p))
{
EmptyClipboard();//清空剪切板
HANDLE hHandle = GlobalAlloc(GMEM_FIXED, );//分配内存
char* pData = (char*)GlobalLock(hHandle);//锁定内存,返回申请内存的首地址
strcpy(pData, p);
strcpy(Temp, p);
SetClipboardData(CF_TEXT, hHandle);//设置剪切板数据
GlobalUnlock(hHandle);//解除锁定
}
}
CloseClipboard();//关闭剪切板
Sleep();
}
return ;
}

不愧是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(, 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 = ; /// <summary>
/// A handle to a bitmap (<c>HBITMAP</c>).
/// </summary>
public const int CF_BITMAP = ; /// <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 = ; /// <summary>
/// Microsoft Symbolic Link (SYLK) format.
/// </summary>
public const int CF_SYLK = ; /// <summary>
/// Software Arts' Data Interchange Format.
/// </summary>
public const int CF_DIF = ; /// <summary>
/// Tagged-image file format.
/// </summary>
public const int CF_TIFF = ; /// <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 = ; /// <summary>
/// A memory object containing a <c>BITMAPINFO</c> structure followed by the bitmap bits.
/// </summary>
public const int CF_DIB = ; /// <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 = ; /// <summary>
/// Data for the pen extensions to the Microsoft Windows for Pen Computing.
/// </summary>
public const int CF_PENDATA = ; /// <summary>
/// Represents audio data more complex than can be represented in a CF_WAVE standard wave format.
/// </summary>
public const int CF_RIFF = ; /// <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 = ; /// <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 = ; /// <summary>
/// A handle to an enhanced metafile (<c>HENHMETAFILE</c>).
/// </summary>
public const int CF_ENHMETAFILE = ; /// <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 = ; /// <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 = ; /// <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 = ; /// <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;
}

在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();
}

是不是和最开始展示的调用.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能够找到

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

以上

C# 使用WinApi操作剪切板Clipboard的更多相关文章

  1. C#操作剪切板(Clipboard)

    剪切板是Windows系统提供的功能,从我最早接触到的Windows 3.2版本开始,就一直带着了.以前使用C++的时候,是直接使用Windows API对其进行操作的,到了.NET下,在WinFor ...

  2. Javascript操作剪切板数据(支持IE、Chrome、360、搜狗),亲测!

    clipboarddata只能在IE浏览器中使用,在chrome下会提示对象未定义!以下的方法支持IE.Chrome.360.搜狗等浏览器,其它浏览器还未验证. <!DOCTYPE html&g ...

  3. c#操作剪切板

    C#定义了一个类System.Windows.Forms.Clipboard来简化剪切板操作,这个类有一个静态方法,主要有: Clear 清除剪切板中的所有数据: ContainsData,Conta ...

  4. 对c#剪切板Clipboard占用的问题一点解决方法

    以前在百度写的文档,转移到此处 前几天做一个程序,其中有一个剪切板的操作,具体代码: Clipboard.SetText(“ABC”); 来完成一个复制字符串的操作. 自己调试通过,完全正常,然后就交 ...

  5. 在Java中调用与系统有关的剪切板Clipboard

    java从1.5版开始已经能与系统的剪切板很好的交互了. 如果可以在程序中直接调用系统的剪切板来保存“复制”的对象内容,那可以说的比之前的想法好很多. 下面是一个对java.io.File对象进行co ...

  6. VBS操作剪切板

    '设置剪切板的内容 Dim Form, TextBox Set Form = CreateObject("Forms.Form.1") Set TextBox = Form.Con ...

  7. python 操作剪切板

    python3 在使用网上找到的一些使用剪切板的片段时发现存在写入剪切板后乱码的情况, 研究后发现python3不能使用SetClipboardData方法, 要使用SetClipboardText ...

  8. 【转载】VC操作剪切板

    1.在剪切板上放置数据 if(OpenClipboard())    //打开剪切板{    EmptyClipboard(); //清空剪切板    CString str;       //从控件 ...

  9. 使用ZeroClipboard操作剪切板

    一.ZeroClipboard下载地址 点击下载 二.添加js引用 <script src="../Assets/js/jquery-1.8.3.min.js">< ...

随机推荐

  1. 医学图像数据(一)——TCIA基本介绍

    1.介绍 The Cancer Imaging Archive (TCIA)是癌症研究的医学图像的开放获取数据库.该网站由国家癌症研究所(NCI)癌症影像计划资助,合同由阿肯色大学医学科学院管理.存档 ...

  2. go语言中使用defer、panic、recover处理异常

    go语言中的异常处理,没有try...catch等,而是使用defer.panic.recover来处理异常. 1.首先,panic 是用来表示非常严重的不可恢复的错误的.在Go语言中这是一个内置函数 ...

  3. 快速搭建Docker Registry私有仓库

    前提条件: 服务器已经安装Docker(我的服务器是CentOS 7) 服务器已经安装Docker Compose 满足以上条件时就可以开始搭建了: 1. 生成用户密码文件:(运行下面命令后会在当前目 ...

  4. docker简单介绍----存储

    docker容器 中使用Volumes来实现数据的持久性,因为容器的删除会丢失数据,而关闭或者重启容器不会丢失数据 docker run -v即可使用Volumes 1.docker-managed ...

  5. android app 的插件化、组件化、模块化开发-2

    Android 插件化 ——指将一个程序划分为不同的部分,比如一般 App的皮肤样式就可以看成一个插件 Android 组件化 ——这个概念实际跟上面相差不那么明显,组件和插件较大的区别就是:组件是指 ...

  6. QT删除非空文件夹

    int choose; choose = QMessageBox::warning(NULL,"warning","确定删除该文件?",QMessageBox: ...

  7. systemctl: command not found

    可以使用service代替 service语法有一点区别 这里演示了错误语法和正确语法

  8. Spring框架xml配置中属性ref与value的区别

    1.spring批量扫描mybatis的mapper,使用value 2.spring管理mybatis的单个mapper,用的是ref 虽然引用的是同一个bean,但两个对象的属相类型明显不一样,一 ...

  9. xss攻击与防御

    一.XSS攻击 Cross Site Scripting跨站脚本攻击 利用js和DOM攻击. 盗用cookie,获取敏感信息 破坏正常页面结构,插入恶意内容(广告..) 劫持前端逻辑 DDos攻击效果 ...

  10. 我对PMO的理解(持续更新)

    PMO的价值 为项目管理提供方法上的指导,对项目的实施过程提供监督.评价. PMO应该从哪方面着手建立管理体系 如何量化工作 如何考评工作 如何激励员工 如何进行人工.成本核算 如何进行进度跟踪与控制 ...