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

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

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

 

2. 关于剪贴板格式(Clipboard Formats)

所谓数据格式,是指为了便于储存、分析、交换和显示数据而规定的摆放数据的方式。我们知道,在计算机中,所有数据都是以0或1构成的位构成的,在大多数的编程语言中,数据的最小单位是字节,它由8个位构成,它可以表示0-255之间256个数值,要用这256个数值表示各种各样的信息,比如文字、图形、音频、视频等,就需要给代表每种信息的一串字节规定一个固定的结构和摆放规则,否则如果只是把所有数据罗列到一起,就会带来解析上的困难。如果这种结构和规则被普遍接受,它就会成为一种通用的数据格式。通常每种数据格式都会包含一个简单的易于识别的字节特征,比如我们的磁盘中的各种文件,就有文本格式、位图格式、超文本格式等成百上千种格式,假如我们用UltraEdit之类十六进制编辑器打开一个没有后缀名的文件,如果发现它的前四个字节是47 49 46 38("GIF8"),它很有可能是一个gif格式图片,假如我们对gif格式的规定有全面的了解,那不但可以100%判断它是或不是一个有效的gif格式图片,还能把它所代表的图形显示到屏幕上。对于用户来说,对不同格式的文件可以使用相应的程序打开,对于程序来说,对不同格式的数据可以使用相应的解析方式加以解读,从而挖掘其中携带的信息。

  剪贴板里可以同时存放多种格式的数据,我们将他们统称为剪贴板格式(Clipboard Formats)。为了区别各种格式,windows给每种剪贴板格式都分配了一个特有的长整型数字,我们称作剪贴板格式编号(Clipboard Format Number),同时,为了便于人们记忆和使用,除了少数几个通用的标准格式,大部分数据格式还对应一个英文名称,叫作剪贴板格式名称(Clipboard Format Name),这个名称是大小写敏感的,即CSV和Csv在剪贴板中代表两种不同的格式,它们的剪贴板格式编号也不会相同。剪贴板格式的编号和名称是由Windows系统分配和管理的,对于常用的格式,windows对它们的编号进行了预定义,这些格式被称为标准格式或预定义格式(Standard/Predefined Clipboard Formats),见下面的列表:

剪贴板格式编号API常量 所代表的数据格式 此格式内的数值所代表的含义
Const CF_TEXT =     1 文本格式,以chr(0)作为字符串结束标志 存储此字符串的内存对象的句柄
Const CF_BITMAP =    2 Bitmap对象 Bitmap GDI对象的句柄
Const CF_METAFILEPICT = 3 Metafile Picture格式 Metafile Picture的句柄
Const CF_SYLK =     4 微软符号连接格式
(Microsoft Symbolic Link Format)
 
Const CF_DIF =      5 Software Arts' Data Interchange Format.  
Const CF_TIFF =     6 标签图像文件格式(TIFF)  
Const CF_OEMTEXT =   7 包含OEM字符集的文本格式 存储此字符串的内存对象的句柄
Const CF_DIB =     8 设备无关位图(DIB)格式,前面是一个BITMAPINFO结构,后面是图像像素位 存储此DIB数据的内存对象的句柄
Const CF_PALETTE =   9 调色板对象格式,当程序向剪贴板中放入一幅使用调色板的位图时,它需要同时将调色板也放入剪贴板 调色板的句柄
Const CF_PENDATA =   10 手写笔数据  
Const CF_RIFF =     11 比标准CF_WAVE所能代表的音频格式更加复杂的音频格式  
Const CF_WAVE =     12 标准音频格式(如11kHz或22kHz脉冲编码调制)的数据  
Const CF_UNICODETEXT = 13 Unicode文本格式 存储此字符串的内存对象的句柄
Const CF_ENHMETAFILE = 14 增强图元文件格式 增强图元文件的句柄
Const CF_HDROP =    15 文件名列表 存储此文件名列表的内存对象的句柄
Const CF_LOCALE =    16 与剪贴板内文本相关的区域选项的ID  
Const CF_MAX =     17    


  注意:标准剪贴板格式是没有对应的剪贴板格式名称的。

  如果应用程序需要向剪贴板中放入的数据不能以上述格式表示或者不能转化为上述格式时,它可以使用注册剪贴板格式(Registered Clipboard Formats),也就是说,它可以自行为这个数据格式定义一个名称,然后使用RegisterClipboardFormat函数注册这个名称,如果注册成功,它将得到这个新建的剪贴板格式对应的编号。RegisterClipboardFormat函数的声明方式如下:

Declare Function RegisterClipboardFormat Lib "user32" Alias "RegisterClipboardFormatA" (ByVal lpString As String) As Long
作用 注册一个新的剪贴板格式,如果注册成功,这个格式可以当作有效剪贴板格式使用
参数 lpString字符串;指定要注册的剪贴板格式的名称
返回值 如果注册成功,则返回该剪贴板格式所对应的编号如果注册失败,则返回0
说明 如果被注册的剪贴板格式名称已经存在,并不会重新注册一个新的剪贴板格式,而是返回原有的同名剪贴板格式编号。这个函数不需要使用OpenClipboard。


  这和工商登记有点类似,假设你要开办一家公司,那么你首先要给公司取个名字,然后到工商部门去注册,如果这个公司名没有被其他人注册,那么注册成功后工商部门会告诉你一个工商注册号,这个号码与所有其他公司都是不同的;但有一点不同,在现实中,如果你要注册的这个公司名称已经被注册了,你只能另选其他名称重新登记,而在剪贴板机制中,所有公司名(剪贴板格式名称)和工商登记号(剪贴板格式编号)都是公用的,只要有一个人(应用程序)在工商部门(windows操作系统)中注册了一个公司,所有人(应用程序)都可以使用此公司名称和工商登记号,如果有人尝试注册一个已经存在的公司名称,他可以沿用这个公司原有的工商注册号。

  这里提供一个山寨版的剪贴板查看工具,运行它以后,你可以随意到其他程序中复制不同的对象,然后在左侧列表中可以看到当前剪贴板内所有数据格式的编号和对应的名称,如果格式对应的是一个内存对象,工具右侧的表格中可以显示它存储的十六进制数据,它还允许你将这些十六进制的数据保存为文件,然后到UltraEdit等专门的工具中去分析。

点击下载:  clipboardviewer.rar 
 201411101121
  由于剪贴板内的数据是由收到“复制”或“剪切”命令的应用程序放进去的,而各种应用程序所处理的数据格式范围也不一样,因此,不同的应用程序在不同的上下文环境中放进剪贴板的数据格式种类也不同。这个很容易理解,当我们使用word选中一段文字后选择复制命令时,word放入剪贴板的肯定是与文本有关的格式,而在画图中选中图片的一部分后选择复制命令时,画图程序放入剪贴板的肯定是与图形相关的格式。同时,对于收到“粘贴”命令后读取和使用剪贴板内数据的应用程序来说,它需要先判断剪贴板内是否存在自己可解读的数据格式。比如我们选中某个网页图片后使用复制命令,这时浏览器会把网页图片的数据以相关的图形格式存放在剪贴板中,此时在可以解读和处理图片的程序(比如画图、phtoshop或word)中,粘贴按钮是可用的,而在另外的程序中粘贴按钮是灰色/不可用的。这是因为这些程序在显示编辑菜单之前使用与剪贴板格式相关的几个API函数预先判断一下剪贴板内是否有自己可以处理的数据格式。接下来我们就看看如何使用API函数获取与剪贴板格式相关的信息。

  如果一个应用程序只对剪贴板内格式名称为PNG的数据感兴趣,并且它已经获得了PNG剪贴板格式的编号,假设是49538,那判断它自己是否可以使用粘贴命令的最简单的办法是使用IsClipboardFormatAvailable(49538),然后根据返回值确定剪贴板内是否包含PNG格式数据,这个函数定义如下: 

Declare Function IsClipboardFormatAvailable Lib "user32" (ByVal wFormat As Long) As Long
作用 判断剪贴板内是否存在指定格式的数据
参数 wFormat标准剪贴板格式或注册剪贴板格式的编号
返回值 如果剪贴板中存在相应格式的数据,则返回一个非0值否则返回0
说明  


  如果一个应用程序可以处理多种数据格式,比如Word,它可以处理富文本(Rich Text Format)、超文本(HTML Format)和纯文本(CF_TEXT),并且当剪贴板可以提供多种格式时优先考虑包含更多格式信息的富文本和超文本,然后是纯文本,只有前面的格式不存在时才考虑后面的格式,那么它不必使用3次IsClipboardFormatAvailable来分别判断,因为有另外一个功能相似但允许传入数组的API函数GetPriorityClipboardFormat,这个函数定义如下:

Declare Function GetPriorityClipboardFormat Lib "user32" (lpPriorityList As Long, ByVal nCount As Long) As Long
作用 这个函数返回指定数组中第一个可用的剪贴板格式
参数 lpPriorityList指向一个数组的指针,这个数组中按优先级别存放多个剪贴板格式编号
nCount
数组内元素的个数
返回值 如果剪贴板内存在一种或多种数组中列出的格式,则返回优先级别最靠前的剪贴板格式编号如果剪贴板内有数据但不包含数组中列出的任何一种格式,则返回-1
如果剪贴板为空,则返回Null
说明 这个函数不需要OpenClipboard在VB中,如果API需要一个数组的指针,那么应该向它传递数组第一个元素,并且不要使用byval


  接着上面的话题,假设三种格式对应的编号分别为Rich Text Format:49465,HTML Format:49363和CF_TEXT:1,它可以使用下列代码一次得到需要的剪贴板格式:

Dim lFormats()
Dim lFormatFristAvailable As Long
lFormats = Array(49465, 49363, 1)
lFormatFristAvailable = GetPriorityClipboardFormat(lFormats(0), 3)

运行代码之后lFormatFristAvailable的值可能是49465、46363、1、0、-1,这些值具体代码什么含义应该不需要再多说了吧。
  如果应用程序只知道自己可以处理的剪贴板格式的名称,而不知道它的编号,它可以通过两种方法得到对应的编号:
  1) 使用前面提到的RegisterClipboardFormat函数;
  2) 使用EnumClipBoardFormats函数获取剪贴板内所有可用格式的编号,然后使用GetClipboardFormatName函数取得该编号对应的格式名称
  但如果应用程序需要列出剪贴板内所有可用格式的编号和名称时,就只能通过后面两个函数了,这两个函数常组合在一起使用。

Declare Function EnumClipboardFormats Lib "user32" (ByVal wFormat As Long) As Long
作用 这个函数可以列举出当前剪贴板内所有可用的数据格式
参数 wFormat代表剪贴板内已知可用的标准剪贴板格式或注册剪贴板格式的编号
在列举过程最开始,向函数传入0值,函数将返回第一个可用的剪贴板格式编号,将返回值传给下一次函数调用,可以得到下一个可用的剪贴板格式编号。
返回值 如果调用成功,则返回wFormat之后第一个可用的剪贴板格式编号如果调用失败,则返回0
说明 这个函数需要事先使用OpenClipboard成功打开剪贴板
这个函数返回的剪贴板格式的顺序与ClipboardOwner放入数据时的顺序一样。微软建议,当向剪贴板中放入数据时,首先放入最能描述所要传递的信息的数据,当从剪贴板中读取数据时,尽量使用程序能够解读的最靠前的格式的数据,因为它所包含的信息最接近原始数据所要表达的信息。


  如果你以前使用过FindWindowEx,那么使用起这个函数来肯定容易上手。如果没有使用过也没有关系,形象点地比喻,这有点像我们上学时上体育课,假如应用程序是新来的体育老师,他对班里的同学(剪贴板内可用的数据格式)一个也不认识,那么他可以让班里的同学站成一队,然后问班长(Windows操作系统):“排头的同学学号是多少?”(对应代码:lFormat=EnumClipBoardFormats(0) ),班长回答:“是5号”(这时lFormat=5),老师又问:“5号同学后面的同学学号是多少?”(对应代码:lFormat=EnumClipBoardFormats(lFormat) ),班长回答:“5号后面是7号”,“那7号后面呢?”……这样一直问下去,直到班长回答:“99号同学是最后一位,后面没有了”,这样就完成了一次列举,老师可以把得到的学号依次记到本子上,下次在需要的时候不必再问班长。当然聪明点的老师可以这样告诉班长:“把排头到排尾的同学学号依次报给我”,这样对应的是一个do……loop until循环。
  按照号码记忆是容易弄混的,通常老师要知道学生的名字,这样他就要给班长一张纸,上面有某个学号,让班长把这个学生的名字写到纸上,这种情况相当于使用GetClipboardFormatName函数,函数这样声明:

Declare Function GetClipboardFormatName Lib "user32" Alias "GetClipboardFormatNameA" (ByVal wFormat As Long, ByVal lpString As String, ByVal nMaxCount As Long) As Long
作用 给出指定剪贴板格式编号所对应的剪贴板格式名称,函数将把此名称复制到指定的缓冲区中。
参数 wFormat:剪贴板格式的名称
lpString:
[输出]用于接收剪贴板格式名称的缓冲字符串
nMaxCount:
缓冲字符串的字节长度,如果剪贴板格式名称的文本长度超出nMaxCount数值,超出部分将被截去
返回值 如果函数调用成功,返回值是复制到lpString中的字节数目如果函数调用失败,返回0
说明  


  这里又有一个在VB中不常见的概念,缓冲字符串(有时候是缓冲字节数组或是其他结构)。
  在API中,函数的返回值通常是Long型,所以字符串的赋值通常不是向VB中那样strOut=strFunction(),而是先定义一个字符串变量,然后用String函数或Space函数使它具备足够的长度以容纳将要获得的字符,然后将它以参数的形式传递给函数,函数运行过程中将把要获取的字符串复制进这个字符串中,这种方式在API中很常用,我们要习惯它。
  最后,发一个在立即窗口中输出剪贴板内所有可用格式编号和名称的过程作为本节的结束:

Public Declare Function OpenClipboard Lib "user32" (ByVal hwnd As Long) As Long
Public Declare Function CloseClipboard Lib "user32" () As Long
Public Declare Function EnumClipboardFormats Lib "user32" (ByVal wFormat As Long) As Long
Public Declare Function GetClipboardFormatName Lib "user32" Alias "GetClipboardFormatNameA" (ByVal wFormat As Long, ByVal lpString As String, ByVal nMaxCount As Long) As Long
Public Const CF_TEXT = 1
Public Const CF_BITMAP = 2
Public Const CF_METAFILEPICT = 3
Public Const CF_SYLK = 4
Public Const CF_DIF = 5
Public Const CF_TIFF = 6
Public Const CF_OEMTEXT = 7
Public Const CF_DIB = 8
Public Const CF_PALETTE = 9
Public Const CF_PENDATA = 10
Public Const CF_RIFF = 11
Public Const CF_WAVE = 12
Public Const CF_UNICODETEXT = 13
Public Const CF_ENHMETAFILE = 14
Public Const CF_HDROP = 15
Public Const CF_LOCALE = 16
Public Const CF_MAX = 17
Dim lFormat As Long
Public Sub ListClipFormats()
  IF OpenClipboard (byval 0&) Then
    lFormat = EnumClipboardFormats(0)
    If lFormat <> 0 Then
      Do While lFormat <> 0
        debug.print  lFormat & vbTab & ": " & GetFormatName(lFormat)
        lFormat = EnumClipboardFormats(lFormat)
      Loop
    End If
    CloseClipboard
  End If
End Sub
Public Function GetFormatName(lFormat As Long) As String
  Select Case lFormat
  Case 1
    GetFormatName = "CF_Text"
  Case 2
    GetFormatName = "CF_Bitmap"
  Case 3
    GetFormatName = "CF_MetaFilePict"
  Case 4
    GetFormatName = "CF_SYLK"
  Case 5
    GetFormatName = "CF_Dif"
  Case 6
    GetFormatName = "CF_Tiff"
  Case 7
    GetFormatName = "CF_OEMText"
  Case 8
    GetFormatName = "CF_DIB"
  Case 9
    GetFormatName = "CF_Pallette"
  Case 10
    GetFormatName = "CF_PenData"
  Case 11
    GetFormatName = "CF_Riff"
  Case 12
    GetFormatName = "CF_Wave"
  Case 13
    GetFormatName = "CF_UnicodeText"
  Case 14
    GetFormatName = "CF_EnhMetaFile"
  Case 15
    GetFormatName = "CF_HDrop"
  Case 16
    GetFormatName = "CF_Locale"
  Case 17
    GetFormatName = "CF_Max"
  Case Else:
    Dim sBuffer As String
    sBuffer = String(100, Chr(0))
    GetClipboardFormatName lFormat, sBuffer, 100
    GetFormatName = Trim(sBuffer)
  End Select
End Function

 

给我留言

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