セル範囲の伸び縮みに追随して名前を定義する[Excel](2)

 

セル範囲の伸び縮みに追随して名前を定義するマクロの改良

改良ポイント

前回

akashi-keirin.hatenablog.com

リスト1には問題があった。すなわち、

  • 対象のセル範囲から値を全消去したら、名前の定義されたセル範囲が元のまま残る
  • 対象のセル範囲(を含む範囲)を削除すると、参照切れになった名前だけが残る

というもの。

そもそもが、名前を付けた範囲にちょっと要素を付け足したり、要素を削除したり、ぐらいの使い方を想定していたので、ごっそり削除するなどということは考えていなかったわけですよ。

でもまあ、せっかくなので、対応を考えてみたというわけ。

修正後のコード

次のように修正した。

リスト1 標準モジュール
Public Sub updateRangeName(ByVal Target As Range, _
                           ByVal targetColumn As Long, _
                           ByVal startRow As Long, _
                           ByVal targetName As String)
On Error Resume Next
  If Target.Column <> targetColumn Then Exit Sub
  Dim Sh As Worksheet
  Set Sh = Target.Parent
  Dim tmpRange As Range    '……(1)'
  Set tmpRange = Sh.Range(targetName)    '……(2)'
  If Err.Number = 0 Then    '……(3)'
    If Not hasAnyValue(tmpRange) Then _
        tmpRange.Cells(1, 1).Name = targetName: Exit Sub    '……(4)'
  Else
    Err.Clear    '……(5)'
  End If
On Error Goto 0
  Dim maxRow As Long
  maxRow = Sh.Cells(Rows.Count, targetColumn).End(xlUp).Row
  If Target.Row < startRow Or startRow > maxRow Then    '……(6)'
    If Not hasReferences(targetName) Then _
      Sh.Parent.Names(targetName).Delete: Exit Sub
  End If
  With Sh
    .Range(.Cells(startRow, targetColumn), _
           .Cells(maxRow, targetColumn)).Name = targetName
  End With
End Sub

Private Function hasAnyValue(ByVal targetRange As Range) As Boolean    '……(a)'
  Dim targetCell As Range
  For Each targetCell In targetRange
    If targetCell.Value <> "" Then hasAnyValue = True: Exit Function
  Next
  hasAnyValue = False
End Function

Private Function hasReferences(ByVal NameOfRange As String) As Boolean    '……(b)'
On Error Resume Next
  Dim tmpRange As Range
  Set tmpRange = Range(NameOfRange)
  If Err.Number = 0 Then
    hasReferences = True
  Else
    hasReferences = False
  End If
  Err.Clear
End Function

変更箇所を中心に。

まずは(1)からの8行(実質7行)

Dim tmpRange As Range
Set tmpRange = Sh.Range(targetName)    '……(2)'
If Err.Number = 0 Then    '……(3)'
  If Not hasAnyValue(tmpRange) Then _
      tmpRange.Cells(1, 1).Name = targetName: Exit Sub    '……(4)'
Else
  Err.Clear    '……(5)'
End If

(2)の

Set tmpRange = Sh.Range(targetName)

で、Range型の変数tmpRangeにRange(targetName)をとりあえずぶち込んでみる。

もし、引数targetNameで受け取った名前が定義されていなければ、ここでエラーが出る。

そこで、(3)からの6行(実質5行)

If Err.Number = 0 Then
  If Not hasAnyValue(tmpRange) Then _
      tmpRange.Cells(1, 1).Name = targetName: Exit Sub    '……(4)'
Else
  Err.Clear    '……(5)'
End If

で、エラーの有無(うむ/ゆうむ)によって分岐する。

エラーが出ていなかった場合、すなわちtargetNameで受け取った名前のセル範囲が存在するということだから、(4)の

If Not hasAnyValue(tmpRange) Then _
  tmpRange.Cells(1, 1).Name = targetName: Exit Sub

に移る。

hasAnyValueというのは自作のFunction。リスト1の(a)がそれ。セル範囲(この場合はtargetNameで指し示されるセル範囲)を調べて値が入っているセルがあればTrue、全て空白ならFalseを返す。

targetNameで指し示されるセル範囲が空白、つまりセル範囲の値が全削除された場合にThen以下の

tmpRange.Cells(1, 1).Name = targetName: Exit Sub

を実行する。

定義されたセル範囲の値が全削除された場合は、先頭の1セルだけに名前を定義し直して処理を抜ける、ということになる。

エラーが出ていた場合、すなわちtargetNameで指し示されるセル範囲がない場合は、(5)の

Err.Clear

でErrオブジェクトをリセットして次へ進む。

あとは(6)からの4行(実質3行)

If Target.Row < startRow Or startRow > maxRow Then
  If Not hasReferences(targetName) Then _
    Sh.Parent.Names(targetName).Delete: Exit Sub
End If

行をまるごと削除するなどして、名前を定義していたセル範囲がなくなってしまったような場合には、

startRow > maxRow

がTrueになるので、Then以下の

If Not hasReferences(targetName) Then _
  Sh.Parent.Names(targetName).Delete: Exit Sub

に処理が移る。

hasReferencesというのは、自作のFunction。リスト1の(b)がそれ。名前を渡したら、その名前がリンク切れになっているかどうかを調べ、リンクが生きていればTrue、リンクが切れていたらFalseを返す、というもの。

よって、リンクが切れていたら

Not hasReferences(targetName)

がTrueになり、

Sh.Parent.Names(targetName).Delete: Exit Sub

が実行される。リンク切れになった名前を削除し、処理を抜けることになる。

処理がここまでたどり着いている時点でtargetNameで渡された名前はブックに存在していることになるので、targetNameのチェックは必要ないと思う。

ざっと、こんな感じ。

実行結果

シートモジュールのコードは前回同様。

f:id:akashi_keirin:20180318165411j:plain

まず、この状態で、好漢たちの名前を全消去してみる。

f:id:akashi_keirin:20180318165420j:plain

名前が消えるとともに、もはやこのセル範囲の名前が「梁山泊」ではなくなっていることがお分かりだろう。

f:id:akashi_keirin:20180318165431j:plain

しかし、梁山泊はここに細々と生き残っているのであった。

f:id:akashi_keirin:20180318165442j:plain

分かりにくい画像ですまんが、今度は豪快に行ごと削除してやった。

[数式]タブから「名前の管理」を見てみると、

f:id:akashi_keirin:20180318165504j:plain

どうやら梁山泊は滅亡した模様。金に攻められたのだろうか……。

しかし!

f:id:akashi_keirin:20180318165513j:plain

1つでも名前を入れると、梁山泊復活! 胸熱!

おわりに

本当にここまでする必要があったのだろうか、とは思うが、いろいろ勉強にはなった。

@akashi_keirin on Twitter