トレース機能の貧弱さ
- 参照先の確認は、「参照先のトレース」または「参照先へジャンプ」で行える
- 一方、他シートを参照していると挙動が怪しいなど、不便な点が多い
- また、参照先の参照先を見たりしていると、もとのセルに戻れなくなる
VBAを使った改善案 - マクロの要件
- 参照先の参照先の参照先・・・ を確認できるようにする
- 元のセルに戻れるようにする
- セルを選択して起動すれば、あとはフォーム内の操作で完結できるようにする
マクロの全体像
選択中のセルの参照先をリストアップする
選択中のRangeオブジェクトに対して、Precedentsプロパティを使用したくなるところですが、Precendetsプロパティは他シートを参照できません。
次は、NavigateArrowメソッドを使うという手が考えられます。こちらは、他シートを参照することもできます。
しかしながら、NavigateArrowは、元のセルに近い順に参照先をリストアップしてしまうので、数式を確認するという用途では使いづらいと言えます。
したがって、数式の文字列を直接抽出するという方法をとるしかありません。
ユーザーフォームに並べる
抽出した参照先の配列をユーザーフォームに並べます。
ListBoxオブジェクトのListプロパティに入れるだけです。
セルを選択する
これが少し厄介になります。Selectメソッドは、実はシートをまたぐことができません。
同じように、Rangeオブジェクトの引数にシート名を入れることはできませんし、参照先には名前が使われることもあります。
要するに、Range("Sheet1!A1").Select を行えるようなマクロを組む必要があります。
再度参照先をリストアップする
参照先の参照先を参照するためには、最初に戻れなくてはなりません。
これを何段階か行える仕組みをユーザーフォームで実現する必要があります。
サンプルコード
参照先をカンマ区切りで取得する
参照先かどうかを直接的に判断することはできませんから、配列化して確認します。
配列化に使うSplit関数は、複数の区切り文字をしていることができません。このため、まずは四則演算の記号や()をあらかじめカンマ(カンマ以外でもよい)に置き換えます。
あとは、配列の要素が A1 のような恰好をしているかを判定します。ここが力業になってしまいますが、よほど特殊な書き方をしていない限り、!, &, : の3記号を含む要素はセルアドレスです。それらを含まないもののうち、アルファベット&数字型の要素もアドレスでしょう。
Function Precedents_Csv(r_argument As Range) As String
Dim address_csv As String
address_csv = ""
'===============================
' r_argument is not a cell
'===============================
Dim r As Range
If r_argument.Count >= 2 Then
For Each r In r_argument
address_csv = address_csv & "," & r.Parent.Name & "!" & r.Address
Next r
Precedents_Csv = Mid(address_csv, 2)
Exit Function
End If
'===============================
' r_argument hasn't got formula
'===============================
If r_argument.HasFormula = False Then
Precedents_Csv = r_argument.Value
Exit Function
End If
'===============================
' r_argument is a cell
'===============================
Dim all_array As Variant
Dim i As Integer
Dim address_of_element As String
all_array = Split(Replace_Symbols(r_argument.Formula), ",")
For i = 0 To UBound(all_array)
Select Case Type_Of_Address(all_array(i))
Case 0 ' Address with "!"
address_csv = address_csv & "," & all_array(i)
Case 1 ' Address without "!"
address_csv = address_csv & "," & r_argument.Parent.Name & "!" & all_array(i)
Case 2 ' Non Address
address_of_element = ""
End Select
Next i
Precedents_Csv = Mid(address_csv, 2)
End Function
Function Replace_Symbols(target_formula As String) As String
Dim tmp As String
tmp = target_formula
tmp = Replace(tmp, "+", ",")
tmp = Replace(tmp, "-", ",")
tmp = Replace(tmp, "*", ",")
tmp = Replace(tmp, "/", ",")
tmp = Replace(tmp, "^", ",")
tmp = Replace(tmp, "&", ",")
tmp = Replace(tmp, "=", ",")
tmp = Replace(tmp, ">", ",")
tmp = Replace(tmp, "<", ",")
tmp = Replace(tmp, "(", ",")
tmp = Replace(tmp, ")", ",")
Replace_Symbols = tmp
End Function
Function Type_Of_Address(test_string As Variant) As Integer
' Declaration
Dim addresses(1 To 21)
Dim alp As String
Dim num As String
alp = "[A-Z]"
num = "[0-9]"
addresses(1) = alp & num
addresses(2) = alp & num & num
addresses(3) = alp & num & num & num
addresses(4) = alp & num & num & num & num
addresses(5) = alp & num & num & num & num & num
addresses(6) = alp & num & num & num & num & num & num
addresses(7) = alp & num & num & num & num & num & num & num
addresses(8) = alp & alp & num
addresses(9) = alp & alp & num & num
addresses(10) = alp & alp & num & num & num
addresses(11) = alp & alp & num & num & num & num
addresses(12) = alp & alp & num & num & num & num & num
addresses(13) = alp & alp & num & num & num & num & num & num
addresses(14) = alp & alp & num & num & num & num & num & num & num
addresses(15) = alp & alp & alp & num
addresses(16) = alp & alp & alp & num & num
addresses(17) = alp & alp & alp & num & num & num
addresses(18) = alp & alp & alp & num & num & num & num
addresses(19) = alp & alp & alp & num & num & num & num & num
addresses(20) = alp & alp & alp & num & num & num & num & num & num
addresses(21) = alp & alp & alp & num & num & num & num & num & num & num
'===============================
' Is Name
'===============================
Dim n As Name
For Each n In Names
If test_string = n.Name Then
test_string = Mid(n.RefersTo, 2)
Type_Of_Address = 0
Exit Function
End If
Next n
'===============================
' Has Specific Symbol
'===============================
If test_string Like "*!*" Then
Type_Of_Address = 0
Exit Function
ElseIf test_string Like "*$*" Or test_string Like "*:*" Then
Type_Of_Address = 1
Exit Function
End If
'===============================
' Is [A-Z] & [0-9] Type
'===============================
Dim i As Integer
For i = 1 To 21
If test_string Like addresses(i) Then
Type_Of_Address = 1
Exit Function
End If
Next i
'===============================
' Others: Is not an address
'===============================
Type_Of_Address = 2
End Function
アクティブでないシートのセルの選択と取得
Sub Flex_Select(address_argument As String)
' Select range in nonactive worksheet
On Error Resume Next
Worksheets(Split(address_argument, "!")(0)).Activate
Range(Split(address_argument, "!")(1)).Select
End Sub
Function Address_To_Range(address_argument As String) As
' Get range in nonactive worksheet
On Error Resume
' Convert Name to Address
Dim n As Name
For Each n In Names
If address_argument = n.Name Then
address_argument = Mid(n.RefersTo, 2)
Exit For
End If
Next
' Set Range
Dim ws As Worksheet
Set ws = Worksheets(Split(address_argument, "!")(0))
Set Address_To_Range = ws.Range(Split(address_argument, "!")(
End Function
ユーザーフォームの設計
フォーム
コード
Option Explicit
Private Sub Label8_Click()
With UFM_TRACE
Flex_Select .Label8.Caption
.ListBox1.SetFocus
.ListBox2.Clear
.ListBox3.Clear
.ListBox4.Clear
.ListBox5.Clear
End With
End Sub
Private Sub CommandButton1_Click()
With UFM_TRACE
Flex_Select .Label8.Caption
.ListBox1.SetFocus
.ListBox2.Clear
.ListBox3.Clear
.ListBox4.Clear
.ListBox5.Clear
End With
End Sub
Private Sub CommandButton2_Click()
Dim previous_lbox As Object
Dim current_lbox As Object
With UFM_TRACE
.TextBox2.Value = ""
If .ListBox5.ListCount > 0 Then
Set current_lbox = .ListBox5
Set previous_lbox = .ListBox4
ElseIf .ListBox4.ListCount > 0 Then
Set current_lbox = .ListBox4
Set previous_lbox = .ListBox3
ElseIf .ListBox3.ListCount > 0 Then
Set current_lbox = .ListBox3
Set previous_lbox = .ListBox2
Else
Set current_lbox = .ListBox2
Set previous_lbox = .ListBox1
End If
End With
current_lbox.Clear
previous_lbox.SetFocus
End Sub
Private Sub CommandButton3_Click()
Dim next_lbox As Object
Dim current_lbox As Object
With UFM_TRACE
If .ListBox2.ListCount = 0 Then
Set current_lbox = .ListBox1
Set next_lbox = .ListBox2
ElseIf .ListBox3.ListCount = 0 Then
Set current_lbox = .ListBox2
Set next_lbox = .ListBox3
ElseIf .ListBox4.ListCount = 0 Then
Set current_lbox = .ListBox3
Set next_lbox = .ListBox4
Else
Set current_lbox = .ListBox4
Set next_lbox = .ListBox5
End If
End With
With current_lbox
Update_Formulabox .List(.ListIndex)
End With
Dim precedents_arr As Variant
With current_lbox
precedents_arr = Split(Precedents_Csv(Address_To_Range(.List(.ListIndex))), ",")
End With
Update_Listbox next_lbox, precedents_arr
next_lbox.SetFocus
End Sub
Private Sub CommandButton4_Click()
Unload UFM_TRACE
End Sub
Private Sub ListBox1_Change()
Change_Listbox UFM_TRACE.ListBox1
End Sub
Private Sub ListBox2_Change()
Change_Listbox UFM_TRACE.ListBox2
End Sub
Private Sub ListBox3_Change()
Change_Listbox UFM_TRACE.ListBox3
End Sub
Private Sub ListBox4_Change()
Change_Listbox UFM_TRACE.ListBox4
End Sub
Private Sub ListBox5_Change()
Change_Listbox UFM_TRACE.ListBox5
End Sub
Private Sub UserForm_Initialize()
With UFM_TRACE
.StartUpPosition = 2
.Width = 570
.Height = 340
.Caption = "Precedent Tracer"
' Original Formula
With .Label1
.Top = 10: .Left = 10: .Width = 200: .Height = 15
.Font.Size = 8: .Font.Bold = True
.Caption = "Original Formula"
End With
With .TextBox1
.Top = 25: .Left = 10: .Width = 540: .Height = 20
.Font.Size = 10
.SpecialEffect = fmSpecialEffectEtched
.Text = Selection.Formula
End With
' Current Formula
With .Label2
.Top = 55: .Left = 10: .Width = 200: .Height = 15
.Font.Size = 8: .Font.Bold = True
.Caption = "Current Formula/Value"
End With
With .TextBox2
.Top = 70: .Left = 10: .Width = 540: .Height = 20
.Font.Size = 10
.SpecialEffect = fmSpecialEffectEtched
End With
' Labels
With .Label3
.Top = 100: .Width = 100: .Height = 15
.Left = 10
.Font.Size = 8: .Font.Bold = True
.Caption = "Precedents 1"
End With
With .Label4
.Top = 100: .Width = 100: .Height = 15
.Left = 120
.Font.Size = 8: .Font.Bold = True
.Caption = "Precedents 2"
End With
With .Label5
.Top = 100: .Width = 100: .Height = 15
.Left = 230
.Font.Size = 8: .Font.Bold = True
.Caption = "Precedents 3"
End With
With .Label6
.Top = 100: .Width = 100: .Height = 15
.Left = 340
.Font.Size = 8: .Font.Bold = True
.Caption = "Precedents 4"
End With
With .Label7
.Top = 100: .Width = 100: .Height = 15
.Left = 450
.Font.Size = 8: .Font.Bold = True
.Caption = "Precedents 5"
End With
' List Boxes
With .ListBox1
.Top = 115: .Width = 100: .Height = 150
.Left = 10
.Font.Size = 10
.TabStop = False
.SpecialEffect = fmSpecialEffectEtched
.SetFocus
End With
With .ListBox2
DoEvents
.Top = 115
.Width = 100
.Height = 150
.Left = 120
.Font.Size = 10
.TabStop = False
.SpecialEffect = fmSpecialEffectEtched
End With
With .ListBox3
.Top = 115: .Width = 100: .Height = 150
.Left = 230
.Font.Size = 10
.TabStop = False
.SpecialEffect = fmSpecialEffectEtched
End With
With .ListBox4
.Top = 115: .Width = 100: .Height = 150
.Left = 340
.Font.Size = 10
.TabStop = False
.SpecialEffect = fmSpecialEffectEtched
End With
With .ListBox5
.Top = 115: .Width = 100: .Height = 150
.Left = 450
.Font.Size = 10
.TabStop = False
.SpecialEffect = fmSpecialEffectEtched
End With
' Buttons
With .CommandButton1
.Top = 280: .Width = 40: .Height = 20
.Font.Size = 8: .Font.Bold = True
.Left = 10
.Caption = "Go to:"
.Accelerator = "G"
End With
With .Label8
.Top = 283: .Width = 120: .Height = 20
.Font.Size = 10: .Font.Underline = True
.ForeColor = RGB(22, 0, 245)
.Left = 55
.Caption = ActiveSheet.Name & "!" & Selection.Address
End With
With .CommandButton2
.Top = 280: .Width = 100: .Height = 20
.Font.Size = 8: .Font.Bold = True
.Left = 230
.Caption = "Previous Precedents"
.Accelerator = "P"
End With
With .CommandButton3
.Top = 280: .Width = 100: .Height = 20
.Font.Size = 8: .Font.Bold = True
.Left = 340
.Caption = "Next Precedents"
.Accelerator = "N"
End With
With .CommandButton4
.Top = 280: .Width = 100: .Height = 20
.Font.Size = 8: .Font.Bold = True
.Left = 450
.Caption = "Close Tracer"
.Accelerator = "C"
End With
Call Update_Listbox(.ListBox1, Split(Precedents_Csv(Selection), ","))
End With
End Sub
Private Sub Update_Listbox(list_box As Object, list_array As Variant)
Dim i As Integer
With list_box
.Clear
For i = 0 To UBound(list_array)
.AddItem list_array(i)
Next i
End With
End Sub
Private Sub Update_Formulabox(address_argument As String)
On Error GoTo ErrorExit
Dim r As Range
Dim target_range As Range
Dim formula_box As Object
Set target_range = Address_To_Range(address_argument)
Set formula_box = UFM_TRACE.TextBox2
formula_box.Value = ""
If target_range.Count >= 2 Then
For Each r In target_range
formula_box.Value = formula_box.Value & "," & r.Address
Next r
formula_box.Value = Mid(formula_box, 2)
ElseIf target_range.HasFormula = True Then
formula_box.Value = target_range.Formula
Else
formula_box.Value = target_range.Value
End If
ErrorExit:
End Sub
Private Sub Change_Listbox(list_box As Object)
On Error GoTo ErrorExit
Dim selected_address As String
With list_box
selected_address = .List(.ListIndex)
End With
Flex_Select selected_address
ErrorExit:
End Sub