设为首页收藏本站|繁體中文

Excel 技巧网

 找回密码
 注册

QQ登录

只需一步,快速开始

手机号码,快捷登录

查看: 14358|回复: 22

[Excel VBA] #第1期专题#如何运用VBA调用WINDOWS剪贴板

  [复制链接]
发表于 2012-5-24 23:50:02 | 显示全部楼层 |阅读模式
  • 署名作者: xmyjk
  • 版权声明: 版权归本站与作者共有 除本站官方外非作者本人转载须经许可并注明出处
  • 本文来自:
  • 引用作品:
  • 适用版本: 2013 2010 2007 2003以前版本 
  • 语言环境: 简体中文
  • 学习方法: 掌握Excel技巧的关键是动手操作 | 下载 ≠ 知识


  • 免费注册成为本站会员,享用更多功能,结识更多Office办公高手!

    您需要 登录 才可以下载或查看,没有帐号?注册

    x
    本帖最后由 rongjun 于 2012-5-30 14:56 编辑

    一、        何为WINDOWS剪贴板

        剪贴板是内存中的一块连续可变的区域,使得在各种应用程序之间,实现数据交换和共享信息。在WINDOWS的运行窗口,键入clipbrd,可以弹出官方的“剪贴簿查看程序”,实现对剪贴板内数据的监控。如下图:

    #第1期专题#如何运用VBA调用WINDOWS剪贴板

    #第1期专题#如何运用VBA调用WINDOWS剪贴板


        它的工作原理和流程,用通俗的语言举例,例如我们在IE中复制了网页中我们喜欢的一张图片,那描述这张图片的数据,将存放在剪贴板所在的特定内存区域中。一旦,我们在WORD中黏贴了这张图片,其实是WORD这个应用程序,访问了剪贴板,读取了这张图片的数据,然后乾坤大挪移到我们的WORD文档中进行储存。因为都是在内存中操作,所以速度非常快。

    评分

    参与人数 7技术分 +1 魅力值 +35 收起 理由
    chenlifeng + 5 师祖,我剪贴版上有内容,想通过代码复制到.
    白米饭 + 5 学习**写的方法
    CheryBTL + 5 师傅V5
    水星钓鱼 + 1 功力深厚
    rongjun + 10 仰望一下:)

    查看全部评分

     楼主| 发表于 2012-5-24 23:51:05 | 显示全部楼层
    本帖最后由 xmyjk 于 2012-5-24 23:55 编辑

    二、WINDOWS剪贴板的数据格式

          既然:剪贴板是用于多个应用程序之间交换数据的,那存放在剪贴板的数据,是怎样的呢?

          ◆首先,由于存放在全局内存中,他们是公开的,应用程序们可以访问到的,并能交换所需的数据的。

          ◆其次,既然是公共的数据,那这些数据是有规范标准,是系统/用户已经预先设定格式,可以在不同的应用程序中进行交换。
             例如我在网页复制了一段文字,既可以黏贴到记事本,也可黏贴到WORD。这说明了,这些数据是具有某些标准格式的,符合系统或程序间的设定的格式的。

           ◆进而,应用程序们都可以访问这些数据,那么,这些数据就必然是多元的。这就类似我们EXCEL的选择性黏贴。我们复制了单元格的一个公式,可以相对的黏贴这个公式,也可以仅黏贴值,还可以黏贴成图片。
              这说明了,剪贴板中是可以同时存放多种格式的数据。我们移交给剪贴板的某个数据或消息,将在剪贴板内,自动以尽可能多种的以预设过数据格式进行呈现(取决于发起复制或剪贴的应用程序,不同应用程序不同上下文环境,处理数据格式范围不同,放进剪贴板数据格式类别也不同)。微软建议,当向剪贴板中放入数据时,首先放入最能描述所要传递的信息的数据,因为它所包含的信息最接近原始数据所要表达的信息。当从剪贴板中读取数据时,将按顺序枚举所有的数据格式,如果该数据格式在目标应用程序可用,则进行黏贴,否则继续枚举,直至有可用数据。
             即使上传的数据结构自身就非常简单,就例如记事本的文本,剪贴板也能按照一些标准的预设格式,在内存中自动转换,生成多个剪贴板数据格式,以便多种程序的交换。就例如,我们仅仅在记事本复制了一小段文字,剪贴板也能按照如下几个格式,自动进行多样化存放。如下图:

    #第1期专题#如何运用VBA调用WINDOWS剪贴板

    #第1期专题#如何运用VBA调用WINDOWS剪贴板

             翻阅了下资料,有四类数据,剪贴板是自带转化功能的(剪贴板格式名称请参与下面的介绍):
          (1)CF_TEXT、CF_UNICODETEXT与CF_OEMTEXT这三个文本格式相互之间;
          (2)CF_METAFILEPICT和CF_ENHMETAFILE这两个图元文件相互之间;
          (3)CF_BITMAP、CF_DIB与CF_DIBV5这三个位图数据格式相互之间;
          (4)如果CF_DIB与CF_DIBV5的数据格式,将自动转化CF_PALETTE的数据格式,这个是单向的,反之不成立。
       
            总之,剪贴板的数据是以多种数据格式进行呈现的。

            ◆最后,这些数据格式,是能被检索查询的,如果新的应用程序,需要新的数据格式,也可以使用API的RegisterClipboardFormat函数进行注册。这些已经预设或者注册过的,剪贴板存放过的数据格式,WINDOWS都会用一个长整形的编号,像字典一样,进行记录。通过这个编号,我们可以获取对应格式的数据。
    对于WINDOWS系统初始的标准格式,这个编号是固定的,如下图:

    #第1期专题#如何运用VBA调用WINDOWS剪贴板

    #第1期专题#如何运用VBA调用WINDOWS剪贴板

            
            如果是非系统标准格式的,允许运用RegisterClipboardFormat函数,用数据格式的名称,去注册这个格式,如果注册成功,将得到新的剪贴板数据格式对应的编号。如果重名,将直接返回已有这个数据格式名称对应的格式编号。
    回复 支持 反对

    使用道具 举报

     楼主| 发表于 2012-5-24 23:56:55 | 显示全部楼层
    本帖最后由 xmyjk 于 2012-5-25 00:23 编辑

    三、获取剪贴板数据的套路和主要API的剪贴板函数简介

        一般而言,获取剪贴板数据的大致步骤如下(按需增减):
       
        (一)用OpenClipboard打开剪贴板
    1. Declare Function OpenClipboard Lib "user32" (ByVal hwnd As Long) As Long
    复制代码
    参数hwnd:用来传递目前打开剪贴板的窗口的句柄。如果它修改了剪贴板内的数据,它就会成为剪贴板数据拥有窗口即ClipboardOwner,这样其他程序可以使用GetClipboardOwner去获取它。如果只是读取剪贴板内的数据,可以传递一个0&;如果要向剪贴板内写入数据,则必须指定有效的hwnd,否则不能成功调用SetClipboardData函数;
          
          如果调用成功,将返回剪贴板的句柄。失败返回0。

         (二)EnumClipboardFormats加while来循环出一个可用的剪贴板格式
    1. Declare Function EnumClipboardFormats Lib "user32" (ByVal wFormat As Long) As Long
    复制代码
    参数:wFormat表示剪贴板内已知可用的标准剪贴板格式或注册剪贴板格式的编号。在枚举的最开始,向函数传入0值,将返回第一个可用的剪贴板格式编号,将该返回值传给下一次函数调用,将得到下一个可用的剪贴板格式编号。以此循环,直至  返回0,可以枚举所有的可用的数据格式。

          或用IsClipboardFormatAvailable或GetPriorityClipboardFormat判断可用格式的存在
    1. Declare Function IsClipboardFormatAvailable Lib "user32" (ByVal wFormat As Long) As Long
    复制代码
    参数:wFormat标准剪贴板格式或注册剪贴板格式的编号
          如果剪贴板中存在相应格式的数据,则返回一个非0值否则返回0。
          如果一个应用程序可以处理多种数据格式,运行传入数组的API函数:
    1. Declare Function GetPriorityClipboardFormat Lib "user32" (lpPriorityList As Long, ByVal nCount As Long) As Long
    复制代码
    参数:lpPriorityList指向数组第一个元素的地址,这个数组中需按优先级别存放多个剪贴板格式编号;nCount表示数组内元素的个数;
          如果剪贴板内存在一种或多种数组中列出的格式,则返回优先级别靠前的剪贴板格式编号;如果剪贴板内有数据但不包含数组中列出的任何一种格式,则返回-1;如果剪贴板为空,则返回0。
    1. Dim arrf()
    2. Dim f As Long
    3. arrf = Array(49290, 49386, 1)
    4. f = GetPriorityClipboardFormat(arrf(0), 3)
    复制代码
    (三)使用GetClipboardData获得剪贴板的内存块首地址
    1. Declare Function GetClipboardData Lib "user32" (ByVal wFormat As Long) As Long
    复制代码
    参数:wFormat表示将要取出的数据格式的编号;
          如果成功,返回剪贴板中以指定格式存放的数据对象的句柄;失败,返回0;
          
         一般而言,返回的内存对象的句柄,但是如果数据是一个图像格式,返回的将是一个GDI对象的句柄,例如,位图格式的返回位图GDI对象句柄,图元文件返回的是图元文件对象的句柄等。GDI对象另贴再解析。先按返回内存对象的句柄进行后续讲解。

          (四)用GlobalLock锁定剪贴板那内存块
    1. Declare Function GlobalLock Lib "kernel32" (ByVal hMem As Long) As Long
    复制代码
    参数hMem是内存对象的句柄;
          成功,返回值是内存对象所占用内存的第一个字节的内存地址;失败,返回0

         (五)GlobalSize可以用来获取该内存块的大小
    1. Declare Function GlobalSize Lib "kernel32" (ByVal hMem As Long) As Long
    复制代码
    参数hMem是内存对象的句柄;
          成功,返回是内存对象所占用内存的字节长度,失败返回值0

          (六)用CopyMemory将二进制数据取出来并赋值给字节数组
    1. Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" (Destination As Any, Source As Any, ByVal Length As Long)
    复制代码
    参数:Destination是要赋值的数组第一个元素的内存地址;Source是来源的第一个字节的内存地址;Length是要复制的数据的长度;

         (七)处理数据,转换数据格式等等
           呵呵,这个就按需处理了。文本的,转换成文本数据,图片的,转换成图片,二进制的转换16进制翻译,等等

          (八)使用GlobalUnlock解除锁定
    1. Declare Function GlobalUnlock Lib "kernel32" (ByVal hMem As Long) As Long
    复制代码
    参数hMem是内存对象的句柄;
          被解锁,则返回0

          (九)CloseClipboard关闭剪贴板
    1. Declare Function CloseClipboard Lib "user32"() As Long
    复制代码
    成功,返回一个非0值;失败返回0

          有了上述的套路,我们就可以随意的读取剪贴板的相应数据了。
    回复 支持 反对

    使用道具 举报

     楼主| 发表于 2012-5-24 23:57:43 | 显示全部楼层
    本帖最后由 xmyjk 于 2012-5-25 00:05 编辑

    简易剪贴板查看程序.zip (18.22 KB, 下载次数: 121)
    回复 支持 反对

    使用道具 举报

     楼主| 发表于 2012-5-24 23:59:11 | 显示全部楼层
    本帖最后由 xmyjk 于 2012-5-25 11:37 编辑

    四、如何监控剪贴板

    (较难,作为拓展补充,以下涉及的知识,需要有WINDOWS消息传递机制和拦截WINDOWS消息的基础知识(或者再另贴讲吧)。涉及到相关的API,就不详细阐述,读者可自行查阅API手册。)

          WINDOWS剪贴板观察器(Clipboard Viewer)是一个显示剪贴板当前内容的窗口。剪贴板观察链(Clipboard Viewer Chain)是一系列相互独立的剪贴板观察窗口,是保存Clipboard   Viewer窗口以及他们之间的前后向关系的一个Windows系统链表,它们都能够监控并读取当前发送到剪贴板的内容。

          如何将我们的程序,注册成一个系统认可并可实时刷新的剪贴板监视程序呢?
          首先,使用SetClipboardViewer函数向剪贴板观察链中加入一个观察窗口。该函数需要传递的参数是观察窗口的句柄。返回值也是链条下一个窗口句柄。当一个窗口注册为Clipboard Viewer后,他会被加入Clipboard Viewer Chain,并得到链表中下一个Viewer窗口的句柄,该句柄必须保存以在响应消息时使用。Windows正是通过Clipboard Viewer Chain保证了所有Clipboard Viewer能接收和响应剪贴板变化消息。当剪贴板的内容发生变化时,该窗口会接收到一个WM_DRAWCLIPBOARD的消息。
          然后,响应WM_DRAWCLIPBOARD消息处理剪贴板内容的变化。
          最后,在程序退出或关闭时需要调用ChangeClipboardChain函数来将自己从观察链中删除。然后调用SendMessage函数把这些消息传递到观察链中的下一个观察窗口。

          另外,窗口都是有自己的窗口程序,来相应WINDOWS之间的消息传递,我们要把我们的窗体程序,注册成为相应剪贴板消息的监视程序,还需要如下步骤:
          首先,先用一个句柄,用GetWindowLong函数记录原来的窗口程序;
          然后,用SetWindowLong函数,设置相应剪贴板消息的监视程序和窗口进行绑定;
          接着,循环处理WINDOWS系统发来的消息,如果是剪贴板相关的消息,就来用我们写的剪贴板查看程序来处理,如果是非剪贴板查看的其他消息,就用CallWindowProc函数调用窗口原来的窗口程序处理消息。
          最后,当结束时,再次使用SetWindowLong将原窗口程序还原。
          
          不过,我做出来的程序,在WINDOWS XP的系统环境下,会造成EXCEL崩溃,但是在WIN7环境下,程序没有任何问题。或许我的理解在某方面还是有偏差的吧。待查证补充。也请大家指点。
    回复 支持 反对

    使用道具 举报

     楼主| 发表于 2012-5-24 23:59:55 | 显示全部楼层
    本帖最后由 xmyjk 于 2012-5-25 00:05 编辑

    简易剪贴板监视查看程序.zip (19.98 KB, 下载次数: 64)
    回复 支持 反对

    使用道具 举报

     楼主| 发表于 2012-5-25 00:00:52 | 显示全部楼层
    本帖最后由 xmyjk 于 2012-5-25 00:17 编辑

    五、其他补遗

    (一)剪贴板数据格式注册函数
    Declare Function RegisterClipboardFormat Lib "user32" Alias "RegisterClipboardFormatA" (ByVal lpString As String) As Long
    参数lpString表示指定要注册的剪贴板格式的名称
    注册成功,则返回该剪贴板格式所对应的编号,失败,则返回0;        如果被注册的剪贴板格式名称已经存在,并不会重新注册一个新的剪贴板格式,而是返回原有的同名剪贴板格式编号。

    (二)获取剪贴板数据格式名称的API函数
    Declare Function GetClipboardFormatName Lib "user32" Alias "GetClipboardFormatNameA" (ByVal wFormat As Long, ByVal lpString As String, ByVal nMaxCount As Long) As Long
    参数:wFormat表示剪贴板格式的编码;lpString表示用于接收剪贴板格式名称的缓冲字符串;nMaxCount:缓冲字符串的字节长度,超出部分将被截去。
    成功, 返回lpString中储存相关格式名称的字节数,否则返回0

    (三)将数据上传剪贴板的API函数
    Declare Function SetClipboardData Lib "user32" (ByVal wFormat As Long, ByVal hMem As Long) As Long
    参数:wFormat表示剪贴板格式的编码;hMem表示上传的数据对象的句柄

    (四)其他功能函数
    CountClipboardFormats:返回剪贴板可用数据格式的个数
    EmptyClipboard:清空剪贴板
    GetClipboardOwner:获取当前剪贴板是属于哪一个窗口的句柄
    GetClipboardSequenceNumber:返回剪贴板序号
    GetClipboardViewer:返回剪贴板监控程序所属窗口的句柄
    GetOpenClipboardWindow:返回打开剪贴板的那个窗口句柄

    (五) 另外,如果是纯文本格式数据对剪贴板的访问,可以使用DATAOBJECT,可参阅我以前写过的一个帖子。
    如何运用VBA实现复制指定选择的单元格区域的数据,使粘贴到QQ后出现的是文本形式的
    http://www.exceltip.net/thread-26337-1-1.html
    回复 支持 反对

    使用道具 举报

    发表于 2012-5-25 08:16:01 | 显示全部楼层
    只能仰视啦,收藏下
    回复 支持 反对

    使用道具 举报

    发表于 2012-5-25 09:37:53 | 显示全部楼层
    太牛啦**!仰望中!
    回复 支持 反对

    使用道具 举报

    发表于 2012-5-25 10:05:05 | 显示全部楼层
    太牛啦**!仰望中!
    回复 支持 反对

    使用道具 举报

    您需要登录后才可以回帖 登录 | 注册

    本版积分规则

    关闭

    站长推荐上一条 /1 下一条

    Excel技巧网的会员探讨问题仅代表其个人意见,与网站的立场无关。任何违反国家和地方相关法律法规的言论,本站有义务协助政府相关部门追究发言者的责任!
    本站中非注明转载文章与案例的版权为作者与Excel技巧网共有。若非原文作者,本站之外任何单位或个人未经允许,不得将其用于商业用途。
    若非原文作者,任何形式的非商业性转载必须获得Excel技巧网或作者允许,并注明作者和出处。
    会员发表的帖子如涉及版权纠纷,须自行负责。详情请参考注册时的网站服务条款。
    本站特聘法律顾问:沈学律师

    Archiver|手机版|Excel技巧网 ( 闽ICP备08107682号-2 ) | 闽公网安备 35020302032608号  

    GMT+8, 2019-7-23 11:12

    Powered by Discuz! X3.4

    © 2001-2017 Comsenz Inc.

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