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

Excel 技巧网

 找回密码
 注册

QQ登录

只需一步,快速开始

手机号码,快捷登录

查看: 17342|回复: 37

[Excel VBA] 如何在VBA中使用钩子(HOOK)以及WINDOWS消息机制简介

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


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

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

    x
    本帖最后由 い卋玑┾宝珼 于 2012-7-31 12:02 编辑


      要讲钩子(HOOK),就要了解WINDOWS的消息机制,还要初步会进行API编程,因此,我们首先简要的讲讲API和WINDOWS消息机制。

      文中的措辞,特别是API编程,都可以写一本书的,本文知识给予API初学者使用,用最通俗的语言去讲解,如有不当,也请各位指正。API很多东西都是C语言过来的,理解不当也请指正。

     
     一、    API基础知识
      (一)WINDOWS API
      WINDOWS API是WINDOWS自带的一套函数集,可以直接访问操作系统的底层,学API的东西,VBA的等级首先要为中级,也就是对VBA有个系统性的了解后,方适宜介入。这些函数,可以在,http://www.vbgood.com/api.html,等资源里面,进行查询。

      (二)动态链接库(DLL)
      API的很多函数保存在哪?存放在DLL文件中,一旦应用程序要调用的时候,可以引用链接某个DLL文件,所有应用程序都有自己的私有空间,每个进程的空间都是相互独立的,当进程在载入DLL时,系统自动把DLL地址映射到该进程的私有空间,这样,调用这些函数就非常方便。

        (三)API声明
      VBA中,声明格式如下:
      Declare Function 函数别名 Lib “DLL文件名” Alisa “函数名” (ByVal/ByRef 变量名 As 类型,…) As 输出数据类型
      在模块头部声明一下,就可以在VBA里面使用这些函数了,ByVal/ByRef这个事VBA基础的老概念啦,呵呵,自行复习吧。VBA里面数据类型很多是一个自定义数据结构,这个就要复习Type语句啦,呵呵。另外,有时候这个数据类型是个指针,那VBA貌似不支持指针,其实不然。

      (四)了解指针
      指针是啥?最通俗的话讲,指针就是数据的内存地址。在32位WINDOWS中(帖子最后来得及的话,后面讲讲64位的区别吧,变动不是很大,现在还是蛮多机子是32位的,因此从32位讲),指针/内存地址就是32位长的。那储存这个内存地址的变量,就是指针变量。
    由于指针访问的就是数据的内存地址,因此,这数据交换等操作中,速度比通常的通过一般变量名赋值,快得多。
      VBA中,和指针最靠近的概念就是ByRef,就是VBA中默认的参数按内存地址传递,即传递实际参数的地址/指针。简而言之,例如,在调用FUNCTION的时候,用ByRef传递参数,实参和形参指向的是同一个内存地址,形参和实参是一起变化的。如果采用ByVal,形参的改变就和实参没关系了,因为是传值,传递的是数据的副本,也就数据的值。这些都是VBA基础,不多讲了。
      VBA如何获取指针呢,很简单,一个函数——VarPtr,这个函数,其实VBA是隐藏函数。
    试看看下面的代码:
    1. Sub t()
    2.     Dim a As Long, za As Long, zb As Long
    3.     a = 100
    4.     za = VarPtr(a)    'za就是个指针变量,存放着变量a代表的100这个数据的内存地址。
    5.     zb = VarPtr(100)    '注意,100被保存在临时变量里面了,虽然它没有变量名,还是有地址的
    6.     Debug.Print za, zb '存放的都是内存地址咯
    7. End Sub
    复制代码
      知道如何取出数据的指针了,那如何采用指针的方式,复制数据呢?这就又要用到另一个API函数了——CopyMemory,它的语法是:  CopyMemory目标的指针,复制源的指针,内存字节长度(看数据源的数据类型了,如果是长整形,长度是4个字节,实在不知道就用lenb函数)。这样,就以内存拷贝的形式,进行了数据复制。


      结合上面所说的,大家看懂以下的程序,就没问题了。
    1. Private Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" (Destination As Any, Source As Any, ByVal Length As Long)
    2. Sub jh()
    3.     Dim a As Long, b As Long
    4.     a = 1000
    5.     CopyMemory b, a, LenB(a)
    6.     Debug.Print b
    7.     CopyMemory b, 2000&, 4
    8.     Debug.Print b
    9.     a = 3000
    10.     CopyMemory b, ByVal VarPtr(a), LenB(a)
    11.     Debug.Print b
    12.     CopyMemory ByVal VarPtr(b), 4000&, 4
    13.     Debug.Print b
    14.     a = 5000
    15.     CopyMemory ByVal VarPtr(b), a, LenB(a)
    16.     Debug.Print b
    17.     a = 6000
    18.     CopyMemory ByVal VarPtr(b), ByVal VarPtr(a), LenB(a)
    19.     Debug.Print b
    20. End Sub
    复制代码
      (五)字符串   另外,再讲讲字符串。VBA的字符串,其实是一个指向Unicode字符数组的指针,也就是:
    1. Dim str as string
    2. str = “help”
    复制代码
      结果如下图
       image001.png
      Str其实是个指针变量,指向一个Unicode数组,这个数组内存位置的前面4个字节长的字段,是用来储存字符串的字节长度。另外,这个数组,是以两个字节的空字符串结束的。注意,这个指针是指向字符数组的开始,而不是指向前面那个长度字段。另外,我们还可以用Strptr这个隐藏函数来获取这个字符数组的起始地址看看下面的案例:
    1. Private Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" (Destination As Any, Source As Any, ByVal Length As Long)
    2. Sub st()
    3.     Dim s As String, a() As Byte, cs As String, cd As Long, cs1 As String

    4.     s = "hello"
    5.     a = s    'a就是字符串的unicode数组去最后双字节空字符串的部分,固定用法记住即可
    6.     cs = String(Len(s), vbNullChar) '返回结果的时候,API函数不会自动生成字符串只会填充字符串,必须要做好目标长度的字符串来缓冲,以免内存溢出。
    7.     CopyMemory ByVal cs, ByVal s, LenB(s)
    8.     cs1 = String(Len(s), vbNullChar)
    9.     CopyMemory ByVal StrPtr(cs1), ByVal StrPtr(s), LenB(s)
    10.     CopyMemory cd, ByVal StrPtr(s) - 4, 4 '字符串数组起始位置-4个字节,就是字符串字节长度的起始地址了,由此可以获得这个字符串的字节长度,和lenb函数结果一样。
    11.     Debug.Print cs, cs1, cd
    12. End Sub
    复制代码
      虽然上面两个方法的效果差不多的,但是区别在于下面一个例子:
    1. Private Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" (Destination As Any, Source As Any, ByVal Length As Long)
    2. Sub t2()
    3.     Dim s As String, sb1(1 To 5) As Byte, sb2(1 To 5) As Byte, a() As Byte
    4.     s = "12"         '1的ANSI码是49,UNICODE字节数组是49 00,而2的ANSI码是50,UNICODE字节数组是50 00
    5.     a = s
    6.     CopyMemory sb1(1), ByVal s, LenB(s)
    7.     CopyMemory sb2(1), ByVal StrPtr(s), LenB(s)
    8. End Sub
    复制代码
    image002.png
      很明显地能看出,VBA默认储存文本的方式是Unicode字符数组,当需要传送UNICODE文本数组给予API函数时,其实,VBA将强制做了个ANSI数组版本的转换拷贝,然后才将这个转换后ANSI版本的字符数组指针传送给API函数,所以返回的结果是不一样的。其实不单单是传出的过程,传回的时候也是一样的,把先前那个ANSI字符数组回转回原来的UNICODE数组。

      (六)过程/函数的指针
      在VBA中一个过程可以调用另一个过程/函数,一样的,API函数也可以调用我们VBA里面的过程。但是,要在API里面传递过程/函数,也是通过过程/函数的指针实现的。VBA里面怎么办呢,用:AddressOf 过程/函数名,AddressOf将其后面的过程/函数的地址传递给一个 API函数。上面这些都是API要注意的,特别在传递参数的时候。下面讲讲与钩子(HOOK)非常相关的,WINDOWS消息传递机制的概念和WINDOWS的一些基础知识。





    评分

    参与人数 2魅力值 +10 收起 理由
    lslly + 5 才女
    0Mouse + 5 西西才女啊!强!

    查看全部评分

     楼主| 发表于 2012-7-30 22:14:27 | 显示全部楼层
    本帖最后由 い卋玑┾宝珼 于 2012-7-31 12:37 编辑

    二、WINDOWS一些基础概念

      (一)进程和线程  进程是一个正在运行的WINDOWS应用程序的实例和系统分配给它一组必要的资源(内存等)。有些晦涩吧,我们从程序的运行过程开始说吧,比较清晰易懂。
      当我们运行一个程序是,首先,WINDOWS会为这个程序创建一个进程空间,并且为其分配一些资源。然后将可执行文件按照一定规则加载到该进程空间内,操作系统还会根据可执行文件的“说明”,将相应的动态链接库(DLL)映射进程空间内。接下来,WINDOWS会为该进程空间创建一个主线程,为该主线程分配一些资源,主线程从这个exe文件指定的位置开始执行代码,程序开始运行。这么看,进程就是WINDOWS负责为一个程序构造一个独立的环境,这个环境内的资源可以相互直接访问,不同的进程之间则无法直接打交道。当一个进程创建之后,操作系统会自动为它创建一个主线程,用来执行程序代码,那线程又是什么呢?
      线程则是CPU实际执行任务的环境,负责执行进程空间里的代码,一个进程空间可以有多个线程。每个线程包含一组状态,其中一些用来记录和恢复CPU的执行状态,另一些则是由程序的自行记录的内容,还有一些则是给系统内核使用的。WINDOWS内核会进行线程调度,按照一定规则给每个线程分配CPU时间。一般情况下,同级别的每个线程都会获得一小段很短的时间片,使各个任务看起来像在“同时”运行,直到执行完毕或是采用某种方式暂停或退出为止。
      另外,线程中如果创建了窗口,就叫GUI线程,否则就是工作线程。

      
    (二)窗口
      大部分应用程序都有它的图形界面,这个就是它的窗口。窗口里面还有很多文本框和按钮等,这些就是控件。窗口句柄就是一个32的数值,用来系统识别这个窗口。那窗口里面的控件,子窗口啊,也一样,也拥有自己的句柄。
      注意,窗口的查找过程,要先用FINDWINDOW函数(自己翻查API手册吧)查找到顶层父窗口的句柄,然后才能用FINDWINDOWEX查询,这个窗口下属的控件或子窗口的句柄。大家看附件WinExplorer.rar这个工具,就是一个高手用VB做的,用来查找窗口句柄的。我们可以用这个工具,和我们程序里面获得的句柄语句进行比对,校验句柄是否正确之用。

      
    (三)WINDOWS消息
      1、啥系WINDOWS消息
      WINDOWS是基于事件驱动的,当事件触发了(例如移动鼠标等),WINDOWS系统产生一个消息进行通讯。消息可以是系统产生,也可以是应用程序产生。例如,当应用程序改变了自己窗口大小的时候,系统也会产生消息。
      消息构成如下:
      Hwnd:接受该消息的窗口句柄
      Message:消息常量标识符,也就是我们通常所说的消息类型
      wParam:32位消息的特定附加信息,通常是一个与消息有关的常量值,也可能是窗口或控件的句柄。
      lParam:32位消息的特定附加信息,通常是一个指向内存中数据结构(类似VBA中的TYPE自定义结构)的指针
      time:消息创建时的时间
      pt:消息创建时的鼠标/光标在屏幕坐标系中的位置


      2、这个消息有何用?
      产生的消息,将根据消息的窗口句柄,传给相应的窗口,每个窗口都有一个叫做窗口过程的函数,用来处理发送过来的消息。当窗口过程接收到消息之后,他就会使用消息标识符来决定如何处理消息,如果他不处理,它会将消息传回到执行默认的处理。窗口过程通过调用DefWindowProc来做这个默认处理。窗口过程函数处理完消息后就会把控制权转给操作系统。处理的结果,就是我们鼠标点击等后的效果。


      3、消息的类别
      消息主要有系统消息和应用程序消息。当系统要和应用程序通讯的时候,他就会发出或者投递一个系统消息,它使用这些消息来控制应用程序的操作和为应用程序提供输入或者其他信息。应用程序也可以发动和投递系统定义消息,应用程序一般使用这些消息来控制某些控件的动作。另外,应用程序可以创建消息以供他自己的窗口或者和其他进程的窗口通讯而用。


      4、消息的投递过程
      系统使用两个方法来传递消息给窗口过程:一种是投递到消息队列,还有一种就是投递消息到系统定义的一个内存对象临时存储,并且直接把这个消息发送给窗口过程(这个方法不会进入消息队列)。需要投递到消息队列的消息称为队列化消息。他们主要是用户通过键盘或者鼠标输入。其他直接发送给窗口过程的消息称为非队列化消息。消息队列是啥呢?得从他的工作机制进行讲解。


      5、何为消息队列
         系统维护着一个系统级的消息队列,同时为每个GUI线程(前面线程讲了哈)维护着一个线程级的消息队列。所有线程在最初创建的时候是没有消息队列的。只有当线程首次调用用户用户函数或者图形设备接口函数的时候系统才会为线程创建一个消息队列。所以只有GUI线程有消息队列。
         只要用户移动鼠标,单击鼠标按钮或者敲击键盘的时候,设备驱动程序都会为鼠标或者键盘把输入转换成消息,并且把这个消息放在系统消息队列中。系统一次一个的从系统消息队列中取出消息,分析确定目标窗口,接着把这个消息投递到创建目标窗口的线程消息队列中。线程从他的消息队列中取出消息,让系统发送这个消息给对应的窗口过程函数进行处理。
      一般而言,系统总是把消息投递到队列的尾部,这样可以确保窗体遵循先入先出的顺序接收到输入消息,但是消息是有优先等级的,优先等级高的消息先行处理。


      6、消息传递的大致编程原理
         首先,应用程序通过调用GetMessage函数从线程中的消息队列中取出一个消息,然后使用DispatchMessage函数触发系统把这个消息发送给窗口过程函数进行处理。当消息队列中没有消息的时候,线程可以使用WaitMessage函数来把控制权让给其他线程。这个函数可以把当前线程挂起,直到线程消息队列中有新的消息被放置进来,那么线程才会继续执行。
         非队列化消息会绕过系统消息队列和线程消息队列直接发动给目标窗口过程函数,一般系统发送非队列化消息是为了通知受到事件影响的窗体。例如,当用户激活了一个新的应用程序窗口,系统会发送一系列的消息。非队列化消息也可能是应用程序调用某个系统函数导致的结果。


      7、动手玩玩
      了解了上面的这些基础理论之后,我们就可以进行一下简单的消息发送。玩玩常见的SendMessage吧。
    1. Declare Function SendMessage Lib "user32" Alias "SendMessageA" (ByVal hwnd As Long, ByVal wMsg As Long, ByVal wParam As Long, lParam As Any) As Long
    复制代码

      这个函数主要是向一个或多个窗口发送一条消息,一直等到消息被处理之后才会返回。父窗口和子窗口经常使用这个发送消息的方式互相通讯。不过需要注意的是,如果接收消息的窗口是同一个应用程序的一部分,那么这个窗口的窗口函数就被作为一个子程序马上被调用;如果接收消息的窗口是被另外的线程所创建的,那么系统就切换到相应的线程并且调用相应的窗口函数,这条消息不会被放进目标应用程序队列中。函数的返回值是由接收消息的窗口的窗口函数返回的。
      假设我们要去除EXCEL界面的左上角的图表,他对应的消息是什么呢,通过搜索我们的WINDOWS消息大全的附件,会查到,WM_SETICON这个消息,进行上网搜索这个消息,得到:
      参数: wParam:指定图标的类型。该参数可以为下列值之一:
              ICON_BIG   为窗口设置大图标
              ICON_SMALL  为窗口设置小图标
         lParam:新的大、小图标的句柄。如果该参数为NULL,由wParam参数指定的图标将会移除。
      因此,我们只要把lParam参数设为0即可。整个程序就这么短。如下:
    1. Private Declare Function SendMessage Lib "user32" Alias "SendMessageA" (ByVal hwnd As Long, ByVal wMsg As Long, ByVal wParam As Long, lParam As Any) As Long
    2. Private Const WM_SETICON = &H80
    3. Private Const ICON_SMALL = 0
    4. Private Const ICON_BIG = 1
    5. Sub test()
    6.     Dim hwnd As Long

    7.     hwnd = Application.hwnd '获取EXCEL窗口的句柄
    8.     SendMessage hwnd, WM_SETICON, ICON_BIG, 0&
    9.     SendMessage hwnd, WM_SETICON, ICON_SMALL, 0&
    10. End Sub
    复制代码

    WINDOWS消息大全.zip (19.37 KB, 下载次数: 159)
    回复 支持 反对

    使用道具 举报

     楼主| 发表于 2012-7-30 22:14:45 | 显示全部楼层
    本帖最后由 い卋玑┾宝珼 于 2012-8-1 00:17 编辑

    三、钩子HOOK

      (一)什么是钩子(hook)
      钩子(hook)是一种特殊的消息处理机制,可以监视系统或进程中的各种事件消息,截获发往某窗口的消息,然后进行处理。主要功能是监视,然后处理消息。说白了,就是一个Windows消息的拦截机制。
      钩子的种类很多,后面我们再详细讲,每种钩子可以截获并处理相应的消息,如键盘钩子可以截获键盘消息等。
      钩子还可以分为线程钩子和系统钩子, 线程钩子监视指定线程的事件消息, 系统钩子监视系统中的所有线程的事件消息。


      (二)工作原理
      在使用钩子前,我们先理解钩子的工作原理。当创建一个钩子时,WINDOWS会先在内存中创建一个数据结构,该数据结构包含了钩子的相关信息,然后把该结构体加到已经存在的钩子链表中去(系统或特定线程中,是允许同时存在多个钩子的,这样就形成了一个链)。新的钩子将加到老的前面。当一个事件发生时,如果安装的是一个线程钩子,钩子相关的函数将被调用。如果是一个系统钩子,系统就必须把钩子相关的函数插入到其它进程的地址空间,要做到这一点要求该函数必须在一个动态链接库中。
         需要注意:
      (1) 如果对于同一事件(如鼠标消息)既安装了线程钩子又安装了系统钩子,那么系统会自动先调用线程钩子,然后调用系统钩子。
      (2) 对同一事件消息可安装多个钩子处理过程,这些钩子处理过程形成了钩子链。当前钩子处理结束后应把钩子信息传递给下一个钩子的函数。而且最近安装的钩子放在链的开始,而最早安装的钩子放在最后,也就是后加入的先获得控制权。
      (3) 钩子特别是系统钩子会消耗消息处理时间,降低系统性能。只有在必要的时候才安装钩子,在使用完毕后要及时卸载。


      (三)制作钩子的标准流程和相关API函数说明
      1、制作钩子挂钩的函数  钩子函数指钩子在拦截了消息后,进行对应消息处理的函数,也可以通过返回其值TRUE直接抛弃消息,其格式为:
        函数名(nCodeas long, wParam as long,lParam as Any)
       参数说明:
        nCode:包含所拦截的消息本身的内含信息。(附件钩子函数有详细说明)
        wParam:消息标识,用于判断该消息是那种消息,如WM_MOUSEMOVE,WM_NCMOUSEMOVE
        lParam:包含所钩消息的信息指针,比如鼠标位置、状态,键盘按键等。


       2、如何创建钩子
      需要API SetWindowsHookEx函数:
    1. Public Declare Function SetWindowsHookEx Lib "user32" Alias "SetWindowsHookExA" (ByVal idHook As Long, ByVal lpfn As Long, ByVal hmod As Long, ByVal dwThreadId As Long) As Long
    复制代码

      这函数的返回值是这个钩子的句柄
      参数说明:
      idHook:钩子的拦截消息类型
      lpfn:拦截消息的函数指针,若第四个参数dwThreadId为0或者指向了一个其他进程创建的线程之标识符,则参数lpfn必须指向一个动态链接中的函数。反之,指向的是本进程内的线程,参数lpfn可以指向一个与当前进程相关的代码中定义的挂钩处理函数,VBA中表示为ADDRESSOF 函数名
      hMod:钩子函数所在的动态链接的句柄。若参数dwThreadId指示的线程由当前进程创建,并且相应的挂钩处理函数定义于当前进程相关的代码中,则参数hMod必须被设置为0&。
      dwThreadId:钩子所监视的线程的线程标识,可通过GetCurrentThreadId()获得线程号。对于系统钩子,该参数为0&。


       3、创建钩子后,通过钩子函数处理消息后,需要将信息传递给下一个钩子:
    1. Public Declare Function CallNextHookEx Lib "user32.dll" (ByVal hHook As Long, ByVal nCode As Long, ByVal wParam As Long, lparam As Any) As Long
    复制代码

      hHook:钩子句柄。
      nCode、wParam和lParam 是钩子函数对应的参数。


       4、卸载钩子函数
      当不再使用钩子时,必须及时卸载。
    1. Private Declare Function UnhookWindowsHookEx Lib "user32.dll" (ByVal hHook As Long) As Long
    复制代码

      参数hHook就是钩子的句柄。

    示例HOOK.zip (12.02 KB, 下载次数: 146)
    回复 支持 反对

    使用道具 举报

     楼主| 发表于 2012-7-30 22:15:03 | 显示全部楼层
    本帖最后由 い卋玑┾宝珼 于 2012-8-1 22:43 编辑

    新添加示例:拦截保护工作表的对话框

    拦截.zip (12.74 KB, 下载次数: 149)

    钩子函数.zip

    30.94 KB, 下载次数: 83, 下载积分: 消费券 -5 Ti币

    钩子函数

    WinExplorer.rar

    10.02 KB, 下载次数: 69, 下载积分: 消费券 -5 Ti币

    Hook例子程序

    示例HOOK.zip

    12.02 KB, 下载次数: 86, 下载积分: 消费券 -5 Ti币

    Hook示例

    一篇外文文献.zip

    25.08 KB, 下载次数: 81, 下载积分: 消费券 -5 Ti币

    参考文档

    帖子.zip

    114.09 KB, 下载次数: 106, 下载积分: 消费券 -5 Ti币

    主帖

    HOOK常用数据结构.zip

    13.42 KB, 下载次数: 117, 下载积分: 消费券 -5 Ti币

    参考文档

    WINDOWS消息大全.zip

    19.37 KB, 下载次数: 67, 下载积分: 消费券 -5 Ti币

    Windows消息大全

    回复 支持 反对

    使用道具 举报

    发表于 2012-7-30 23:42:18 | 显示全部楼层
    占楼围观,有能力的时候再学
    回复 支持 反对

    使用道具 举报

    发表于 2012-8-2 16:39:58 | 显示全部楼层
    谢谢分享!有些还是看不懂呀,得慢慢消化.
    有个问题请教一下:
    关于三楼的代码,可以用什么方法获取编辑框的内容吗?
    个人感觉如果能获取编辑框内容的话,可以搞出很多有用的东西.(在编辑状态下获取)
    回复 支持 反对

    使用道具 举报

     楼主| 发表于 2012-8-2 19:51:17 | 显示全部楼层
    DJ_Soo 发表于 2012-8-2 16:39
    谢谢分享!有些还是看不懂呀,得慢慢消化.
    有个问题请教一下:
    关于三楼的代码,可以用什么方法获取编辑框的内 ...

    哪里的编辑框是Excel单 元格的编辑框还是?

    点评

    这个都可以的呀,就是正在编辑的那个单元格没回车之前的内容. 也就是当前激活的那个编辑框里面的内容.  详情 回复 发表于 2012-8-3 09:01
    回复 支持 反对

    使用道具 举报

    发表于 2012-8-3 09:01:19 | 显示全部楼层
    い卋玑┾宝珼 发表于 2012-8-2 19:51
    哪里的编辑框是Excel单 元格的编辑框还是?

    这个都可以的呀,就是正在编辑的那个单元格没回车之前的内容.
    也就是当前激活的那个编辑框里面的内容.
    回复 支持 反对

    使用道具 举报

    发表于 2012-8-6 13:28:52 | 显示全部楼层
    必须精华啊,真心学习了
    回复 支持 反对

    使用道具 举报

    发表于 2012-10-5 01:12:21 | 显示全部楼层
    留记号,以后学习了
    回复 支持 反对

    使用道具 举报

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

    本版积分规则

    关闭

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

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

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

    GMT+8, 2018-12-17 17:40

    Powered by Discuz! X3.4

    © 2001-2017 Comsenz Inc.

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