連絡先 Hikwareホーム
Hikware.Tech
自分用の覚え書きをそのまま公開。参考程度にどうぞ。

VBAで選択中のセルを取得、判定

公開日 2024/03/04
最終更新 2024/03/04
  • 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

 ちなみに、何も選択してなければ SelectionNothing を返すらしいのだけど、Excel でどうやったら何も選択してない状態にできるのかは結局分からなかった。

もっと簡単に取得させてよ

 てことで Selection は使う前に判定が必要なので面倒だなぁと他の方法も探してみた結果、以下の2つを発見。

ActiveWindow.RangeSelection

 今現在、セル以外の何を選択していようが、最後に選択していたセルを Range で取得できる。範囲や複数箇所を選択している場合は全て取得できるSelection と違って Range かどうかの判定が不要なので扱いやすい。

 ActiveWindow (正確には Application.ActiveWindow)は現在作業中の Excel ウインドウのこと。

ActiveCell

 今現在、セル以外の何を選択していようが、最後に選択していたアクティブセルを Range で取得できる。複数個所を選択している場合は最後に選択したセル、それが範囲であれば範囲選択した始点の、必ず1セルだけを返す。Excel の画面で言うと下記の白い選択セル。

 以下のどの記述でも同じ物を指すらしい。

ActiveCell
Application.ActiveCell 
ActiveWindow.ActiveCell 
Application.ActiveWindow.ActiveCell

 Excel でセルを1箇所も選択しない状態にはできないと思われるので、上記のどちらについても、シートがない状態でマクロ実行でもしない限りは無効値が返ってくる事はないはず。

 ただ、セル以外の物を選択すると今までどのセルを選択してたか見た目で分からなくなるので、その状況で最後に選択していたセルに対して処理を行うのはどうなの? という気はする。結局のところ、SelectionRange 以外の場合は「セルを選択してください」などメッセージを出して処理を行わない方が親切なのかも。

範囲や複数個所を選択中の判定方法

 選択中のセルの Range が取得できたとして、それが単独セルか範囲選択か、複数個所の選択かなどは Range のプロパティで判定できる。よく使うのは下記。

Range.Address

全ての選択個所と範囲を文字列形式で確認できる。
Debug.print 等で確認するには便利だが、これで何かを判定するのは面倒。

Range.CountLarge

選択中のセルの総数。単独セルの選択か否かの判定に使える
例えば3行×5列の範囲を選択していたら 15。複数選択なら全ての箇所の総和。範囲が重なっている場合、重複部分は重複してカウントされる。

Range.countRange.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.RangeSelectionActiveCell を使うだけで問題はないのだけど、やはりセル以外を選択していて選択中のセルが見えてないのに処理を行うのは気持ち悪かったので、セルを選択中のみ 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行のみ選択してください"