C# 使用WinApi操作剪切板Clipboard

发布时间:2018/5/31 17:41:09 次浏览

前言:

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

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

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

然后就有了如下代码

 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++复现了上述功能

 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是怎么来的问题,其实这个剪切板的格式参数,下面有一张表,就是自从这里来的

 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类里面的方法,不然可能会出错,毕竟这个类就是专门为非托管代码而设立的)