VBA实战技巧26:使用递归确定所有的引用单元格

excelperfect

在Excel中,经常存在一个单元格引用另一个单元格中,而另一个单元格又引用其他单元格的情形。如何使用VBA代码编程确定指定单元格的所有引用单元格呢?

引用单元格是由公式引用并在 Excel 的计算树中识别的单元格。例如,如果在单元格A1中有公式=B2,那么单元格B2是单元格A1的引用单元格;如果在单元格B2中也有公式=C3,那么单元格B2(第一级)和单元格C3(第二级)都是单元格A1的引用单元格。

可以单击功能区“公式”选项卡“公式审核”组中的“追踪引用单元格”来追踪引用的单元格,如下图1所示。

图1

根据VBA帮助文件,Range.Precedents属性返回一个Range对象,代表所有引用的单元格。因此,编写下面的代码:

Sub test() Dim rngToCheck As Range Dim rngPrecedents As Range Dim rngPrecedent As Range Set rngToCheck = Range('A1') On Error Resume Next Set rngPrecedents = rngToCheck.Precedents On Error GoTo 0 If rngPrecedents Is Nothing Then Debug.Print rngToCheck.Address(External:=True) & '没有引用单元格.' Else For Each rngPrecedent In rngPrecedents Debug.Print rngPrecedent.Address(External:=True) Next rngPrecedent End IfEnd Sub

针对图1所示的工作表,上面代码的输出结果如下图2所示。

图2

立即窗口中的输出告诉我们,Precedents属性适用于这个简单的示例,但是这个示例和帮助文件没有告诉我们的是它不会返回其他工作表或其他工作簿上的引用单元格。这个限制由Range.Precedents属性的定义所限制,因为该属性返回一个Range对象,而Range对象不能跨不同工作表引用单元格区域。

一种针对Range.Precedents属性不足的解决方案是使用Range.ShowPrecedents方法显示导航箭头,然后使用Range.NavigateArrow方法沿着每个箭头导航。

然而,还可以使用递归编程技术来解决。这也是展示递归技术的一个极好的示例。

代码如下:

Sub test2()    Dim rngToCheck As Range    Dim dicAllPrecedents As Object    Dim i As Long       Set rngToCheck = Sheet1.Range('A1')    Set dicAllPrecedents = GetAllPrecedents(rngToCheck)      Debug.Print '= = ='       If dicAllPrecedents.Count = 0 Then       Debug.Print rngToCheck.Address(External:=True); '没有引用单元格.'    Else        For i= LBound(dicAllPrecedents.keys) To UBound(dicAllPrecedents.keys)           Debug.Print '[ 层级:'; dicAllPrecedents.items()(i); ' ]';           Debug.Print '[ 地址:'; dicAllPrecedents.keys()(i); ' ]';           Debug.Print vbCrLf        Nexti    End If   Debug.Print '= = ='End Sub '不能遍历关闭的工作簿中的引用单元格'不能遍历受保护工作表中的引用单元格'不能识别隐藏工作表中的引用单元格Public Function GetAllPrecedents(ByRef rngToCheckAs Range) As Object    Const lngTOP_LEVEL As Long = 1    Dim dicAllPrecedents As Object    Dim strKey As String       Set dicAllPrecedents = CreateObject('Scripting.Dictionary')      Application.ScreenUpdating = False      GetPrecedents rngToCheck, dicAllPrecedents, lngTOP_LEVEL    Set GetAllPrecedents = dicAllPrecedents      Application.ScreenUpdating = TrueEnd Function Private Sub GetPrecedents(ByRef rngToCheck As Range, ByRef dicAllPrecedents As Object, ByVal lngLevel As Long)    Dim rngCell As Range    Dim rngFormulas As Range       If Not rngToCheck.Worksheet.ProtectContents Then        If rngToCheck.Cells.CountLarge > 1 Then           On Error Resume Next           Set rngFormulas = rngToCheck.SpecialCells(xlCellTypeFormulas)           On Error GoTo 0        Else           If rngToCheck.HasFormula Then Set rngFormulas = rngToCheck        End If               If Not rngFormulas Is Nothing Then           For Each rngCell In rngFormulas.Cells               GetCellPrecedents rngCell, dicAllPrecedents, lngLevel           Next rngCell           rngFormulas.Worksheet.ClearArrows        End If    End IfEnd Sub Private Sub GetCellPrecedents(ByRef rngCell As Range, ByRef dicAllPrecedents As Object, ByVal lngLevel As Long)    Dim lngArrow As Long    Dim lngLink As Long    Dim blnNewArrow As Boolean    Dim strPrecedentAddress As String    Dim rngPrecedentRange As Range       Do       lngArrow = lngArrow + 1       blnNewArrow = True       lngLink = 0               Do           lngLink = lngLink + 1           rngCell.ShowPrecedents           On Error Resume Next           Set rngPrecedentRange = rngCell.NavigateArrow(True, lngArrow, lngLink)           If Err.Number <> 0 Then               Exit Do           End If           On Error GoTo 0            strPrecedentAddress =rngPrecedentRange.Address(False, False, xlA1, True)           If strPrecedentAddress = rngCell.Address(False, False, xlA1, True) Then               Exit Do           Else               blnNewArrow = False               If Not dicAllPrecedents.exists(strPrecedentAddress) Then                   dicAllPrecedents.Add strPrecedentAddress, lngLevel                   GetPrecedents rngPrecedentRange, dicAllPrecedents, lngLevel + 1               End If           End If        Loop        If blnNewArrow Then Exit Do    LoopEnd Sub

GetAllPrecedents函数返回一个Dictionary对象,包含键中的单元格区域地址和项中的引用单元格层级。代码中最重要的概念是递归:GetPrecedents过程和GetCellPrecedents过程一遍又一遍地相互调用,直到它们遍历完引用单元格。对代码功能的一个简单增强是对它可以到达的层级数添加了限制:在递归技术中经常需要设置这样的限制。

注意,这段代码不会遍历关闭的工作簿或受保护的工作表追踪引用单元格,也不会在隐藏的工作表中找到引用单元格。

GetAllPrecedents函数可能会返回重叠的地址,例如B2:B10和B4,因为它使用联合单元格区域地址以提高效率。当代码沿引用单元格树导航时,如果它遇到之前导航过的单元格,将忽略它。同样,这是出于效率的目的。该函数不能作为自定义函数工作,因为当调用者是Range时,Range.ShowPrecedents和Range.NavigateArrows方法被禁用。

在代码中使用了Range.CountLarge,如果使用的是Excel2003或更早版本,则需要将其更改为Range.Count。

在Excel2010之前的版本中,Range.SpecialCells的返回值限制为8,192个不连续的单元格。你不可能打破此限制。

注:本文学习整理自colinlegg.wordpress.com,供有兴趣的朋友参考。

欢迎在下面留言,完善本文内容,让更多的人学到更完美的知识。

欢迎到知识星球:完美Excel社群,进行技术交流和提问,获取更多电子资料。

(0)

相关推荐

  • 【递归图片的图片】-神奇代码系列 02

    递归图片的图片 140之内的字符能够编写出什么程序来? 在 Wolfram One-Liner 大赛获奖的 Zdenek Buk 给我们展示了一个有趣的例子 - 由递归图片组成的图片. 仔细查看下面图 ...

  • Excel VBA Range单元格操作实例

    四.Range操作 4.2取得最后一个非空单元格 xlDown/xlToRight/xlToLeft/xlUp Dim ERow as Long Erow=Range("A" &a ...

  • 如何按指定个数在Excel中获得一列数据的所有可能组合?

    Q:数据放置在列A中,我要得到这些数据中任意3个数据的所有可能组合.如下图1所示,列A中存放了5个数据,要得到这5个数据中任意3个数据的所有可能组合,如列B中所示.如何实现? 图1 (注:这是无意在o ...

  • 如何实现打开工作簿后自动跳转到当前日期所在列?

    Q:在Excel工作表中的第1行的每一列,都是按顺序排列的日期,如何在打开工作簿时自动跳转到当前日期所在的列?如下图1所示,如果今天是2019年4月6日,打开工作簿后自动选择这一列. 图1 A:很简单 ...

  • 按指定次数重复内容

    前言 有时候我们需要对一列单元格按照指定的次数进行重复,如下图所示,D列为结果. 解决方法 今天我们试图用VBA来实现,方法如下: ALT+F11 打开VBA,插入任意模块,在模块中输入如下代码: S ...

  • 如何把多个单元格的内容装入到多个合并单元格

    如何取消合并单元格并填充内容,以前弄过一段视频,分别用了四种方法,今天用VBA实现将多个单元格的内容填写到多个不同的合并单元格中. 如下图,需要把多个单元格分别装入到多个合并的区域.一个个复制粘贴显然 ...

  • VBA实战技巧22:调整XY图表缩放比例以获取正确的宽高比

    excelperfect 目标:想要调整XY(散点图)图表,以使两个轴的单位坐标轴值具有相同的比例.也就是说,需要调整图1中的图表,以便成为如图2所示的正方形和圆形. 图1:开始时是椭圆形和长方形 图 ...

  • VBA实战技巧24:调整图表数据标签的位置

    excelperfect 有时候,在Excel中绘制的图表会出现数据标签重叠的情形,不便于查看,如下图1所示. 图1 此时,可以手工拖动数据标签来进行位置调整,也可以使用VBA代码来自动调整. 首先, ...

  • VBA实战技巧25:巧用文本框和列表框

    excelperfect 如下图1所示,在用户窗体界面上实际放置着一个文本框和一个列表框,当单击文本框右侧的下拉按钮时,会出现一个列表框,你可以从中选择数据项并将其输入到文本框中. 图1 也就是说,通 ...

  • VBA实战技巧27:根据颜色汇总单元格数据

    excelperfect 本文给出了一种根据单元格背景色汇总单元格数据的方法:使用VBA创建一个自定义函数来实现该目的. 我们希望这个函数工作的方式是,填充了颜色的单元格来表示额外的信息,例如代表诸如 ...

  • VBA实战技巧28:自动关闭指定时间没有进行操作的工作簿

    excelperfect 有时候,我们打开了一个工作簿,但长时间没有使用,此时,你可能想让Excel自动将其关闭.也就是说,对于某个工作簿,如果用户在指定的时间内没有进行任何操作,那么Excel会保存 ...

  • VBA实战技巧29:从一个工作表复制数据到另一个工作表

    excelperfect 今天演示一个简单的例子,也是经常看到网友问的问题,将一个工作表中的数据复制到另一个工作表. 如下图1所示,有3个工作表,需要将工作表"新数据#1"和&qu ...

  • VBA实战技巧30:创建自定义的进度条1

    excelperfect 宏是Excel中最好的工具之一,可以让我们节省时间. 使用VBA宏,可以自动执行重复.单调且有时非常无聊的任务.在某些情况下,这有可能将数小时的工作减少到几分钟或几秒钟. 但 ...

  • VBA实战技巧30:创建自定义的进度条2

    excelperfect 有创意的进度条 采用相反的方式来显示进度,将使用标签"缩小"而不是"增长".诀窍是我们的标签不是进度的指示器.相反,有一个指示进度的静 ...

  • VBA实战技巧31:彻底移除Excel加载宏

    excelperfect 有时候,当我们不再需要使用某加载宏时,我们可能会直接从系统文件中将该加载宏文件删除.如果这样的话,那么每当你启动Excel时,Excel都会给出一条如下图1所示的消息. 图1 ...