现在的位置: 首页 > 精选转贴 > 正文

详解 VBA 中 Windows 剪切板的操作(二)

2014年11月04日 精选转贴 ⁄ 共 5263字 ⁄ 字号 暂无评论 ⁄ 阅读 14,093 次
上接:详解 VBA 中 Windows 剪切板的操作(一)

 

三、如何操作剪贴板内的数据


1.读取剪贴板内的数据,这一节主要说说几个相关的剪贴板函数和内存函数


  要读取剪贴板,首先要打开剪贴板,用OpenClipboard函数,格式如下:

Declare Function OpenClipboard Lib "user32" Alias "OpenClipboard" (ByVal hwnd As Long) As Long
作用 打开剪贴板,并且阻止其他程序访问剪贴板;
参数 hwnd:
通常用来传递目前打开剪贴板的窗口的句柄,如果它修改了剪贴板内的数据,它就会成为剪贴板数据拥有窗口ClipboardOwner,这样其他程序可以使用GetClipboardOwner获取它。如果只是读取剪贴板内的数据,可以传递一个Null变量(VB中是Byval 0&);如果要向剪贴板内写入数据,则必须指定有效的hwnd,否则不能成功调用SetClipboardData函数;
返回值 如果调用成功,它会返回一个非0值;如果失败,则返回0;
说明 如果有其他窗口已经打开剪贴板,这个函数会调用失败。
如果函数调用成功,一定要记得使用CloseClipboard函数关闭它。


  如果OpenclipBoardData函数调用成功,我们接下来就可以用GetClipboardData读取里面的数据了。来看看GetClipboardData函数的样子。

Declare Function GetClipboardData Lib "user32" (ByVal wFormat As Long) As Long
作用 返回剪贴板中以指定格式存放的剪贴板对象的句柄
参数 wFormat:
将要取出的数据格式的编号
返回值 如果调用成功,返回剪贴板中以指定格式存放的剪贴板对象的句柄;
如果调用失败,返回Null;
说明 在使用GetClipboardData之前,必须先成功调用OpenClipboard


注意,VB6自带的APIViewer中对这个函数是声明是错误的,见下面一句中带下划线的部分:

Declare Function GetClipboardData Lib "user32" Alias "GetClipboardDataA" (ByVal wFormat As Long) As Long

前面说过,剪贴板内可以同时存放多种格式的数据。Windows不会一次性地把这些数据全交到你手中,它一次只会给你一种格式的数据,只要你向它提供了这种数据格式对应的编码,也就是括号内的长整型变量wFormat。关于剪贴板格式的话题,我们在后面的相关函数中单独讨论。目前你可以把它想象为股票代码,要从股票软件那里查询股票行情,要向软件提供这个代码,你提供的代码不同,软件返回的数据也不一样。
  可能很多人要问,如果调用成功,返回的这个句柄代表什么,又怎么利用呢?按照小fisher的观察,至少有下面三种情况:

  • 如果这个格式对应的是GDI对象(比如CF_BITMAP, CF_METAFILEPICT,CF_PALETTE,CF_PENDATA等),这个返回值就是这个GDI对象的句柄,对于这个句柄,我们可以使用相关的GDI函数创建一个它的副本,然后使用其他GDI函数操作它就可以了。关于GDI的话题,要细讲起来用的篇幅恐怕比剪贴板还要多,在后面的小节“利用从剪贴板中取出的数据”里对于用到的几个GDI函数会给出一些简单的说明,感兴趣的同志们也可以从网上或MSDN中查找相关的说明,这里我推荐一个英文的教程,里面讲解得非常细致和系统,网址是http://edais.mvps.org/Tutorials/GDI/DC/index.html
  • 如果是其他格式,这通常是一个内存对象的句柄;
  • 应该还有一些特殊情况(比如对于剪贴板内的DataObject格式,这个返回值指向的是一个长整型数字,估计是个内存指针类型),由于我自己也没研究,所以目前不讨论。

  这里我们重点探讨一下第二种情况。现在我们得到的是一个内存对象的句柄,要在VB(A)中使用它,就需要将它的二进制数据读取到我们在VB(A)中声明的变量、字符串或数组中。假设我们已经定义好了一个动态的字节数组,Dim bytClipData() as Byte,用来存放从剪贴板中获取的二进制数据。通常,我们马上就会想到CopyMemory函数,这个函数声明方式如下:

Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" (Destination As Any, Source As Any, ByVal Length As Long)
作用 将一定字节长度的数据从内存中的一个位置(源)复制到另一个位置(目的地)
参数 Destination:目的地的第一个字节的内存地址(指针)
Source:    源的第一个字节的内存地址(指针)
Length:    要复制的数据的长度
返回值 无;因为它是一个Sub过程
说明 作为复制目的地的变量、字符串或数组应该有足够的长度以容纳数据,否则会产生内存溢出错误。
在VB中要注意,因为前两个参数分别是源和目的地的内存地址(指针),如果使用数值变量表示此内存地址,需要在前面加上Byval,说明是按数值传值


  只有这个函数还不够,对于数据的源(即内存对象)我们目前只知道它的句柄,而CopyMemory需要的是它的位置和字节长度,所以还要借助另外三个内存函数:

Declare Function GlobalLock Lib "kernel32" (ByVal hMem As Long) As Long
作用 锁定一个全局内存对象并返回它所占用内存块的第一个字节的内存地址(指针)
参数 hMem:内存对象的句柄
返回值 如果调用成功,返回值是内存对象所占用内存的第一个字节的内存地址
如果调用失败,返回值是Null
说明 内存对象的内部包含了一个锁计数器,初始值是0。对于可移动的内存对象(用于存放剪贴板对象的内存必须标记为可移动GMEM_MOVEABLE),在程序使用这个内存对象前,它需要先调用GlobalLock函数锁定它,同时这个函数会使内存对象的锁计数器数值+1,当程序不再需要使用这个内存对象时,它调用GlobalUnlock函数解锁,同时,内存对象的锁计数器-1,当内存对象的锁计数器降到0时,表示当前没有任何应用程序使用当前的内存对象,Windows在需要的时候会回收这部分空间。



Declare Function GlobalSize Lib "kernel32" (ByVal hMem As Long) As Long
作用 返回给定内存对象的字节长度
参数 hMem:内存对象的句柄
返回值 如果调用成功,返回值是内存对象所占用内存的字节长度
如果传入的hMem参数不是有效的内存对象或者如果该对象已经被销毁,返回值为0
说明  



Declare Function GlobalUnlock Lib "kernel32" (ByVal hMem As Long) As Long
作用 将可移动(GMEM_MOVEABLE)内存对象的锁计数器数值-1,对于固定位置(GMEM_FIXED)的内存对象,这个函数不起作用.
参数 hMem:内存对象的句柄
返回值 如果在调用函数之后,内存对象仍然被锁定,则返回一个非0的值
如果在调用函数之后,内存对象被解锁,则返回0
说明 见GlobalLock函数的说明


  最后别忘了使用CloseClipboard关闭剪贴板:

Declare Function CloseClipboard Lib "user32"() As Long
作用 关闭剪贴板
参数
返回值 如果调用成功,返回一个非0值
如果调用失败,返回0
说明 当窗口完成了对剪贴板的检视或修改之后,它需要调用CloseClipboard,这样可以使其他窗口访问剪贴板


  OK,有了这几个内存API函数,我们就可以将剪贴板中所存放的内存对象复制到自己定义的变量、数组或字符串中了。为了加深印象,我们用图形重现一下整个工作流程:
2014110401 
理清了思路,你现在可以尝试写一个读取剪贴板内文本的过程了
第1题:写一个读取剪贴板内文本的过程,如果剪贴板内有文本格式的数据,则通过对话框将此文本内容显示给用户,否则提示用户当前剪贴板内没有文本
要求:使用剪贴板API函数
如果你能独立完成,那么恭喜你已经掌握了这些函数的使用要领,如果出错了也不要在灰心,下面是参考答案,跟你自己的代码对照一下,看哪里有出入,然后最小化这个网页窗口,回到你的程序中重新修改过再试验,相信你会成功!
特别提醒:如果CopyMemory使用不当,会造成Excel关闭重启,所以在运行代码前注意要保存文件

Declare Function OpenClipboard Lib "user32" (ByVal hwnd As Long) As Long
Declare Function GetClipboardData Lib "user32" (ByVal wFormat As Long) As Long
Declare Function CloseClipboard Lib "user32" () As Long
Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" (Destination As Any, Source As Any, ByVal Length As Long)
Declare Function GlobalLock Lib "kernel32" (ByVal hMem As Long) As Long
Declare Function GlobalUnlock Lib "kernel32" (ByVal hMem As Long) As Long
Declare Function GlobalSize Lib "kernel32" (ByVal hMem As Long) As Long
Private Const CF_TEXT = 1
Private Sub GetClipText()
    Dim hMem As Long
    Dim lpData As Long
    Dim nClipSize As Long
    Dim bytClipData() As Byte
    Dim sClipString As String
    
    If OpenClipboard(ByVal 0&) Then                 '如果OpenClipboard函数返回非0值,说明成功打开剪贴板
        hMem = GetClipboardData(CF_TEXT)            '获取剪贴板中以文本格式存在的内存对象的句柄
        If CBool(hMem) Then                         '如果剪贴板中对应的格式不存在,此时的hMem会是0(Null),这里用CBool把它转换成Boolean类型加以判断
            lpData = GlobalLock(hMem)               '获取内存对象第一个字节的内存地址
            nClipSize = GlobalSize(hMem)            '获取内存对象的字节长度
            ReDim bytClipData(1 To nClipSize)       '修改缓冲字节数组的长度,确保能够容纳内存对象的全部数据
            CopyMemory bytClipData(1), ByVal lpData, nClipSize  '复制内存对象的数据到字节数组中,注意Byval的用法
            sClipString = StrConv(bytClipData, vbUnicode)       '将字节转化成字符串
            MsgBox "当前剪贴板内的文本是:" & vbCrLf & sClipString '将结果显示给用户
        Else
            MsgBox "当前剪贴板内没有文本"
        End If
        CloseClipboard
    End If
End Sub


可能很多朋友会问:要读取剪贴板内文本,使用下面3行代码就行了,为什么要花费这么大力气使用API呢?

Private Sub GetClipText()
  Dim objData As New DataObject  '需要引用“Microsoft Forms 2.0 Object Library”
  objData.GetFromClipboard
  MsgBox "当前剪贴板内的文本是:" & objData.GetText
End Sub


是的,单就题目本身而言,使用MSForms.DataObject更合适。但DataObject目前只支持文本操作,所以它的GetFromClipboard方法只能用于读取剪贴板内文本,而通过剪贴板API函数,我们可以使用剪贴板内全部格式的数据,下面我们就转入剪贴板数据格式的讨论。

 

下接:详解 VBA 中 Windows 剪切板的操作(三)

 

给我留言

您必须 [ 登录 ] 才能发表留言!