VBA编程技巧 之 字典对象使用经验谈

2025-06-22

VBA编程技巧 之 字典对象使用经验谈(更新至四之一) 字典对象使用经验谈 目录

前言_______________________________________01 楼 一、字典的基本功能___________________________01 楼 二、The Hardcore of Dictionary__________________02 楼 二之一、多层字典对象应用案例分析 1 _____________16 楼 二之二、多层字典对象应用案例分析 2 _____________30 楼 三之一、动态的树形数据结构的构建________________35 楼 三之二、动态树形结构的遍历____________________54 楼 三之三、送你把漂亮的解牛小刀___________________64 楼 三之四、上帝的归上帝,凯撒的归凯撒______________66 楼 四之一、利用字典动态的构建自定义数据类型___________68 楼 前言

相信大家对字典对象已经是耳熟能详了。现在帮人写个代码如果不来个字典,出门都不好意思和人打招呼。EH里也有大量的帖子详尽的介绍了字典对象的功能和各种使用方法,我大致翻过这些帖子,感觉很有必要和大家交流一下最近一段时间泡坛子、帮人家写代码得到的一些心得体会。可能更多的会聊一些编程思路的东西,所以我想这篇文章应该是给有一定基础的朋友看的,起码应该能不需要注释就能看懂代码,起码应该看过置顶贴里提到的那些帖子。 一、字典的基本功能

相信字典对象最为出名的是它的关键字不重复特性,我们经常会看到这样的语句: For i=0 to UBound(arr) dic(arr(i,1))=\Next

这段语句唯一的作用就是将数组的第一列数据去掉了重复项。但值得强调的是既然我们叫它字典对象,那么它就理所应当的具有翻译功能。

以一个典型的EXCEL数据表为例,很多情况下会是类似于一个数据库中 表 这样的一个结构,即具有第一行的表头部分定义了每一列的内容是什么,其下每一行都是一条单独的纪录。那么这种情况下,我们完全可以用字典对象来创建由表头来翻译索引列号。这至少带来两个好处,1、使得你的代码更具有可看性,或则说更像自然语言;2、使得你的代码不会依赖于表格的地理位置,也就是说即便出于某种原因列的顺序有了变动,你也不需要去找出你的代码里涉及到相应列号并逐一改正。其实,更重要的一点,是你的代码会具有更大的适用性。

让我们来比较两段代码,设想我们需要读取一个月工资表并统计各班组的绩效奖金,其包含 姓名、班组、工位、基本工资、绩效奖金等等信息,那么可能的代码会是这样的: 复制内容到剪贴板 代码:

Dim dic, arr, i&, lRow&

lRow = Sheet1.[a65536].End(xlUp).Row arr = Sheet1.Range(\

Set dic = CreateObject(\For i = 1 To UBound(arr)

dic(arr(i, 2)) = dic(arr(i, 2)) + dic(arr(i, 5)) Next

'================= Dim dTitle, arr, i&, dic arr = Sheet1.[a1].CurrentRegion

Set dTitle = CreateObject(\For i = 1 To UBound(arr, 2) dTitle(arr(1, i)) = i Next

1

Set dic = CreateObject(\For i = 2 To UBound(arr)

dic(arr(i, dTitle(\班组\班组\绩效奖金\Next

第二段代码我们使用了一个名为dTitle的字典对象来记录表头名称和对应列号,这样当我们需要使用某列数据的时候,我们可以使用这个对象来将表头名翻译成列号。很明显的是第一段代码完全依赖于表格内容的地理位置,而且如果不去看数据表的话,你根本不知道它在干什么。而相应的,对于第二段代码而言,我们完全可以不用去了解数据表是什么样的,只需要知道它有这样的两个表头就可以了。并且你不觉得它很接近自然语言了吗?哦,不吗,你确定?那我再稍微改一下: 复制内容到剪贴板 代码:

数据 = Sheet1.[a1].CurrentRegion

Set 表头之列号 = CreateObject(\For i = 1 To UBound(数据, 2) 表头之列号(数据(1, i)) = i Next

Set 班组绩效奖金 = CreateObject(\For i = 2 To UBound(数据)

班组名 = 数据(i, 表头之列号(\班组\

成员绩效奖金 = 数据(i, 表头之列号(\绩效奖金\

班组绩效奖金(班组名) = 班组绩效奖金(班组名) + 成员绩效奖金 Next

如果出于某些原因,原来的那个工资表在绩效奖金之前增加了一列,比如说老板大发善心为大家增发了住房津贴,显然作为劳资统计的你不会希望把它给漏了。那么这时,如果你的代码是前面第一种方法,那么你必须仔细检查你的代码,确保每一个数字对应的列是你需要的内容。但是如果你非常幸运的看过了这篇文章,并且使用了第二种方法,恭喜你,你不用像前者那样心惊胆颤的一个个数列数了,开开心心的在一边数钱吧!

可能有看官说了:嘿,我们老板才烦呢,他不会加发工资的,他会把那个绩效奖金的名字改成工作表现奖!你瞧,这下你要去改代码了吧。那么这里我想说的是,养成良好的编程习惯,使用常量设置。如果你经常写代码的话,你肯定会碰到前面这位看官提到的情况,那么你就会知道使用常量设置是多么方便的事情。千万不要为了少敲键盘而省略这个过程,我们要牢记我军的优良训练传统:训练多流汗,战时少流血!编写多常量,更改不挠头!

想想还是把代码写出来看看效果吧: 复制内容到剪贴板 代码:

Public Const PR_SALARY_GROUP = \班组\Public Const PR_SALARY_BONUS = \绩效奖金\....

Dim dTitle, arr, i&, dic arr = Sheet1.[a1].CurrentRegion

Set dTitle = CreateObject(\For i = 1 To UBound(arr, 2) dTitle(arr(1, i)) = i Next

Set dic = CreateObject(\For i = 2 To UBound(arr)

dic(arr(i, dTitle(PR_SALARY_GROUP))) = _

dic(arr(i, dTitle(PR_SALARY_GROUP))) + arr(i, dTitle(PR_SALARY_BONUS)) Next <未完待续>

二、The Hardcore of Dictionary

2

琢磨了半天,还真没想出什么中文词来表达Hardcore比较合适。(题外话,不建议去Google搜索这个关键字,但相信我这个词本身没有任何相关的含义,真的是个好词。)

我们知道字典对象由关键字 Key 和数据项 Item 构成。通常情况下 Key 是字符串,实际上也可以是其它数据类型,比如整数、小数等。而数据项则可以是任何数据类型,包括字典对象本身。这样我们就可以创建多层的字典对象了。利用多层字典对象,我们可以实现诸如级联菜单、联动数据有效性序列、联动下拉框等等应用,这也常见于坛子里各个帖子。这里我不想重复谈这些应用,而是想着重强调其背后隐藏的一个概念。

我们到底用字典作了什么?一言以蔽之,所谓的多层字典,实际上你利用它构造了一个树型数据结构!

坛子里也有很多帖子在介绍TreeView这个控件,它和我们的多层字典何其相似。让我们还是以上面那个工资表来作为例子,我们可能希望把它处理成这样的一个形式: 复制内容到剪贴板 代码:

Public Const PR_SALARY_GROUP = \班组\Public Const PR_SALARY_POSITION = \工位\Public Const PR_SALARY_NAME = \姓名\Public Const PR_SALARY_BASE = \基本工资\Public Const PR_SALARY_BONUS = \绩效奖金\

Public Function ParseData() Dim dTitle, arr, i&, dic, dTemp arr = Sheet1.[a1].CurrentRegion

Set dTitle = CreateObject(\ For i = 1 To UBound(arr, 2) dTitle(arr(1, i)) = i Next

Set dic = CreateObject(\ For i = 2 To UBound(arr)

If Not dic.Exists(arr(i, dTitle(PR_SALARY_GROUP))) Then _

Set dic(arr(i, dTitle(PR_SALARY_GROUP))) = CreateObject(\ Set dTemp = dic(arr(i, dTitle(PR_SALARY_GROUP)))

If Not dTemp.Exists(arr(i, dTitle(PR_SALARY_POSITION))) Then _

Set dTemp(arr(i, dTitle(PR_SALARY_POSITION))) = CreateObject(\ Set dTemp = dTemp(arr(i, dTitle(PR_SALARY_POSITION))) If Not dTemp.Exists(arr(i, dTitle(PR_SALARY_NAME))) Then _

Set dTemp(arr(i, dTitle(PR_SALARY_NAME))) = CreateObject(\ Set dTemp = dTemp(arr(i, dTitle(PR_SALARY_NAME))) dTemp(PR_SALARY_BASE) = arr(i, dTitle(PR_SALARY_BASE)) dTemp(PR_SALARY_BONUS) = arr(i, dTitle(PR_SALARY_BONUS)) Next

Set ParseData = dic Set dTitle = Nothing End Function

如果我们使用类似这样的语句 Set dicSalary = ParseData() 调用上面这个程序,那么我们可能得到的一个数据结构,会是如下这样子的:

复制内容到剪贴板 代码: dicSalary ├─甲班 │ ├─拼装

3

│ │ ├─张三

│ │ │ ├─基本工资 -> $1000 │ │ │ │

│ │ │ └─绩效奖金 -> $800 │ │ │ │ │ └─李四

│ │ ├─基本工资 -> $1000 │ │ │

│ │ └─绩效奖金 -> $800 │ │ │ └─焊接 │ ├─王二麻子

│ │ ├─基本工资 -> $1100 │ │ │

│ │ └─绩效奖金 -> $900 │ │ │ └─赵大

│ ├─基本工资 -> $1100 │ │

│ └─绩效奖金 -> $900 │ └─乙班 ├─拼装 │ ├─诸葛

│ │ ├─基本工资 -> $1000 │ │ │

│ │ └─绩效奖金 -> $800 │ │ │ └─南宫

│ ├─基本工资 -> $1000 │ │

│ └─绩效奖金 -> $800 │ └─焊接 ├─西门

│ ├─基本工资 -> $1100 │ │

│ └─绩效奖金 -> $900 │ └─轩辕

├─基本工资 -> $1100 │

└─绩效奖金 -> $900

那么,对于这样一个数据结构,我们调用张三的基本工资就会是这样子的: 张三的基本工资 = dicSalary(\甲班\拼装\张三\基本工资\

当然,我们也可以使用自定义类型来实现这一目的,代码可能会是象下面这个样子: 复制内容到剪贴板 代码:

4

Public Type Salary Name As String Amount As Single End Type

Public Type Person Name As String Salaries() As Salary End Type

Public Type Position Name As String Persons() As Person End Type Public Type Group Name As String Positions() As Position End Type

这里,我不想再去写赋值代码,因为那实在是一个非常繁琐的过程。不过我们可以想象一下这个赋值的过程,我们需要重新定义每层的数组元素数量,可能还需要通过循环来定位是数组的第几个元素。而反过来当我们需要调用某个值得时候,也同样的啰嗦。这时我们可以非常明显的看到使用字典对象的方便了,因为字典对象让我们可以用关键字来进行索引,而不需要对整个元素集合进行顺序遍历来查找定位。

提到集合,实际上我们还可以使用VBA原生的一个对象,就是集合对象(Collection),来实现这一目的。但这里存在一个问题,集合对象没有 Exists 方法,也就是说你无法知晓某个关键字是否存在,只能通过 On Error Resume Next,引用此关键字,再去判断 Err.Number > 0 来得到答案,同时还要再清除这个错误,会麻烦不少。不过这里不得不提一下集合对象的一个优势,那就是在它的Add方法支持 After/Before 参数,使得在初始赋值时,非常适合同时进行排序工作,如果你需要对你的树结构进行排序的话,建议你考虑用Collection对象。

看到这,相信你已经完全了解了字典对象在构造树形结构方面的优势。有必要在这里解释一下,为什么这种数据结构非常重要。通过上面的树形图,想必很容易理解这种结构清晰的反映了数据间的归属关系或是上下级关系。而在现实生活中,我们几乎可以用这种结构来描述各种事物,公司的人员结构、文档的归类整理、你家的门牌号,等等等等。这也是为什么我们在EH的VBA版看到大量的字典对象应用的根本原因,因为它太适合用来处理最常见的各种数据了。 接下来,我会结合具体的案例,来聊聊字典是如何处理树形结构数据的。 <未完待续>

二之一、多层字典对象应用案例分析 1

这里我们以一个跟据已有标签内容数据、创建格式化标签供打印用的实际案例,来聊聊使用多层字典对象构造树形数据结构如何解决问题的。

这个例子的帖子地址:http://club.excelhome.net/viewthread.php?tid=720876 为避免由于楼主编辑帖子,我把他的原始附件和最后我帮他完成的附件都贴在这里。有兴趣的同学也可以去这个贴子看看,我在11楼提到的楼主代码的问题其实就是一些经验之谈。看看楼主原来的附件里的代码,你会发现他是在用Select Case语句完成字典的翻译功能,这样的做法违背了一个编程的基本原则,混淆了代码和数据的区别。正如我在该贴11楼提到的,他的SN(Serial Number)和Mask(某种规格)、Tray Type(托盘型号)之间存在着多对一的关系,即SN确定则后两者都是确定的。这种情况下,这样关系太适合用字典来描述了,同时这种关系本身是数据的,那么理应让它成为数据,所以我才要求楼主准备这样一个对照表。可能有朋友会发现我比较啰嗦,但实际情况说明作为一个在EH的VBA版帮人写代码的家伙,适当的和楼主交流是多么的必要。这就和你作为一个销售人员,在商业谈判中穿插着聊一些有趣的话题同时进一步了解客户需求的道理是一样的,即增进了感情又能掌握更多的信息,避免走弯路,呵呵。 还是回到这个小节的主题上来,我们先来分析一下这个例子里的数据是什么样的一个结构。 1、标签的数据有了,而且是按通常的数据库结构建立的,即表头、记录 2、标签的结构有两种,是按托盘型号区分的

3、标签是按包打印的,即每包一个标签,而同一包的芯片对应的Mask和Chip这两个值都是一样的 4、每包内又有数个托盘,每个托盘有数个芯片即序列号

看到这里,相信你也同样的发现这就是一个典型的树形结构,它的数据归属关系应该是这样子的:托盘型号 -> 包号 -> 托盘

5


VBA编程技巧 之 字典对象使用经验谈.doc 将本文的Word文档下载到电脑 下载失败或者文档不完整,请联系客服人员解决!

下一篇:高二(理科)数学第一学期期中试卷

相关阅读
本类排行
× 游客快捷下载通道(下载后可以自由复制和排版)

下载本文档需要支付 7

支付方式:

开通VIP包月会员 特价:29元/月

注:下载文档有可能“只有目录或者内容不全”等情况,请下载之前注意辨别,如果您已付费且无法下载或内容有问题,请联系我们协助你处理。
微信:xuecool-com QQ:370150219