2023-11
1

不闪烁的跨表复制

By xrspook @ 8:00:17 归类于: 烂日记

昨天说到当我想用VBA把一个工作簿里面的某些工作表复制到另外一个工作簿的时候,如果要复制的工作表有多个,复制过程中就会出现闪烁。如果需要复制的工作表很多,闪烁的时间会比较长。我觉得批量选择,最后一次性复制是一个解决办法。如果不是用VBA,而是手动实现这个复制功能,的确会用批量选择,然后再复制。现在按住Ctrl,然后用鼠标选择需要复制的工作表,最后进行复制。如果要在VBA里面实现这个批量选择,该怎么做呢?

一个工作簿里的工作表该如何判断哪些是需要的,哪些是不需要的,对我来说只需要判定工作表的名称就可以,所以我可以对需要的工作表以某种方式结合起来,比如形成一个以固定分隔符的字符串。VBA里要表达批量选择,然后复制,到底该用什么东西呢?这一次我没有搜索,而是直接录制了个宏。结果发现原来批量选择再复制,在宏里默认是通过数组的。需要把目标工作表的名称组合成一个一维数组,然后用最普通的复制方式把拷贝到新的工作簿。所以我要做的就是把目标工作表的名称赋值给一个一维数组。因为不知道到底要复制多少个工作表,所以这个数组应该是一个动态数组,我马上想到的方式就是以默认分隔符打断字符串的方式赋值给一维的动态数组。这个操作非常高效,而且动态数组的大小完全不需要在一开始就定义好,字符串里面有多少东西就可以生成多大的一维数组。

接下来,我要做的是经过一轮循环筛选出某个工作簿里我需要的工作表的名称,然后把这些名称以固定分隔符的方式连接起来。最后在打断之前我还要干掉一个分隔符,因为无论我把分隔符放在最前面还是最后面,整个字符串还是会多一个分格符。我不知道多一个分隔符在赋值给一维数组的时候会不会失败,作为完美主义者,我宁愿手动把那个分隔符去掉。那不过是一个字符串取值的简单操作而已。处理好字符串以后,就可以打断那个字符串赋值给一维数组,最后通过经典的复制方式,把工作簿里的目标工作表拷贝到另外一个工作簿。

我没有测试过这样处理耗时跟基础经典处理有多大差别,但就肉眼来说,差别挺大。因为如果进行这般批量选择一次性复制,基本上可以秒杀完成,但如果使用经典的方法、工作表又比较多,我感觉得闪烁一秒钟以上。秒杀就能完成我感觉那个耗时会在0.3秒以内。

批量选择再复制的确可以秒杀实现拷贝工作表,但是光标会停留在最后一个被复制的工作表里,我需要光标仍然停留在我的点击VBA控件的工作表。所以最后我又增加了一个激活目标工作表,把鼠标锁定在某个单元格的功能。这个功能不是非如此不可的,没有也不影响数据处理本身,但就方便程度而言,这是显而易见的。就如没有初始化和拷贝数据那两个模块,核心功能没问题,但是多了这些贴心的小步骤。整个核对的过程会变得更顺手。

如果使用的时候能更顺畅,我宁愿把代码搞得复杂一些。毕竟复杂的代码是一次性的。

PS:测试
复制19个工作表:批量法0.85秒,经典法7.25秒。
复制8个工作表:批量法0.59秒,经典法3.02秒。
复制4个工作表:批量法0.48秒,经典法1.59秒。

Sub 批量拷贝工作表-批量法()
    Application.ScreenUpdating = False '关闭屏幕刷新
    Dim book As Workbook
    Dim sht As Worksheet
    Dim my_name As String, stringArray() As String
 
    my_name = ""
 
    For Each book In Workbooks
        If book.Name <> "AAA.xlsm" Then
            For Each sht In book.Worksheets
                If Not sht.Name Like "*BBB*" Then
                    my_name = my_name & sht.Name & ","
                End If
            Next
            my_name = Mid(my_name, 1, Len(my_name) - 1)
            stringArray = Split(my_name, ",")
            book.Sheets(stringArray).Copy before:=ThisWorkbook.Worksheets("条件")
        End If
    Next
 
    With ThisWorkbook
        .Worksheets("条件").Activate
        .Worksheets("条件").Range("A27").Select
    End With
 
    MsgBox "完成数据抓取!"
    Application.ScreenUpdating = True '打开屏幕刷新
End Sub
 
************************************
 
Sub 批量拷贝工作表-经典法()
    Application.ScreenUpdating = False '关闭屏幕刷新
 
    Dim book As Workbook
    Dim sht As Worksheet
    For Each book In Workbooks
        If book.Name <> "AAA.xlsm" Then
            For Each sht In book.Worksheets
                If Not sht.Name Like "*BBB*" Then
                    sht.Copy before:=ThisWorkbook.Worksheets("条件")
                End If
            Next
        End If
    Next
 
    With ThisWorkbook
        .Worksheets("条件").Activate
        .Worksheets("条件").Range("A27").Select
    End With
 
    MsgBox "完成数据抓取!"
    Application.ScreenUpdating = True '打开屏幕刷新
End Sub
2023-10
31

老脚本加新模块

By xrspook @ 10:52:25 归类于: 烂日记

几年以后,我终于又在某一个我用来核对账本的VBA脚本上加了两个新的模块。之前这两个模块的内容我都是手动实现的。第一个模块是初始化,就是把已经核对过的明细数据删除掉。初始化这个步骤眨眼间就完成了,因为是在同一个工作簿里进行。另外一个模块做的是数据抓取,抓取我需要核对的那个工作簿里的明细工作表。我要核对的那个工作簿里的汇总,那个工作表默认不抓取,因为那是用来核对的。另外一个跟明细数据和汇总数据不是一伙的工作表也不能抓取,因为那个表的数据格式跟其它的不一样。过去这几年来我做的事情都是手动的在VBA脚本所在的工作簿里删除掉已核对的数据明细,然后再把我需要核对的那个工作簿里的明细粘贴到VBA脚本所在的工作簿。这个操作很机械很单一,完全可以用VBA去实现,但是我却一直没有这么干,为什么呢?大概是因为最初的时候,我写的那个VBA脚本核心部分实现的功能已经减少了我绝大部分的工作量,简单实现了非常麻烦才能实现的事情,所以我觉得自己已经上岸了,相对来说,这些复制粘贴删除的东西比较简单。简单归简单,很容易能实现,但是日积月累经常性习惯性每一次都要整那么十几二十遍也是很烦的。为了简化初始化的那个步骤,我每次核对完就以不保存的方式关闭VBA脚本所在的工作簿,然后重新打开。

我不知道对以前的我来说,写着两个模块有多难,对现在的我来说,对今年已经在VBA脚本里浸润了好几个月的我来说挺简单。实现方法很简单,但是出来的效果我感觉上应该还可以改进。初始化的那个脚本已经很完美,没啥需要再进一步了,但是那个拷贝数据的脚本,如果需要复制的工作表有很多,屏幕会出现长时间的闪烁。那种闪烁源于不同工作簿之间切换导致聚焦位置改变。之所以这样,因为我所使用的复制方式是VBA教程里最简单的那款。微软的默认教程就是那样,当然你也可以使用不一般的方式。闪烁是因为不同工作表之间切换以及活动工作表活动单元格之间的变换。这个问题之前我已经发现。我以前的VBA脚本里也有这种问题。做一个循环,把某个工作表名称符合要求的就复制到 VBA所在的工作簿,这个循环是显而易见的,但是一个一个工作表去判断,一个一个复制显然就会出现很多次闪烁。如果我先去判断,然后把符合要求的全部选定,最后一次性复制,这样会不会就可以从多次闪烁变成一次闪烁呢?多次闪烁你肯定能感觉到闪烁的存在,但如果只是一次,估计那种闪烁感就会感知不到了。有人在网上提出过如何避免闪烁这个问题,但好像大家都没有在批量选择然后一次性复制上面想。我也想参考他们的做法改进,但好像不太适合我的实际情况。

我觉得闪烁这个问题是可以攻克的。

2023-08
23

追求更好

By xrspook @ 8:32:56 归类于: 烂日记

实现功能,我觉得只是最基本的要求。接下来我会考虑到底有什么是可以优化的,比如如果我做了很多个判断,我能不能少做一些,把那些判断合并起来或者里面我做了不少循环,循环通常是为了赋值,赋值完以后我有没有立即就把那个数组或者字典之类的释放。我不知道那些东西释放跟不释放效率会不会相差很多,但可以肯定的是,如果我在一个脚本里同一个数组或者字典的名字被重复用多次,而在使用之前我又没有重新定义,在上一个数组或者字典使用完毕之后,把它清除干净会让我安心很多。

一开始要实现某个功能的时候,我都是写完一大堆,然后发现另外一个功能可能就是在那一大堆里面改一点点,然后就可以实现了,我就会把那一大堆复制粘贴,然后改一点点,发现的确是可以的,但是当这个脚本真的要放出来的时候,我就得合并同类项,能不能把这两个东西通过以某个判断把它们合并在一起呢?如果那两段话的差别就只是某个条件判断不一样,我能不能把那个条件判断放在前面,然后把判断结果赋值给一个变量接着后面就可以在同一段话里面引用那个变量了。这样的话从脚本的体积来说可以缩短很多,起码可以减肥一半,甚至更多。

一直以来我都想不通,如果我要从一个工作簿里复制某个工作表到另外一个新的工作簿。那么我是应该把整个工作表复制过去呢,还是说在新的那个工作簿里面新建一个工作表,然后把原来那个的内容复制到那边。在生成这篇blog的时候,其实我依然没有做过这种尝试,到底哪一个的效率会高一点呢?我选择的是直接把工作表复制过去。当我全部复制完毕以后,因为每个工作簿新建的时候都会有一个默认的工作表,我最后的步骤就是把默认的那个工作表删掉。但是让我想不明白的是,如果我不是在VBA里操作,而是直接操作Excel,我在某个已经打开的工作簿的工作表的表名那里点右键之后选择在新的工作簿里面建立这个工作表的副本,那么新的工作簿里是不会有默认空白工作表的。这是不是证明,如果我在新建工作簿里面设定某些参数可以不让那个默认空白的工作表生成呢?因为如果这样的话,我就不需要在后面再做一个删除的操作了。从数据上说,删不删除都无所谓,因为那个是空白的,但是对完美主义者来说,总觉得有那么一个空表实在很碍眼。

当我终于实现了批量生成文件以后,我发现批量生成文件的速度很慢。慢到大概每个表都需要一秒钟,如果要批量生成9个表就得9秒钟甚至更多。在考虑如何缩短时间的时候,我马上想到我把那些批量生成的表全部都放在一个工作簿里,事情就变成了批量生成一个工作簿里面的N个工作表,而不是生成N个工作簿。接着我发现这样的确能提高一点速度,如果要生成9个表的话,时间会从大概9秒钟下降到6秒钟,但是我觉得还是不行。于是我就开始研究我这个生成的过程到底有没有什么地方可以改进。因为之前我的策略是生成9个工作簿,所以我肯定是从原始的文件里先把自带格式的工作表复制到新的工作簿,然后再把新的数据粘贴到新的工作簿,然后保存。同样的思路,用在一个工作簿的N个工作表里,效率很低。所以接下来我做的就是新建一个工作簿,然后把带有格式的工作表复制过去。接着在新的工作簿里面复制里面带有格式的工作表,改名,接着在新的工作表里赋值。这样最大的区别就在于在多次复制工作表的时候,我不再需要跨工作簿了。这么一个不跨文件的操作,直接让批量生成的时间从6秒下降到2秒,效果非常明显。我觉得两秒也几乎基本上是个极限了,因为即便我用最简单的循环,基本上不在工作表里面录入什么数据也需要接近2秒的时间,现在我粘贴了那么多数据,还有就是生成这些数据之前还进行了N步操作,整个过程下来不到三秒,我觉得完全可以接受。

追求更快更好是从来没有尽头的。

2023-08
5

用VBA秒变身

By xrspook @ 11:07:04 归类于: 烂日记

说到就做到的,感觉非常的美妙。

前天说到我应该可以在VBA上更进一步,把数据整理以及最基本的汇总通过VBA一步搞定,结果我昨天真这么做到了。前天也说到VBA的copy让我非常的烦恼,但在我印象之中,以前我的烦恼并没有这么明显,之所以这样是因为以前粘贴时我不是直接粘贴单元格的内容,而是先把单元格的内容赋值给数组,然后处理数组,然后再贴回单元格。copy看上去很简单,但是在单元格的层面直接赋值好像无论如何都没有任何效果。把所选区域赋值给数组,相对好理解一些,但是要把数组再贴回单元格,相对于python的解决方案来说,VBA就很不人道了。因为我习惯了用python的方风格,所以回到Excel,之所以贴来贴去贴半天都得不到结果,就是因为我在数组复制内容到单元格的时候并没有设定单元格区域。不设定那个区域,永远都是贴了个寂寞。除非你使用Excel自己的那个方案,那个特殊粘贴的方案。我觉得这挺奇怪,既然我用特殊粘贴的方案可以不选定区域,为什么我直接粘贴数组就必须得这样呢?情况就像数组一开始就得设定好,得多大。当然有些数组是不需要事先设定长度,比如如果那个数组传入的数据是一个被打断的字符串。我不知道为什么被打断的字符串就不需要这么干。VBA数组一旦设定了大小,很难改变的。长度如果一开始长了,但最终你没用那么多,我也知道了最终的长度是多少,今天我的解决方法是先把这个二维的数组转置,在保留原数据的基础上限定最终的长度,然后我再把数组转置回来。之所以这么绕,是因为VBA只允许修改最后一个维度的长度。如果你是一个二维的数组。第1个维度是行,第2个维度是列,所以我的方式是行列转置,再转回来,但如果数组是三维的或者多维的,中间的某些要改,显然就不能这样了,但如果你执意要这么干的话,估计可以再建一个数组,设定好目标长度,通过套叠循环的方式把前一个数组的数据赋值到下一个数组里。但是为什么非得这样干呢?如果要限定数据输出的话,你只要限定数据输出的单元格是你最终的范围就可以了。但实际上,像我这种得到了这个数组之后,还得加工,还得在这个基础上汇总,我的数组就不能有空行。比如这一次我要在得出的数组的基础上再进行一个多条件的单汇总,如果我的数组里面有空行,最终得到的字典将出现一个键的内容为空,键值为0的玩意。我也不想这么折腾但是不这么折腾,真的没法玩,当然了,在输出字典的时候依然可以先做一个判断,让键是空的内容不输出。

字典这个概念,我首先是在学习python的时候了解到的。在学习python字典的时候,我就曾经用字典做过字母或者单词的数量计算。现在,如果我要进行前面我说到的多条件单汇总,我就需要用到字典。

至于在VBA里怎么用字典法实现我的目标,下回分解。

2019-05
1

继续折腾吧

By xrspook @ 11:27:21 归类于: 烂日记

软件设计出来唯有在使用的时候你才能体现出它的价值。昨天和今天我把自己写的两个VBA用了好多遍,感觉真的是爽歪了,但有些东西只有当你用的时候你才会明白到某些手续的必要性。

比如我写的第1个VBA,那是用来套打的,可以打一个工作表里面的指定行。一开始的时候我的思路是把要打的表都复制到有保存VBA的工作簿里,当时打印结果出来的时候我傻眼了。因为页边距的问题,所以打歪了。打了第1页以后,我就已经意识到这个问题,于是我修改了有VBA代码的工作簿,但即便这样,从其它地方复制过去的工作簿还是不以我理想的方式出现。所以最终我把需要打印的工作簿复制到一个别的地方,文件重命名,然后同时打开那个工作簿以及含有VBA代码的另外一个工作簿。在需要打印的工作簿下快捷键调用另外一个工作簿里的VBA代码。这样的好处是我不需要把VBA代码到处复制,只要我同时打开两个工作簿,我就能实现这个功能。一开始我是不知道的,虽然之前我已经看到过网上有人说可以这么做,这样做的好处就是不需要到处复制VBA代码,因为在Excel里面,2007以上版本里最普通的xlsx是无法保存宏代码的,如果要保存宏代码就必须使用xlsm,当然可能如果你把它降级为2003的版本就可以在里面保存代码。显然最普通的工作簿里面不允许你默认打开或者保存使用宏代码能提高了文件的安全性。自己用的宏代码放在一个自己的文件里,需要在别的文件调用的时候同时打开工作簿,这其实也很方便。自己的工作簿设置的宏快捷键在其它工作簿也一样可以用这很爽。这些东西在我写代码的时候我没有想考虑过。因为我只是个新手,不可能一开始就那么周全。

我写的第2个VBA是用来汇总数据的。理论上我还需要添加一个筛选的功能。这样的好处是每次我不需要把每个工作表里面的所有内容都复制到大表里,而是先进行筛选,然后再选择性地复制过去。这可以减小文件的体积以及代码运行的速度,但另一方面,其实筛选也是需要时间的,是全部东西拷贝快一点,还是先筛选再拷贝快一点呢?对我来说,因为我的数据不算太多,所以目标以外的东西我完全可以视而不见,但是如果我把这条代码给别人用的话,或许先进行筛选然后再复制会更合理一些。设定筛选会让我落入另外一些烦恼,筛选范围是个问题。最简单的当然是某个指令数字的筛选,靠谱一点的是某个范围的筛选,但某个范围又分为了连续以及间断。显然要全部满足这些,从逻辑上我还没想通。在非大牌软件里同时满足这几种东西的貌似我还没见过,所以我没想通可能是因为要实现这些有难度,但并非不可能。比如在设定打印页面的时候,我就可以用单数字,连续数字或多数字实现我的要求,所以这个功能是可以做到的,只是我还没想到应该如何实施。

折腾是一个没完没了的过程,但我会乐在其中。

© 2004 - 2024 我的天 | Theme by xrspook | Power by WordPress