Application.Intersectメソッドを使う

セルが指定した範囲内にあるかどうかを判定するFunction

Application.Intersectメソッドを使う

何気なく日経ソフトウエアの1月号を読んでいたら、武藤玄氏の連載記事「実務ですぐに役立つExcel VBA」の中に、次のような記述があった。

Intersectメソッドは、第1引数のセル範囲が、第2引数のセル範囲に含まれるとき、共有するセル範囲を返します。また、含まれないときは「Nothing」を返します。

ほおぉ~! 知らなかった。当該記事では、このIntersectメソッドを用いて、選択したセルが表の内側にあるか、外側にあるのかの判定に使っていた。

今まで、同じことをWorksheet_SelectionChangeの中で、引数Target(As Range)のRowプロパティやColumnプロパティで条件分岐してやっていたけれど、Intersectメソッドの挙動を利用すれば、もっとスマートにできるということだ。

Function化

ひとまず、簡単に、第1引数で指定したセルが、第2引数で指定した範囲内にあればTrue、なければFalseを返すFunctionにしてみた。

リスト1 標準モジュール
'宣言セクション'
Public Const ERROR_MESSAGE_10007 As String = _
  "isWithinTheRangeメソッドの引数targetCellは、単一のセルとしてください。"
'宣言セクションここまで'

Public Function isWithinTheRange(ByVal targetCell As Range, _
                                 ByVal serchFor As Range) As Boolean    '……(1)'
  With targetCell    '……(2)'
    If .Rows.Count > 1 Or _
       .Columns.Count > 1 Then _
        Err.Raise Number:=10007, _
                  Description:=ERROR_MESSAGE_10007
  End With
  Dim tmpRange As Range
  Set tmpRange = Application.Intersect(targetCell, serchFor)    '……(3)'
  If tmpRange Is Nothing Then    '……(4)'
    'tmpRangeがNothingだったら、範囲内にない。'
    isWithinTheRange = False
  Else
    'tmpRangeにセルが格納されていたら、範囲内にある。'
    isWithinTheRange = True
  End If
  Set tmpRange = Nothing
End Function

まず、(1)の

Public Function isWithinTheRange(ByVal targetCell As Range, _
                                 ByVal serchFor As Range) As Boolean

で引数と返り値の設定。

第1引数には範囲内にあるかどうかを調べたいセル、第2引数には調べる対象のセル範囲を指定する。んで、範囲内にあったらTrue、範囲外だったらFalseを返す。

(2)からの6行(実質3行)

With targetCell    '……(2)'
  If .Rows.Count > 1 Or _
     .Columns.Count > 1 Then _
      Err.Raise Number:=10007, _
                Description:=ERROR_MESSAGE_10007
End With

では、第1引数をチェックし、複数セルになっていたらエラーを吐く。

ここからがメインの処理。

(3)の

Set tmpRange = Application.Intersect(targetCell, serchFor)

でIntersectメソッドを使う。変数tmpRangeには、targetCellがserchForの範囲内にあればtargetCellが格納され、範囲外だったらNothingが格納されることになる。

あとは、(4)からの5行(コメント行除く)

If tmpRange Is Nothing Then    '……(4)'
  'tmpRangeがNothingだったら、範囲内にない。'
  isWithinTheRange = False
Else
  'tmpRangeにセルが格納されていたら、範囲内にある。'
  isWithinTheRange = True
End If

で、tmpRangeの中身に応じて返り値を設定する。

使用例

f:id:akashi_keirin:20180108102740j:plain

こんなシートを準備して、そのシートモジュールに次のコードを書いてみる。

スト2 シートモジュール

下記は修正前のものです。修正後のコードはコチラをどうぞ!

'宣言セクション'
Private Const START_ROW As Long = 1    '……(1)'
Private Const START_COLUMN As Long = 1
'宣言セクションここまで'

Private Sub Worksheet_SelectionChange(ByVal Target As Range)
  Dim Sh As Worksheet
  Set Sh = Target.Parent
  Dim lastRow As Long    '……(2)'
  lastRow = Sh.Cells(Rows.Count, START_COLUMN).End(xlUp).Row
  Dim lastColumn As Long
  lastColumn = Sh.Cells(START_ROW, Columns.Count).End(xlToLeft).Column
  Dim tableRange As Range    '……(3)'
  With Sh
    Set tableRange = .Range(.Cells(START_ROW, START_COLUMN), _
                            .Cells(lastRow, lastColumn))
  End With
  If Not isWithinTheRange(Target, tableRange) Then _
    Call makeUserSick("表の範囲外を選ぶなボケ!")    '……(4)'
End Sub

(1)からの2行

Private Const START_ROW As Long = 1
Private Const START_COLUMN As Long = 1

は、表の開始位置(A1セル、すなわち列目)を定数にしている。表の開始位置が変わったら、定数の定義だけを変更すればよい。

(2)からの4行

Dim lastRow As Long
lastRow = Sh.Cells(Rows.Count, START_COLUMN).End(xlUp).Row
Dim lastColumn As Long
lastColumn = Sh.Cells(START_ROW, Columns.Count).End(xlToLeft).Column

では、表の終端の行・列番号を、おなじみのEndプロパティを使うやり方で求めている。

(3)からの5行(実質4行)

Dim tableRange As Range
With Sh
  Set tableRange = .Range(.Cells(START_ROW, START_COLUMN), _
                          .Cells(lastRow, lastColumn))
End With

では、表全体を一つのRangeオブジェクトとして変数tableRangeにぶち込んだ。別にここまでする必要はないのだけれど……。

あとは、(4)の

If Not isWithinTheRange(Target, tableRange) Then _
    Call makeUserSick("表の範囲外を選ぶなボケ!")

先ほどのリスト1のisWithinTheRangeメソッドを用いて条件判定し、選んだセル(Target)が表の範囲(tableRange)外にあったら、当ブログではおなじみのmakeUserSickメソッドを呼び出してちょっとむかつくメッセージを表示する、という仕掛けにした。

実行結果

f:id:akashi_keirin:20180108102756j:plain

表の範囲外を選択すると、

f:id:akashi_keirin:20180108102807j:plain

煽られるwww

おわりに

まあ、この程度の使い方なら、「わざわざIntersectメソッドをラップせんでも、そのまま使やいーじゃん」でしょうなあ。

追記

リスト2のコードだと、複数セルを選んだだけでエラーを吐いてしまうので、ちょっと修正する。

スト2改 シートモジュール
'宣言セクション'
Private Const START_ROW As Long = 1
Private Const START_COLUMN As Long = 1
'宣言セクションここまで'
Private Sub Worksheet_SelectionChange(ByVal Target As Range)
  Dim Sh As Worksheet
  Set Sh = Target.Parent
  Dim lastRow As Long
  lastRow = Sh.Cells(Rows.Count, START_COLUMN).End(xlUp).Row
  Dim lastColumn As Long
  lastColumn = Sh.Cells(START_ROW, Columns.Count).End(xlToLeft).Column
  Dim tableRange As Range
  With Sh
    Set tableRange = .Range(.Cells(START_ROW, START_COLUMN), _
                            .Cells(lastRow, lastColumn))
  End With
  Dim targetCell As Range
  For Each targetCell In Target
    If Not isWithinTheRange(targetCell, tableRange) Then _
      Call makeUserSick("表の範囲外を選ぶなボケ!"): Exit For
  Next
End Sub

これで大丈夫だと思います。

参考

私は、以下のコードをXlCommonと名付けた標準モジュールに入れておいて、インポートしていろんなマクロで使い回していますw

makeUserSickメソッド 標準モジュール
'宣言セクション'
Public Const MAKE_USER_SICK_2013 As String = _
              "       _______" & vbCrLf & _
              "   /       \" & vbCrLf & _
              "/ /・\  /・\ \" & vbCrLf & _
              "|   ̄ ̄    ̄ ̄   |   ち~ん(笑)" & vbCrLf & _
              "|    (_人_)   |" & vbCrLf & _
              "|     \  |   |" & vbCrLf & _
              "\     \_|  /"

Public Const MAKE_USER_SICK_2010 As String = _
              "     _______" & vbCrLf & _
              " /               \ " & vbCrLf & _
              "/ /・\  /・\     \" & vbCrLf & _
              "|   ̄ ̄    ̄         | ち~んw" & vbCrLf & _
              "|    (_人_)         |" & vbCrLf & _
              "|     \     |          |" & vbCrLf & _
              "\      \_|       /"
'宣言セクションここまで'

Public Sub makeUserSick(Optional ByVal msg As String)
  Dim ver As String
  ver = Application.Version
  Dim str As String
  Select Case ver
    Case "14.0"
      str = MAKE_USER_SICK_2010
    Case "15.0"
      str = MAKE_USER_SICK_2013
    Case "16.0"
      str = MAKE_USER_SICK_2013
    Case Else
      str = MAKE_USER_SICK_2010
  End Select
  If msg = "" Then msg = "涙拭けよwww"
  MsgBox msg & vbCrLf & str
End Sub