ExcelHome技术论坛

 找回密码
 免费注册

QQ登录

只需一步,快速开始

快捷登录

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

[原创] VBA回调函数作用讲解

[复制链接]

TA的精华主题

TA的得分主题

发表于 2021-11-27 13:50 | 显示全部楼层 |阅读模式
本帖最后由 905738810 于 2021-11-27 14:31 编辑

在我的VBA DataAutomation数据处理类模块,一行代码搞定复杂数据中我在多处使用了回调函数的思想对于没有接触其他语言的VBA爱好者来说,很难理解他,下面我将开一篇新帖来说一说回调函数的好处
我通过对排序方法的优化来叙述回调函数
首先写一个简单的字符串排序函数如下:
  1. Function 字符串排序函数(ByVal st As String)
  2.     '拆分字符串为数组
  3.     arr = Split(st, ",")
  4.     '对数组排序
  5.     For i = 0 To UBound(arr) - 1
  6.         For j = i + 1 To UBound(arr)
  7.             '比较前后两值,如果前值大于后值就交换位置
  8.             If arr(i) > arr(j) Then
  9.                 k = arr(i)
  10.                 arr(i) = arr(j)
  11.                 arr(j) = k
  12.             End If
  13.         Next
  14.     Next
  15.     '对排序后数组合并为字符串
  16.     字符串排序函数 = Join(arr, ",")
  17. End Function

  18. Sub 排序演示()
  19.     st = "E,F,B,A,D"
  20.     Debug.Print 字符串排序函数(st)
  21. End Sub
复制代码
运行排序演示将得到如下结果:
image.png
为了方便理解,排序算法采用最简单顺序排序方法
看起来不错的排序,存在很多问题
问题1:只能升序不能倒序
小白的解决方法是,写两个排序函数
image.png
大佬解决方法是
image.png
最终代码 字符串排序.rar (13.94 KB, 下载次数: 25)

评分

3

查看全部评分

TA的精华主题

TA的得分主题

 楼主| 发表于 2021-11-27 14:11 | 显示全部楼层
问题2:对数字排序不准确如下
image.png
此问题的原因是因为Split函数拆分后的是文本类型String
VBA对文本类型比较是一个字符一个字符比较的,比如比较"10"和"9"
VBA会先比较"10"里的 "1"与"9" ,"1"小于"9" 那么比较就结束了结果是 "10"<"9"
image.png
这样一来前篇大佬虽然加了一个变量巧妙的解决了升序降序,但是这种问题就不单单是小修改了
这时就应该想一下如何让我们函数调用者自己来控制比较规则呢?
当然是回调函数,如下示例
image.png
在排序的比较阶段我把两个值提取出来单独写一个比较函数,这样就是回调了
以后需要修改比较逻辑我们可以直接修改比较函数,而不去修改排序代码
是不是很简单,是不是要说我被骗了,这也叫回调函数?
别急还有讲完

TA的精华主题

TA的得分主题

 楼主| 发表于 2021-11-27 14:24 | 显示全部楼层
[广告] Excel易用宝 - 提升Excel的操作效率 · Excel / WPS表格插件       ★免费下载 ★       ★ 使用帮助
继续上篇说,我增加了一个比较函数使排序算法更灵活
但灵活是有限的,在上篇代码中字符串排序函数永远只调用一个回调函数,就是比较函数
我们只能修改比较函数内容
下面来思考如何让字符串排序函数调用我们写的其他比较函数呢?
我们可以这样
image.png
字符串排序函数增加一个参数,函数名,通过Application.Run方法调用传入的函数名
这样我们可以任意的写比较函数,只需要传入对应的函数名就实现不同的比较条件
到此我的字符串排序函数已经优化完成了,回调函数你们懂了吗

TA的精华主题

TA的得分主题

 楼主| 发表于 2021-11-27 14:57 | 显示全部楼层
说完回调函数,我再说VBA里回调函数实现的方法
很遗憾VBA里的回调函数实现的方法都有缺点
我总结出五种方法:
第一种:Application.Run,前面排序就是用的Application.Run
'速度最慢 100万次回调24秒,不能参数传址,但是最稳定最方便
Run方法自己看帮助,这里不说了:演示代码:
  1. Private Sub test()
  2.     Dim col As Collection
  3.     Set col = New Collection
  4.     Dim i As Long
  5.     i = 1
  6.     s = Application.Run("run方法.测试函数", col, i)
  7. End Sub

  8. Private Function 测试函数(col As Collection, i)
  9.     col.Add 1
  10.     i = 2
  11.     测试函数 = "回调"
  12. End Function
复制代码
第二种:CallByName
'速度Run方法快的多100万2.6秒,很稳定,很方便,可以传址传值,但是回调方法只能写到
这是VBA的函数也是自己看帮助
模块代码:
  1. Sub test()
  2.     Dim col As Collection
  3.     Set col = New Collection
  4.     Dim i
  5.     i = 5
  6.     s = CallByName(New CallByName类, "CallBy", VbMethod, col, i)
  7. End Sub
复制代码
新建一个类,类名为"CallByName类",类中代码
  1. Function CallBy(col As Collection, ByVal i)
  2.     col.Add 1
  3.     i = 2
  4.     CallBy = "回调"
  5. End Function
复制代码
第三种:CallWindowProc
'速度最快100万0.3秒,但是回调函数必须按照固定规则否则崩溃
这是API函数,直接通过函数内存地址来调用,所以速度快到电脑冒烟
致命缺点:函数参数只能固定四个,操作内存地址的话用不好容易把excel蹦掉
我的DataAutomation类使用了很多这个回调我简称CWP回调
代码演示:
  1. #If VBA7 And Win64 Then
  2. Private Declare PtrSafe Function CallWindowProc Lib "user32" Alias "CallWindowProcA" _
  3.     (ByVal 回调函数 As LongPtr, 对象 As Object, 数字1 As Long, 数字2 As Long, ByVal 字符串 As LongPtr) As Boolean
  4. #Else
  5. Private Declare Function CallWindowProc Lib "user32" Alias "CallWindowProcA" _
  6.     (ByVal 回调函数 As Long, 对象 As Object, 数字1 As Long, 数字2 As Long, ByVal 字符串 As Long) As Boolean
  7. #End If
  8. Private Function 回调函数(对象 As Collection, 数字1 As Long, 数字2 As Long, 字符串 As String) As Boolean
  9.     对象.Add 2
  10.     数字1 = 数字1 - 200
  11.     数字2 = 数字2 + 100
  12.     字符串 = "456"
  13.     回调函数 = True
  14. End Function
  15. Private Sub test()
  16.     Dim col As Collection
  17.     Set col = New Collection
  18.     col.Add 1
  19.     Dim 数字1 As Long
  20.     数字1 = 3
  21.     Dim 数字2
  22.     数字2 = 1
  23.     Dim 字符串 As String
  24.     字符串 = "123"
  25.     s = CallWindowProc(AddressOf 回调函数, col, 数字1, 数字2, ByVal VarPtr(字符串))
  26. End Sub
复制代码
注意:我在测试中发现2021版office中此方法会导致excel崩溃
         我也找不到原因,所以只能用在2021版一下了
第四种:Implements(接口)
'速度仅次于CallWindowProc 100万0.7秒,很稳定,可以传址传值,
'但是回调方法只能写到类里,多个函数必须新建多个类,造成浪费,不方便

这个方法除了要新建类之外不是很方便其他都很优秀,以后我还会在我的
DataAutomation类中添加这个回调机制,以保证能在2021版office中正常使用
演示: 接口回调函数.rar (15.64 KB, 下载次数: 13)
第五种:Evaluate
利用Evaluate的特性也可以调用函数
具体优缺点不分析了,认识Evaluate的就懂了
代码:
  1. Sub test()
  2.     s = Evaluate("Evaluate回调(1,2)")
  3. End Sub
  4. Function Evaluate回调(a, b)
  5.     Evaluate回调 = a * b
  6. End Function
复制代码


评分

2

查看全部评分

TA的精华主题

TA的得分主题

 楼主| 发表于 2021-11-27 15:59 | 显示全部楼层
单元格数组排序
image.png
关于这个贴,我继续用我写的字符串排序函数来展示一下,回调函数的强大
演示如下:只需要定义一个比较函数,就可以很轻松的实现排序了
image.png
单元格数组排序.rar (14.38 KB, 下载次数: 14)


TA的精华主题

TA的得分主题

发表于 2021-11-27 14:05 | 显示全部楼层

TA的精华主题

TA的得分主题

发表于 2021-11-27 14:25 | 显示全部楼层

TA的精华主题

TA的得分主题

发表于 2021-11-27 14:33 来自手机 | 显示全部楼层
[广告] Excel易用宝 - 提升Excel的操作效率 · Excel / WPS表格插件       ★免费下载 ★       ★ 使用帮助
难道是address of?

TA的精华主题

TA的得分主题

 楼主| 发表于 2021-11-27 14:59 | 显示全部楼层

TA的精华主题

TA的得分主题

发表于 2021-11-27 15:16 来自手机 | 显示全部楼层
[广告] VBA代码宝 - VBA编程加强工具 · VBA代码随查随用  · 内置多项VBA编程加强工具       ★ 免费下载 ★      ★使用手册
905738810 发表于 2021-11-27 14:59
我总结出五种,还没审核通过

厉害了。。。

TA的精华主题

TA的得分主题

发表于 2021-11-27 15:25 | 显示全部楼层
对,不仅Split有这个问题,在反复使用同一个数组过程中也会出现这种情况,如例
单元格数组排序
https://club.excelhome.net/thread-1607198-1-1.html
(出处: ExcelHome技术论坛)
最后我采用的解决办法是用了
ReDim ar(1 To 2, 1 To UBound(br) + 1) As Variant
不然就是个字符型数组,无法正常排序。用Val函数也改不过来。
您需要登录后才可以回帖 登录 | 免费注册

本版积分规则

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

GMT+8, 2024-5-24 05:23 , Processed in 0.042747 second(s), 14 queries , Gzip On, MemCache On.

Powered by Discuz! X3.4

© 1999-2023 Wooffice Inc.

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

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

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