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

2025-06-22

号 -> 芯片。为此,我写了这样的一个过程来构造这个标签数据结构,如下: 复制内容到剪贴板 代码:

Private Function ParseData()

Dim aData, lRow&, i&, dic, dTemp

lRow = Cells(65536, MPL_COL_SN).End(xlUp).Row

aData = Cells(MPL_START_ROW, 1).Resize(lRow - MPL_START_ROW + 1, 7)

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

If Not dic.exists(aData(i, MPL_COL_TYPE)) Then _

Set dic(aData(i, MPL_COL_TYPE)) = CreateObject(\ Set dTemp = dic(aData(i, MPL_COL_TYPE))

If Not dTemp.exists(aData(i, MPL_COL_PACK)) Then _

Set dTemp(aData(i, MPL_COL_PACK)) = CreateObject(\ Set dTemp = dTemp(aData(i, MPL_COL_PACK)) dTemp(\ dTemp(\ If Not dTemp.exists(aData(i, MPL_COL_TRAY)) Then _

Set dTemp(aData(i, MPL_COL_TRAY)) = CreateObject(\ Set dTemp = dTemp(aData(i, MPL_COL_TRAY)) dTemp(aData(i, MPL_COL_SN)) = True Next

Set ParseData = dic End Function

为了更加直观的表达这段代码,让我们来给它画个树形图 复制内容到剪贴板 代码: dic (变量名) │

└─托盘型号 │ └─包号 │

├─CHIP -> CHIP 值 │

├─MASK -> MASK 值 │ └─托盘号 │

└─序列号

话说我们在处理EXCEL数据时,绝大多数的情况都可以分为三个步骤,即 读取整理数据、计算构造输出数据、输出结果,前者和后者会和EXCEL工作表交互,而中间的那个步骤则是在内存中完成的。我们知道数据结构决定了算法,也就是说第一步的读取整理数据决定了中间的计算和后面的输出的代码难易度。上面这段代码的作用就是把原始的数据记录构造成了上图示意的一个树形数据结构,这也是为什么我把这段程序命名为ParseData而不是ReadData的原因。说起来这个问题,如果你碰到要编程序处理数据了,一时想不好该干些什么,我告诉你可以先写象这样的几行代码,肯定没错的: 复制内容到剪贴板

6

代码:

Public Sub 我要炒股票挣钱()

Call 读取分析股票数据 ' ParseData Call 计算哪个股票挣钱 ' CalData Call 告诉我是哪个股票 ' OutputResult End Sub

是不是看上去很简单啊,事实上就是这么简单的。我们接着来看看此例中,CreateLabels是怎么工作的,它的代码如下: 复制内容到剪贴板 代码:

Public Sub CreateLabels()

Dim dic, aTrayTypes, i%, j%, dTemp, aPacks, dtPack As Date dtPack = Range(MPL_NAME_DATE) Set dic = ParseData aTrayTypes = dic.keys

For i = 0 To UBound(aTrayTypes) ClearLabel aTrayTypes(i) Set dTemp = dic(aTrayTypes(i))

CopyBlankLabel aTrayTypes(i), dTemp.Count aPacks = dTemp.keys If aTrayTypes(i) = 1 Then For j = 0 To UBound(aPacks)

FillLabelOne j + 1, aPacks(j), dTemp(aPacks(j)), dtPack Next

ElseIf aTrayTypes(i) = 2 Then For j = 0 To UBound(aPacks)

FillLabelTwo j + 1, aPacks(j), dTemp(aPacks(j)), dtPack Next End If Next

Set dic = Nothing End Sub

你瞧,整个过程如果用语言来描述的话,就像这样:1、读取分析数据;2、清空标签模版内容;3、按需要复制空白标签模版;4、逐个填写标签内容。如果你有一定的英文基础的话,上面这段代码你甚至不需要任何注释就能轻易看明白,难道不是吗?这里我想说一下另外一个良好的编程习惯:遵循一定的变量命名规则。每个人都会有自己的命名习惯,但我想说的是让这种习惯遵循一定的规则,并尽可能的让它表达自身的含义。我注意到坛子里很多高手都喜欢用单字符、双字符命名变量,他们自己完全搞得清楚,那是因为他们是高手而且是自己写的不长的代码。但我真的很想告诉你,那其实是一个非常非常坏的习惯,甚至都不需要长时间以后再去理解代码,仅仅是需要写一个长点的代码,就会让你感觉云山雾罩了,而不得不写很多的注释来告诉自己这段是干嘛的那段是干嘛的。

另外一个技巧就是把你的代码中相对独立的片断,拎出来单独写个过程或是函数,其好处不仅仅是避免重复的代码,更大的好处是让你可以更为专注的解决一个相对独立的功能而不用去考虑全局的情况,给这个过程起一个恰当的名字,同样会让你的代码更容易看懂,让你的代码会说话。有兴趣的朋友可下载此节开头提到的帖子里4楼的附件,那是一开始我为楼主写的代码,因为最初楼主只是提到了一种标签,估计他的想法是学习一下实现方法然后自己作的,呵呵。

还是回到主题,我们已经构造了合适的数据结构,已经准备好了空白的标签,接下来要做的无非就是把相应的数据填进去就行了。以第一种标签为例,填写一个空白标签的代码如下: 复制内容到剪贴板 代码:

Private Sub FillLabelOne(iLabelNo, iPackNo, dicPack, dtPack As Date) Dim iRow%, iCol%, i%, j%, aTrays, aHead, aCont, iChipNum%, dTray, aSNs

7

ReDim aHead(1 To MPL_LB_ONE_HEAD_ROW, 1 To MPL_LB_ONE_COL) ReDim aCont(1 To MPL_LB_ONE_CONT_ROW, 1 To MPL_LB_ONE_CONT_COL) aTrays = dicPack.keys iChipNum = 0 For i = 1 To 5 aCont(i + 1, 1) = i Next

If UBound(aTrays) > 5 Then For i = 1 To 5 aCont(i + 7, 1) = i Next End If

For i = 2 To UBound(aTrays) Set dTray = dicPack(aTrays(i))

aCont(((aTrays(i) - 1) \\ 3) * 6 + 1, ((aTrays(i) - 1) Mod 3) * 5 + 2) = aTrays(i) aSNs = dTray.keys For j = 0 To UBound(aSNs)

aCont(((aTrays(i) - 1) \\ 3) * 6 + j + 2, ((aTrays(i) - 1) Mod 3) * 5 + 2) = aSNs(j) iChipNum = iChipNum + 1 Next Next

aHead(1, 1) = iPackNo aHead(2, 1) = dicPack(\ aHead(2, 10) = dicPack(\ aHead(3, 1) = iChipNum aHead(4, 1) = dtPack

With Sheets(MPL_SHT_NM_LABEL_ONE)

iRow = ((iLabelNo - 1) \\ 2) * (MPL_LB_ONE_ROW + 1) + 1 iCol = ((iLabelNo - 1) Mod 2) * (MPL_LB_ONE_COL + 1) + 1

.Cells(iRow, iCol).Resize(MPL_LB_ONE_HEAD_ROW, MPL_LB_ONE_COL) = aHead iRow = iRow + MPL_LB_ONE_HEAD_ROW

.Cells(iRow, iCol).Resize(MPL_LB_ONE_CONT_ROW, MPL_LB_ONE_CONT_COL) = aCont End With End Sub

这段代码的作用就是填写一个空白标签,结合前面说到的专注的问题,如果我们把这段代码放回到CreateLabels里并不是做不到,但你会发现那样一来,你的代码就会变得很啰嗦。你需要定位填写的标签,并且需要把这些定位反映到代码里。而如果分离出来,通过参数把需要的信息传递过来,那么你仅仅需要考虑填写一个标签的问题就可以了。

这段代码是将标签分为两部分填写的,头部和下方的详细内容。实际上,几乎任何一个描述类似事情的表格都可以这样来区分,诸如 报关单、工资表、发货清单、加工清单等等,你都可以采用同样的办法,使用字典对象建立一个树形结构,整理成你希望的形式并输出。这里使用的是一个Variant型的数组输出到工作表的,因为标签模版的形式是希望没有数据的地方留为空白,而Variant型数组正好可以实现这一要求,对于没有赋值的数组元素在输出到工作表时,对应的单元格是不填任何东西的。具体到内部的计算,无非就是按照模版的结构,对相应的数组元素赋值的过程,这里就不详细讲了。

此节附件里,还有两个字典对象的应用,一个是根据输入的SN信息,结合包装的特性,生成分包清单;另一个是在输入SN过程中,利用一个全局变量字典对象,检查是否已经存在该SN,以避免重复。有兴趣的朋友可以参考一下。 <未完待续>

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

接下来这个例子是一家保险公司的数据,希望从已有的数据中根据不同的客户类别、地理位置和是否在销售网络中的属性这三个不同层级,来筛选数据,动态的将筛选结果显示在一个工作表内。帖子的地址是:

8

http://club.excelhome.net/viewthread.php?tid=720328 。这个帖子偏长,除了因为楼主后来又增加了要求以外,更大的原因是由于我写代码不仔细,错误的将一个循环变量 i 写成了常数 0 。而这个错误又是非常的不明显,以至于浪费了好几个楼层来讨论如何获得必要的调试信息。我们在这里先看看一个中间品,稍后我们再把最终的成品分析一下,作为对比。可能的话,我还会针对这个案例,再改进一下。另外,有兴趣的朋友也可以看看该贴中,我的头一个附件,在3楼,前后比较一下代码是如何根据需要的不同而演化的。这个中间品的楼层在该贴的第2页的13楼,其后应楼主的要求,我还指导过如何自行调整代码,此帖附件是调整后的代码。

与前一个案例一样,我们还是先结合需要分析一下现有的数据。如前所述,我们需要根据需要动态筛选数据,这一过程其实也可以手动完成的,筛选条件如下:1、客户类别 LOB(没猜错的话是 Label Of Business);2、地理位置,即州别,Phy State(没猜错的话 是 Physician State);3、是否在网络中,In Network(估计指的是销售网络),对于筛选出来的记录,我们需要对其中一列数据(Allowed Costs/Treated Patients,猜测是指每个治愈患者的允许费用)计算一些诸如最大最小值、平均值、方差等,然后需要进一步根据计算结果,将该列数据中大于某种平均值的记录筛选出来,并进一步计算平均值和列出筛选结果。楼主在需要显示结果的表Summary内,已经规划好了显示位置,以及用户交互方式,及使用单元格数据有效性提供的下拉框,形成菜单式选项。

很明显的第一步筛选过程,其实也是一个树形结构,也就是说完全可以使用多层字典对象来实现,有了前面的基础,这里我就不画树形图了,层级关系就上面的1、2、3的顺序。那么ParseData过程的代码就是下面这个样子的: 复制内容到剪贴板 代码:

Private Sub ParseData()

Dim i&, lRowMax&, lColMax&, dTemp, aTitle

lRowMax = Sheets(PR_DATA_SHT_NM).[a1].End(xlDown).Row lColMax = Sheets(PR_DATA_SHT_NM).[a1].End(xlToRight).Column aTitle = Sheets(PR_DATA_SHT_NM).[a1].Resize(1, lColMax)

aData = Sheets(PR_DATA_SHT_NM).[a2].Resize(lRowMax - 1, lColMax)

Set dicData = CreateObject(\ Set dicTitle = CreateObject(\

For i = 1 To UBound(aTitle, 2) dicTitle(aTitle(1, i)) = i Next

Set dicData(PR_LOB_ALL) = CreateObject(\

For i = 1 To UBound(aData, 1)

If Not dicData.exists(aData(i, dicTitle(PR_TITLE_LOB))) Then _

Set dicData(aData(i, dicTitle(PR_TITLE_LOB))) = CreateObject(\ Set dTemp = dicData(aData(i, dicTitle(PR_TITLE_LOB)))

If Not dTemp.exists(aData(i, dicTitle(PR_TITLE_STATE))) Then _

Set dTemp(aData(i, dicTitle(PR_TITLE_STATE))) = CreateObject(\ Set dTemp = dTemp(aData(i, dicTitle(PR_TITLE_STATE)))

dTemp(aData(i, dicTitle(PR_TITLE_NETWORK))) = dTemp(aData(i, dicTitle(PR_TITLE_NETWORK))) & i & \

Set dTemp = dicData(PR_LOB_ALL)

If Not dTemp.exists(aData(i, dicTitle(PR_TITLE_STATE))) Then _

Set dTemp(aData(i, dicTitle(PR_TITLE_STATE))) = CreateObject(\ Set dTemp = dTemp(aData(i, dicTitle(PR_TITLE_STATE)))

dTemp(aData(i, dicTitle(PR_TITLE_NETWORK))) = dTemp(aData(i, dicTitle(PR_TITLE_NETWORK))) & i & \

9

Next End Sub

有了前文介绍的使用表头索引行号的概念、常量的概念、构造树形数据结构的概念,相信读懂这段代码应该不难。这里有三点需要说明一下,1、楼主对于LOB项有个要求,除了表内现有内容外,增加了一个All选项用来显示全部客户类别的信息,所以我们在代码内,定义了一个PR_LOB_ALL这个常量索引,并使它和其它LOB项内容同级;2、由于还要对筛选结果进行二次筛选,而且还要列出源数据表中其它列的内容,所以这里在树形结构的末端,我们采用了字符串的方式来索引每个分支对应的行号,行号之间是由空格分开的,由于最后会多出一个空格,所以在后面引用它的时候,要使用Trim函数把它去掉,然后用Join函数把这个字符串变成数组;3、由于要动态的显示筛选结果,所以我将 标题字典(dicTitle)、树形结构字典(dicData)、数据数组(aData)这三者都设置成了全局变量,也就是说在内存里制作了一个源数据表的副本,并且根据我们的需要,使用字典对象对它进行了索引。这其实是一个典型利用内存空间换取执行速度的方法,要知道对于内存中驻留的数据进行计算的操作要比从任何形式的其它位置读一次数据要快的多,也就事实上使得人们在交互时产生结果实时动态显示的感觉。

好了,我们已经构造好了我们的树形结构,并且对数据数组进行了索引,那么接下来要做的工作就是根据筛选条件,进行筛选和计算。如果大家有去看那个帖子的话,会发现我提出让代码可以在每一次选择菜单的时候,都会动态显示结果,而不是非要选择到末端菜单才进行计算。这意味着在你选取上级菜单时,其下级菜单留空,那么显示的结果则是按当前层级的菜单进行筛选,而结果则包括其后级菜单的全部内容。换而言之,按我们之前构造的那个树形数据结构,意味着我们需要在任何一层的节点起步,遍历其下所有的节点直至末端数据。当时,由于考虑到树形结构已经固定为三层,并不多,所以我为每一层的遍历都单独写了代码,让我们来看看附件中CalAndFill这个过程的前半部,如何进行筛选的。 复制内容到剪贴板 代码: '...

lCol = dicTitle(PR_TITLE_ACPERTP)

ReDim aACPerTP(1 To UBound(aData)), aFilteredRows(1 To UBound(aData)) iCount = 0 If sState = \

aStateKeys = dicData(sLob).keys For i = 0 To dicData(sLob).Count - 1 Set dTemp = dicData(sLob)(aStateKeys(i)) aNetworkKeys = dTemp.keys For j = 0 To dTemp.Count - 1

aRows = Split(Trim(dTemp(aNetworkKeys(j)))) For k = 0 To UBound(aRows) iCount = iCount + 1

aACPerTP(iCount) = aData(Val(aRows(k)), lCol) aFilteredRows(iCount) = Val(aRows(k)) Next k Next j Next i

ElseIf sNetwork = \

Set dTemp = dicData(sLob)(sState) aNetworkKeys = dTemp.keys For i = 0 To dTemp.Count - 1

aRows = Split(Trim(dTemp(aNetworkKeys(i)))) For j = 0 To UBound(aRows) iCount = iCount + 1

aACPerTP(iCount) = aData(Val(aRows(j)), lCol) aFilteredRows(iCount) = Val(aRows(j)) Next

10


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

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

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

下载本文档需要支付 7

支付方式:

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

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