ExcelHome技术论坛

 找回密码
 免费注册

QQ登录

只需一步,快速开始

快捷登录

搜索
EH技术汇-专业的职场技能充电站 妙哉!函数段子手趣味讲函数 Excel服务器-会Excel,做管理系统 效率神器,一键搞定繁琐工作
HR薪酬管理数字化实战 Excel 2021函数公式学习大典 Excel数据透视表实战秘技 打造核心竞争力的职场宝典
让更多数据处理,一键完成 数据工作者的案头书 免费直播课集锦 ExcelHome出品 - VBA代码宝免费下载
用ChatGPT与VBA一键搞定Excel WPS表格从入门到精通 Excel VBA经典代码实践指南
查看: 21207|回复: 29

小fisher-闲来无事,说说Windows剪贴板

  [复制链接]

TA的精华主题

TA的得分主题

发表于 2013-9-22 11:08 | 显示全部楼层 |阅读模式
本帖已被收录到知识树中,索引项:Windows API应用
[原创] 闲来无事,说说Windows剪贴板
小fisher首发office精英俱乐部-http://www.officefans.net/cdb/,转载请注明出处
一、剪贴板概述
  首先,讨论一下剪贴板是什么
  Windows的帮助文件中对剪贴板的描述是这样的:剪贴板是从一个地方复制或移动并打算在其他地方使用的信息的临时存储区域。可以选择文本或图形,然后使用“剪切”或“复制”命令将所选内容移动到剪贴板,在使用“粘贴”命令将该内容插入到其他地方之前,它会一直存储在剪贴板中。例如,您可能要复制网站上的一部分文本,然后将其粘贴到电子邮件中。大多数 Windows 程序中都可以使用剪贴板
  对于经常使用Ctrl+C/X、Ctrl+V的Windows用户来说,这个解释是非常易懂并且符合人们感观上的认识的。
  既然我们今天讨论的话题是如何在VBA程序中通过API调用剪贴板,那就先看看微软MSDN中对于剪贴板的定义吧:剪贴板是一组用于在多个应用程序之间交换数据的函数和消息(原文:The clipboard is a set of functions and messages that enable applications to transfer data)。
  呵呵,是不是有些晕?没关系,因为这句话是针对C/C++程序员给出的说明,我们仍然可以按习惯上的理解把它当做一个对象(使用过VB6的同志们可能更容易理解,因为VB6中确实有一个名字叫ClipBoard的对象,它其实是VB对windows的剪贴板API函数封装后产生的真正的对象)。
  然后,再来看看这个对象有什么特点,又是如何工作的呢?概括来说,剪贴板有以下的特性(这是我个人总结的,不一定正确或精确,也不一定全面):
  • 公开性
    剪贴板中的数据存放在全局内存中,因此大部分的windows应用程序都可以访问其中的数据,在遵守相关API函数约定的前提下,应用程序可以自由地打开剪贴板(OpenClipboard),读取剪贴板内的数据(GetClipboardData)、或者清空剪贴板(EmptyClipboard)、然后设置剪贴板内的数据(SetClipboardData),最后关闭剪贴板(CloseClipboard);
  • 独占性
    既然剪贴板是公开的,那么多个程序同时访问必然会导致冲突,比如数据互相覆盖。因此,Windows规定应用程序对剪贴板的访问是独占性的,当一个应用程序使用OpenClipboard打开剪贴板之后,其他程序 就不可以再访问剪贴板,直至前一程序使用CloseClipboard关闭剪贴板 。通常我们使用剪贴板的时候不会感觉受其他程序的影响,这是因为剪贴板内的数据操作都是在内存中进行的,速度非常快,对于特大块的数据,应用程序还可以选择延时处理(Delayed Rendering)机制以保证速度。但是,我们在对剪贴板编程的时候要注意以下两点:1)每次使用完剪贴板之后一定要记得使用CloseClipBoard关闭 它;2)在OpenClipboard和CloseClipboard之间不要放置耗时很长的代码,以免影响其他程序正常工作。
    设置剪贴板内数据的应用程序窗口被称为剪贴板数据拥有者(ClipboardOwner),可以通过GetClipboardOwner函数获得它的句柄。反过来说,如果一个应用程序想向剪贴板中放入数据,需要先成为ClipboardOwner。程序要成为ClipboardOwner需要先将自己的句柄传给OpenClipboard函数,如果剪贴板中已经有数据存在,还需要先调用EmptyClipboard;
  • 多元性
    剪贴板中可以同时存放多种格式的数据,各自放在全局内存的不同位置 ;剪贴板中的数据有标准格式/预定义格式的,如文本、位图、Wav声音……;也有非标准格式/用户自定义格式,比如word中的域和公式、Excel中的图表
  • 可检索性
    对于每种在剪贴板中存放过的格式,Windows都会给它分配一个独特的长整型编号,通过这个编号可以知道对应的数据格式的名称(GetClipboardFormatName),或者 查询对应的数据在剪贴板中是否存在(IsClipBoardFormatAvailable),如果存在,还可以通过这个编号找到对应的数据在内存中存放的位置(GetClipboardData);
    对于标准格式,这个编号是固定的,可以通过VB6自带的APIViewer查询以CF_开头的常量得到,比如:
    Public Const CF_TEXT = 1
    Public Const CF_BITMAP = 2
    Public Const CF_METAFILEPICT = 3
    Public Const CF_SYLK = 4
    ……
    对于非标准格式,这个编号由提供此格式数据的应用程序给此格式定义一个名称,然后将此名称传递给RegisterClipboardFormat函数,如果此名称已经存在,函数将返回此名称对应的编号,如果此名称不存在,函数将返回一个之前未使用的编号,在系统关机重启之前,这个名称和编号可以一直使用下去。
  • 可监视性
    有一组特别的窗口可以持续监视剪贴板内的数据变化,这组窗口被称作剪贴板观察程序(ClipboardViewer),由它们构成的这个小圈子叫作剪贴板观察程序链ClipBoardViewerChain,每当剪贴板内数据发生变化时,windows将向消息链中的第一个窗口发送一个WM_DRAWCLIPBOARD消息,再由它转发给第二个……直至消息到达消息链中的最后一个窗口,同样,当消息链中的成员发生变化时,每个窗口都会收到一个WM_CHANGECBCHAIN消息;
    通过SetClipboardViewer函数,应用程序可以将自己的窗口句柄告诉windows,从而注册成为剪贴板观察程序链中的一员;通过ChangeClipboardChain函数,应用程序可以退出剪贴板观察程序链而不影响它后面的窗口继续接收相关消息。
    希望以上文字能对大家重新认识剪贴板能够有所帮助。休息一会儿,我们再讨论剪贴板在Office VBA中的用途。

二、剪贴板在Excel VBA中的用途
  只要进行了正确的API声明,我们在VBA中也可以对剪贴板的进行操作,以它为媒介,我们可以访问到通过VBA难以接触到的数据,从而给我们的VBA或Office应用程序增加一些新奇和实用的功能。这里全是以Excel为例,在其他Office程序中原理一样。
  下面先看三个实例,它们分别演示了剪贴板数据的读取、写入和监视。
  附件1: ShapeToPicture.rar (86.95 KB, 下载次数: 1543)
  运行效果:

说明:这个代码演示了如何在窗体中浏览工作簿中所有Shape对象,并且提供了按各种格式保存的功能。完成这个程序,并不能说明本人对各种图像格式有多深的认识,真正的幕后英雄是Excel,是它在我们选中工作表中的Shape对象并按下Ctrl+C后的1/1024柱香的时间内把这个Shape换成了bmp, jpg,gif, png,emf等各种格式并放在了剪贴板中,我们所做的只是把这些数据从剪贴板中取出并简单进行了加工,不过效果已经很理想了,下次再有人跟你说用把工作簿另存为网页的方法可以取出xls中所有的图形时,你就可以有得吹嘘了。说正题,把这个代码稍作修改,就可以做出一个用右键菜单导出当前选中图形的加载宏。我以前做过一个,不过代码不如现在这个严谨和通用。

再看第二个例子: MSFlexGrid.rar (51.89 KB, 下载次数: 577)

这个代码演示的是向放入数据,在userform窗体上的FlexGrid中选择一个单元格区域,然后通过右键或快捷键可以复制和剪切数据,回到Excel工作表后可直接粘贴。当然,跟华丽的Excel比起来,这个就有点槑了,不过这至少提供了一种在窗体上的表格和Excel工作表之间交换数据的思路,这种操作方式也更符合大多数用户的习惯。

接下来看第三个:
clipboardviewer.rar (19.81 KB, 下载次数: 588)
这是一个监控剪贴板数据变化的示例,很简单的一个功能:当剪贴板内数据发生变化时,在窗体左侧的列表中显示当前剪贴板内可用的数据格式,如果里面有文本,则将文本显示在TextBox中,像迅雷的监视剪贴板大致也是这个原理。

  看完之后,你可能对Windows剪贴板产生了一些兴趣。当然,剪贴板在VBA中的使用不止于此,套用一句广告词,哪啥来着?心有多大,舞台就有多大。接下来,剪贴板之旅正式开始,我们理论实践相结合,先逐个认识相关的API函数,最后让他们一起协作帮我们完成某个目的。


评分

2

查看全部评分

TA的精华主题

TA的得分主题

 楼主| 发表于 2013-9-22 11:09 | 显示全部楼层
[广告] Excel易用宝 - 提升Excel的操作效率 · Excel / WPS表格插件       ★免费下载 ★       ★ 使用帮助
三、如何操作剪贴板内的数据
    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函数,我们就可以将剪贴板中所存放的内存对象复制到自己定义的变量、数组或字符串中了。为了加深印象,我们用图形重现一下整个工作流程:

理清了思路,你现在可以尝试写一个读取剪贴板内文本的过程了
第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函数,我们可以使用剪贴板内全部格式的数据,下面我们就转入剪贴板数据格式的讨论。

TA的精华主题

TA的得分主题

 楼主| 发表于 2013-9-22 11:11 | 显示全部楼层
[广告] VBA代码宝 - VBA编程加强工具 · VBA代码随查随用  · 内置多项VBA编程加强工具       ★ 免费下载 ★      ★使用手册
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 =    2Bitmap对象Bitmap GDI对象的句柄
Const CF_METAFILEPICT = 3Metafile Picture格式Metafile Picture的句柄
Const CF_SYLK =     4微软符号连接格式
(Microsoft Symbolic Link Format)

Const CF_DIF =      5Software 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 = 13Unicode文本格式存储此字符串的内存对象的句柄
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等专门的工具中去分析。
附件4: clipboardviewer.rar (115.33 KB, 下载次数: 320)
软件界面:


  由于剪贴板内的数据是由收到“复制”或“剪切”命令的应用程序放进去的,而各种应用程序所处理的数据格式范围也不一样,因此,不同的应用程序在不同的上下文环境中放进剪贴板的数据格式种类也不同。这个很容易理解,当我们使用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中很常用,我们要习惯它。
  

评分

3

查看全部评分

TA的精华主题

TA的得分主题

 楼主| 发表于 2013-9-22 11:12 | 显示全部楼层
[广告] VBA代码宝 - VBA编程加强工具 · VBA代码随查随用  · 内置多项VBA编程加强工具       ★ 免费下载 ★      ★使用手册
最后,发一个在立即窗口中输出剪贴板内所有可用格式编号和名称的过程作为本节的结束:
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

TA的精华主题

TA的得分主题

发表于 2014-2-7 10:13 | 显示全部楼层
liucqa 发表于 2013-9-22 11:12
最后,发一个在立即窗口中输出剪贴板内所有可用格式编号和名称的过程作为本节的结束:
Public Declare Fun ...

老师:请帮忙看看 http://club.excelhome.net/thread-1057488-1-1.html ,学习此贴因个人基础太差没解决我的问题,请抽空看看,谢谢!

TA的精华主题

TA的得分主题

发表于 2014-2-8 22:30 | 显示全部楼层

TA的精华主题

TA的得分主题

发表于 2014-2-9 09:46 | 显示全部楼层
[广告] Excel易用宝 - 提升Excel的操作效率 · Excel / WPS表格插件       ★免费下载 ★       ★ 使用帮助
谢谢分享!!辛苦了

TA的精华主题

TA的得分主题

发表于 2014-2-14 16:12 | 显示全部楼层
谢谢分享!!辛苦了

TA的精华主题

TA的得分主题

发表于 2014-4-9 21:14 | 显示全部楼层

TA的精华主题

TA的得分主题

发表于 2014-8-21 18:55 | 显示全部楼层
[广告] VBA代码宝 - VBA编程加强工具 · VBA代码随查随用  · 内置多项VBA编程加强工具       ★ 免费下载 ★      ★使用手册
可以利用下,每次回帖、谢谢!辛苦了。
您需要登录后才可以回帖 登录 | 免费注册

本版积分规则

手机版|关于我们|联系我们|ExcelHome

GMT+8, 2024-11-14 15:33 , Processed in 0.053676 second(s), 13 queries , Gzip On, MemCache On.

Powered by Discuz! X3.4

© 1999-2023 Wooffice Inc.

沪公网安备 31011702000001号 沪ICP备11019229号-2

本论坛言论纯属发表者个人意见,任何违反国家相关法律的言论,本站将协助国家相关部门追究发言者责任!     本站特聘法律顾问:李志群律师

快速回复 返回顶部 返回列表