前言:

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

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

我想当然的使用我最拿手的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. python封装configparser模块获取conf.ini值

    configparser模块是python自带的从文件中获取固定格式参数的模块,因为是python只带的,大家用的应该很多,我觉得这个参数模块比较灵活,添加参数.修改参数.读取参数等都有对应的参数供用 ...

  2. cadence学习一------>介绍

    Allegro常用组件: 1.orcad capture cis------>>原理图 2.PCB editor----->>PCB 3.PAD designer------- ...

  3. Revit二次开发封装族的替代方法

    使用草图平面:

  4. SSH整合 pom.xml

    <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/20 ...

  5. 020_Linux的孤儿进程与僵尸进程(Unix系统编程)

    1.前言 之前在看<unix环境高级编程>第八章进程时候,提到孤儿进程和僵尸进程,一直对这两个概念比较模糊.今天被人问到什么是孤儿进程和僵尸进程,会带来什么问题,怎么解决,我只停留在概念上 ...

  6. P2444 [POI2000]病毒

    P2444 [POI2000]病毒 题目描述 二进制病毒审查委员会最近发现了如下的规律:某些确定的二进制串是病毒的代码.如果某段代码中不存在任何一段病毒代码,那么我们就称这段代码是安全的.现在委员会已 ...

  7. 【python】使用flask制作小型页面的关键点总结

    目录结构 app.py web代码 store.db 存储信息的轻量数据库,用的sqlite3 schema.sql 数据库的初始化建表语句 settings.cfg 配置信息 static/styl ...

  8. [转] iphoneX、iphoneXS、iphoneXSMax、iphoneXR适配

    基础知识 1. 关于iphoneX .iphoneXS.iphoneXSMax.iphoneXR机型的大小和像素   机型尺寸 注意:开发人员只需要记住开发尺寸 2. 屏幕组成 齐刘海(44px) + ...

  9. P1462 通往奥格瑞玛的道路 最短路

    题目背景 在艾泽拉斯大陆上有一位名叫歪嘴哦的神奇术士,他是部落的中坚力量 有一天他醒来后发现自己居然到了联盟的主城暴风城 在被众多联盟的士兵攻击后,他决定逃回自己的家乡奥格瑞玛 题目描述 在艾泽拉斯, ...

  10. 在命令行输入python出现“Warning:This Python interpreter is in a conda environment, but the environment has not been activated. Libraries may fail to load. To activate this environment please see https://conda.

    [现象] 在命令行输入python出现“Warning:This Python interpreter is in a conda environment, but the environment h ...