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の中身に応じて返り値を設定する。
使用例
こんなシートを準備して、そのシートモジュールに次のコードを書いてみる。
リスト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セル、すなわち1行1列目)を定数にしている。表の開始位置が変わったら、定数の定義だけを変更すればよい。
(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メソッドを呼び出してちょっとむかつくメッセージを表示する、という仕掛けにした。
実行結果
表の範囲外を選択すると、
煽られる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