- VBA で現在選択中のセルを取得し、所定の範囲内に居るか、単独のセルを選択しているかなどを判定したい。
Selection
と思ったか!?それは罠だ!!
「VBA 選択中のセル」などで検索すると、選択中のセルは Selection
で取得できる (正確には Application.Selection
)という記事がたくさん見つかる。
確かにそれで合ってるのだけど、Selection
はセル以外の物(図形やグラフなど)を選択中はそのオブジェクトを返すので、選択中のセルが Range
で返ってくる前提のコードを組むとエラー になるので注意。例えば図形を選択中に以下のマクロを実行するとエラー。Range
型の変数に代入しようとするだけでもエラー。
Sub TestMacro
'セルを選択中はセル位置を出力するが、セル以外を選択しているとエラー
Debug.Print Selection.Address
End Sub
選択している物が何なのかは以下のように判別できる(詳しくは別記事)、が、後述のもっと楽な方法があるので普通こんなことはしない。
If TypeName(Selection) = "Range" Then
Debug.Print "これはセル:" & Selection.Address
End If
ちなみに、何も選択してなければ Selection
は Nothing
を返すらしいのだけど、Excel でどうやったら何も選択してない状態にできるのかは結局分からなかった。
もっと簡単に取得させてよ
てことで Selection
は使う前に判定が必要なので面倒だなぁと他の方法も探してみた結果、以下の2つを発見。
ActiveWindow.RangeSelection
今現在、セル以外の何を選択していようが、最後に選択していたセルを Range
で取得できる。範囲や複数箇所を選択している場合は全て取得できる。Selection
と違って Range
かどうかの判定が不要なので扱いやすい。
ActiveWindow
(正確には Application.ActiveWindow
)は現在作業中の Excel ウインドウのこと。
ActiveCell
今現在、セル以外の何を選択していようが、最後に選択していたアクティブセルを Range
で取得できる。複数個所を選択している場合は最後に選択したセル、それが範囲であれば範囲選択した始点の、必ず1セルだけを返す。Excel の画面で言うと下記の白い選択セル。
以下のどの記述でも同じ物を指すらしい。
ActiveCell
Application.ActiveCell
ActiveWindow.ActiveCell
Application.ActiveWindow.ActiveCell
- Application.ActiveWindow プロパティ (Excel) | Microsoft Learn
- Window.RangeSelection プロパティ (Excel) | Microsoft Learn
- Application.ActiveCell プロパティ (Excel) | Microsoft Learn
Excel でセルを1箇所も選択しない状態にはできないと思われるので、上記のどちらについても、シートがない状態でマクロ実行でもしない限りは無効値が返ってくる事はないはず。
ただ、セル以外の物を選択すると今までどのセルを選択してたか見た目で分からなくなるので、その状況で最後に選択していたセルに対して処理を行うのはどうなの? という気はする。結局のところ、Selection
が Range
以外の場合は「セルを選択してください」などメッセージを出して処理を行わない方が親切なのかも。
範囲や複数個所を選択中の判定方法
選択中のセルの Range
が取得できたとして、それが単独セルか範囲選択か、複数個所の選択かなどは Range
のプロパティで判定できる。よく使うのは下記。
Range.Address
全ての選択個所と範囲を文字列形式で確認できる。
Debug.print
等で確認するには便利だが、これで何かを判定するのは面倒。
Range.CountLarge
選択中のセルの総数。単独セルの選択か否かの判定に使える。
例えば3行×5列の範囲を選択していたら 15。複数選択なら全ての箇所の総和。範囲が重なっている場合、重複部分は重複してカウントされる。
Range.count
や Range.Cells.count
でも同じ値を取得できるが、シート全体を選択するとセル数が Long
の最大値を超えてしまい、個数判定しただけでオーバーフローのエラー になるので(本当クソ仕様)、判定時は CountLarge
を使った方がよさげ。
Range.Areas.count
単独セル、または1つの範囲の場合は 1、複数個所の選択なら 2 以上になる。複数個所の選択か否かの判定に使える。
Range.Areas
は複数選択した箇所の Range
のコレクション。
Range.Rows.count, Range.Columns.count
最初に選択したセルまたは範囲 の行数、列数。
複数箇所を選択している場合は Range.Areas
を1つづつ確認が必要。
Range.Row, Range.Column
最初に選択したセルまたは範囲 の左上位置の行、列番号(1~)。どういう方向に範囲選択した場合も必ず範囲の左上の位置で、範囲選択の始点ではない。
複数箇所を選択している場合は Range.Areas
を1つづつ確認が必要。
ある範囲内を選択中かの判定
Intersect()
を使う。2つの範囲の共通範囲か、なければ Nothing
を返す。
'とあるテーブルの実データ部分の範囲
Dim tableRange As Range
Set tableRange = ActiveSheet.ListObjects("テーブル名").DataBodyRange
'テーブルの範囲内を選択中か?
Dim inTable As Range
Set inTable = Intersect(tableRange, ActiveWindow.RangeSelection)
if inTable Is Nothing Then
Debug.Print "範囲外"
Else
Debug.Print "共通範囲:" & inTable.Address
End If
面倒なのでヘルパー作った
実用上は常に ActiveWindow.RangeSelection
や ActiveCell
を使うだけで問題はないのだけど、やはりセル以外を選択していて選択中のセルが見えてないのに処理を行うのは気持ち悪かったので、セルを選択中のみ Range
を返すヘルパーを作って使うことにした。以下サンプルコード。
'セルを選択中か?
'セルを選択中のみ True と、sel に選択中のセルを返す
Function IsSelectCell(ByRef sel As Range) As Boolean
If TypeName(Selection) = "Range" Then
Set sel = Selection
IsSelectCell = True
Exit Function
End If
IsSelectCell = False
End Function
'範囲内のセルを選択中か?
'inRange 内のセルを選択中のみ True と、sel に選択中のセルを返す
'inRange との共通範囲を返すでもいいのだが、さらに他の判定などもしたい場合は
'無加工の選択中セルの方が扱いやすいのでそうした。
Function IsSelectRange(ByRef sel As Range, inRange As Range) As Boolean
If TypeName(Selection) = "Range" Then
If Not Intersect(Selection, inRange) Is Nothing Then
Set sel = Selection
IsSelectRange = True
Exit Function
End If
End If
IsSelectRange = False
End Function
'使用例:あるテーブルの範囲内の1行のみを選択している場合のみ処理を行う
Dim table As ListObject
Set table = ActiveSheet.ListObjects("テーブル名")
Dim sel As Range
If IsSelectRange(sel, table.DataBodyRange) Then
If sel.Areas.count = 1 And sel.Rows.count = 1 Then
Debug.Print "何らかの処理実行!"
Exit Sub
End If
End If
MsgBox "テーブル内の1行のみ選択してください"