Excel入力規則(リスト)を複数選択するVBA|カンマ区切り追記・再選択で削除

Excelの入力規則(リスト)を「複数選択」できない…そんなときは、Worksheet_Change と Application.Undo を使うVBAで、選択値をカンマ区切りで追記し、再選択で削除(トグル)できます。別シートに候補がある場合の注意点と、実運用で安定する修正版コードをまとめます。

目次

Excelの入力規則(リスト)は基本的に「単一選択」

Excelの入力規則(データの入力規則)の「リスト」は、標準機能だけでは同じセルに複数の候補を選んで保持することができません。ドロップダウンから選び直すと、以前の値が上書きされます。

そのため、チェックボックスのような複数選択を疑似的に実現したい場合は、次のような仕組みをVBAで作るのが定番です。

  • 入力規則リストのセルで値が変わったタイミング(Worksheet_Change)を検知
  • 今選ばれた値(New)を取得し、Application.Undoで直前の値(Old)に戻して取得
  • OldにNewが含まれていなければ「追記」、含まれていれば「削除」(再選択でトグル)

「候補リストが別シートにある」ことは原因になりにくい

入力規則の候補(元データ)が別シートにあること自体は、VBAの「追記/削除」ロジックが動かない直接原因になりにくいです。なぜなら、VBA側は基本的に「セルに入った値」を扱うだけで、候補の所在は参照しないからです。

ただし、別シート参照の入力規則は設定方法を誤ると「入力規則そのものが壊れている」状態になりやすく、結果としてTarget.Validationが想定どおり取得できないことがあります。ここは後半の「入力規則を安定させる(別シート参照の正攻法)」で対策します。

動かないときに多い原因

提示コードが動かないケースの多くは、候補リストの場所よりも「VBAコードの置き場所」や「構文・変数名の不整合」に起因します。特に、Worksheet_Changeはイベントなので、些細なミスでも一切動かなくなります。

症状よくある原因対策
ドロップダウンで選んでも追記されないコードが標準モジュールに入っている/別シートのコードモジュールに入っている対象セルがあるワークシートのコードモジュールに貼り付ける(後述)
エラーで止まる/何も起きない変数名の不一致(例:MultipleSelectRangeMultiSelectRange変数名を統一し、Option Explicitで未宣言変数を検出する
一度動いたが、その後Excel全体が反応しないApplication.EnableEvents = Falseのまま戻っていない必ず復帰する終了処理(Exitラベル+エラーハンドラ)を入れる
リスト以外のセル編集でエラーTarget.Validation.Typeを、入力規則がないセルに対して参照しているエラーを握りつぶす関数で「リストかどうか」を安全判定する
貼り付けで意図しない動作複数セル同時変更でUndoが破綻Target.CountLarge > 1は処理しない(安全側に倒す)
VBEで「コンパイル エラー」になるIfの閉じ忘れ(End If不足)/括弧やThenの書き方ミスインデントを整え、IfのネストをすべてEnd Ifで閉じる。Option Explicitで事前検知する

イベントは「一行でも構文が崩れると発火以前にコンパイルで止まる」ため、インデントを揃えてネストを見える化し、まずはエラーなくコンパイルできる状態にするのが近道です。

まずは入力規則(リスト)を安定させる(別シート参照の正攻法)

別シートに候補リストがある場合、入力規則の「元の値(Source)」に直接=Sheet2!$A$1:$A$100のような形で入れると、環境や操作手順によっては設定できなかったり、いつの間にか壊れたりします。安定運用するなら「名前付き範囲(名前定義)」を経由するのが最も確実です。

やりたいことおすすめの設定ポイント
別シートの範囲を入力規則リストにする名前付き範囲を作り、入力規則の元の値を=名前にする参照先が別シートでも壊れにくい
候補が増減するので自動で追従させたい候補範囲をExcelテーブル化し、その列を参照する名前を作る行追加で候補が自動拡張し、運用が楽
候補に空白が混ざるのを避けたい候補列を「空白なし」に整備(または別列で整形)空白があると「空の選択肢」が出て操作ミスの原因

名前付き範囲の作り方(代表例)は次のとおりです。

  • 候補リストがあるシートで範囲(例:A2:A200)を選択
  • 数式バー左の「名前ボックス」に任意の名前(例:dv_Category)を入力してEnter
  • 入力規則を設定したいセル範囲を選択 →「データ」→「データの入力規則」→「リスト」→「元の値」に=dv_Category

こうしておくと、VBA側のロジックが「入力規則がリストかどうか」を判定する処理も安定します。

VBAを入れる場所が最重要(ここを間違えると動きません)

Worksheet_Changeは「特定のシートで、セルが変更されたとき」にだけ動くイベントです。したがって、コードは次の場所に入れる必要があります。

  • 対象セルが存在するワークシート(例:Sheet1)のコードモジュール

手順の目安は以下です。

  • Alt + F11 でVBE(VBAエディタ)を開く
  • 左のプロジェクトで対象ブックを展開
  • 「Microsoft Excel Objects」配下の該当シート(例:Sheet1(入力シート))をダブルクリック
  • 右側のコードウィンドウに貼り付ける

また、シートを明示するためにRange("C2:C124")ではなくMe.Range("C2:C124")と書くと、「どのシートの範囲か」がブレず安全です。

複数選択(追記/再選択で削除)の完成版コード

以下は、入力規則(リスト)のセルを「カンマ区切りで追記」し、同じ項目をもう一度選ぶと削除する(トグル)実装です。貼り付けなどの複数セル変更は事故りやすいので対象外にし、入力規則がないセルでもエラーにならないようにしています。

Option Explicit

Private Sub Worksheet_Change(ByVal Target As Range)
    Const DELIM As String = ", "   '区切り文字(必要に応じて変更)

    Dim oldText As String
    Dim newItem As String
    Dim multiSelectRange As Range
    Dim prevEvents As Boolean

    prevEvents = Application.EnableEvents
    On Error GoTo CleanUp

    '貼り付け等で複数セルが一度に変わるとUndoが崩れるため対象外
    If Target.CountLarge > 1 Then GoTo CleanUp

    '複数選択を許可する範囲(必要に応じて変更)
    Set multiSelectRange = Me.Range("C2:C124")
    If Intersect(Target, multiSelectRange) Is Nothing Then GoTo CleanUp

    '入力規則が「リスト」以外なら処理しない
    If Not IsListValidation(Target) Then GoTo CleanUp

    'ユーザーがDeleteで消した場合は、そのまま空にする(追記ロジックは走らせない)
    If Len(Target.Value2) = 0 Then GoTo CleanUp

    Application.EnableEvents = False

    '今回選ばれた値(New)
    newItem = Trim$(CStr(Target.Value2))

    '直前の値(Old)を取得するためにUndo
    Application.Undo
    oldText = Trim$(CStr(Target.Value2))

    'Oldが空なら、そのままNewを入れる
    If Len(oldText) = 0 Then
        Target.Value = newItem
        GoTo CleanUp
    End If

    'Oldに含まれていなければ追記、含まれていれば削除(トグル)
    If ListContainsItem(oldText, newItem) Then
        Target.Value = RemoveItemFromList(oldText, newItem, DELIM)
    Else
        Target.Value = AppendItemToList(oldText, newItem, DELIM)
    End If

CleanUp:
    On Error Resume Next
    Application.EnableEvents = prevEvents
End Sub

'セルに入力規則が存在しない場合でも落ちないように安全判定する
Private Function IsListValidation(ByVal cell As Range) As Boolean
    On Error GoTo ErrHandler
    IsListValidation = (cell.Validation.Type = 3) 'xlValidateList = 3
    Exit Function
ErrHandler:
    IsListValidation = False
End Function

'「追記」:既存の末尾に区切り文字+項目を追加
Private Function AppendItemToList(ByVal listText As String, ByVal item As String, ByVal delim As String) As String
    If Len(listText) = 0 Then
        AppendItemToList = item
    Else
        AppendItemToList = listText & delim & item
    End If
End Function

'「含まれるか」:カンマ区切りのトークンとして完全一致で判定(部分一致事故を防ぐ)
Private Function ListContainsItem(ByVal listText As String, ByVal item As String) As Boolean
    Dim arr() As String
    Dim i As Long
    Dim token As String

    arr = Split(NormalizeComma(listText), ",")
    For i = LBound(arr) To UBound(arr)
        token = Trim$(arr(i))
        If StrComp(token, item, vbTextCompare) = 0 Then
            ListContainsItem = True
            Exit Function
        End If
    Next i
End Function

'「削除」:一致した項目を取り除き、区切り文字で再結合
Private Function RemoveItemFromList(ByVal listText As String, ByVal item As String, ByVal delim As String) As String
    Dim arr() As String
    Dim i As Long
    Dim token As String
    Dim result As String

    arr = Split(NormalizeComma(listText), ",")
    For i = LBound(arr) To UBound(arr)
        token = Trim$(arr(i))
        If Len(token) > 0 Then
            If StrComp(token, item, vbTextCompare) <> 0 Then
                If Len(result) > 0 Then result = result & delim
                result = result & token
            End If
        End If
    Next i

    RemoveItemFromList = result
End Function

'全角カンマや読点などの揺れを最低限吸収(既存データが混ざってもSplitできるようにする)
Private Function NormalizeComma(ByVal s As String) As String
    s = Replace(s, ",", ",") '全角カンマ
    s = Replace(s, "、", ",") '読点
    NormalizeComma = s
End Function

コードの押さえどころ(重要ポイントだけ)

対象範囲は「必ず」シートを明示する

Set multiSelectRange = Me.Range("C2:C124")Meは「このシート自身」を指します。標準モジュールや別シートにコードがあると、意図しないシートのRangeを参照してしまい、Intersect判定がずれて動作しません。シートモジュールに置き、Me.Rangeで指定するのが安全です。

Application.Undoが必要な理由

Worksheet_Changeが発火した時点では、セルにはすでに「新しい値(New)」が入っています。そこで、Application.Undoで直前の状態に戻し、戻ったセル値を「旧値(Old)」として読み取ります。最後に改めて目的の値をTarget.Valueへセットし直す、という流れです。

部分一致の事故を避ける

単純にInStrで含有判定すると、例えば「A」と「AA」のように似た値があるときに誤判定しやすくなります。完成版では、いったんSplitで配列化し、トークン単位で完全一致(大文字小文字は無視)判定しています。

EnableEventsは「必ず」元に戻す

Application.EnableEvents = Falseにしないと、コードがセル値を書き換えた瞬間に再度Worksheet_Changeが走り、無限ループになります。一方で、エラーや途中終了で戻し忘れると、Excel全体でイベントが発火しなくなり「マクロが壊れた」ように見えます。終了処理で確実に復帰する構造が重要です。

動作イメージ(追記/削除の挙動)

操作セルの表示例内部で起きていること
最初に「りんご」を選ぶりんごOldが空なのでNewをそのままセット
次に「みかん」を選ぶりんご, みかんOldに含まれないので末尾に追記
もう一度「りんご」を選ぶみかんOldに含まれるので削除(トグル)

カスタマイズ例(現場で便利な調整)

区切り文字を変えたい

コード冒頭のDELIMを書き換えるだけです。日本語の一覧なら読点「、」にしたいケースもありますが、既存データとの互換性を考えるとカンマ+半角スペースが無難です。

DELIMの例見え方向いているケース
", "A, B, C最も一般的。CSVにも近く扱いやすい
"、"A、B、C日本語文として自然に見せたい
" / "A / B / C値の中にカンマが入り得る場合の回避策

重複を許したくない(常にユニーク化したい)

完成版は「含まれていれば削除」という仕様なので、結果的に重複は発生しにくいですが、過去データに重複が混ざることはあります。削除関数は一致項目をすべて取り除くため、再選択で重複が一掃されます。さらに厳密にユニーク化したいなら、追記前に既存リストを整形してから処理する方法が有効です。

選択数に上限を設けたい

例えば「最大3つまで」に制限したい場合は、追記する前に現在の要素数を数え、上限を超えるなら処理を中断します。

'追記の直前に入れる例(最大3件)
If UBound(Split(NormalizeComma(oldText), ",")) + 1 >= 3 Then
    '上限到達:新規追加しない(必要ならメッセージ表示など)
    Target.Value = oldText
    GoTo CleanUp
End If

並び順を常に整えたい(ソートしたい)

「入力者の選んだ順」を残したい場合はそのままでOKですが、検索・集計都合で並び順を固定したい場合もあります。並び替えまでやるとコードが長くなるので、運用上は次のどちらかが現実的です。

  • 表示は選択順のままにして、集計用に別セルで正規化(PowerQueryや関数で整形)
  • 選択肢が少ない場合は、VBAで配列をソートして再結合(追加実装)

トラブルシューティング(うまく動かない時の確認順)

チェック項目確認方法ありがちな落とし穴
マクロ有効ブックか拡張子が.xlsmになっているか.xlsxだとVBAを保存できない
マクロが有効になっているか起動時のセキュリティ警告で「コンテンツの有効化」を押したか社内ポリシーでブロックされていることもある
コードの場所が正しいか対象シートのコードモジュールに貼ったかThisWorkbookや標準モジュールではイベントが発火しない
対象範囲が合っているかMe.Range("C2:C124")が実際のセル範囲と一致しているか列や行がズレると一切処理されない
入力規則がリストになっているかセル→データの入力規則で「リスト」になっているかコピー・貼り付けで入力規則が外れていることがある
貼り付けで試していないかまずはドロップダウン選択で試す複数セル貼り付けは意図通りにならない
共同編集・Web版Excelではないかデスクトップ版Excelで開いているかExcel for the webはVBAが動作しない

実運用でハマりやすい注意点

Application.Undoが効かないケースがある

Application.Undoは「直前の操作」を戻す仕組みなので、操作によっては期待通りに戻らないことがあります。代表例は次のとおりです。

  • 複数セル同時貼り付け(Undo対象が広すぎる)
  • 別マクロが直前に実行されてUndo履歴が変わっている
  • 外部アドインや特殊な編集操作でUndo履歴が崩れる

このため、完成版では複数セル変更を対象外にしています。大量入力をしたい場合は、後処理で整形する運用(入力後に別マクロで分解・整形)も検討してください。

区切り文字がデータに含まれると破綻する

この方式は「セル内を区切り文字で分割して要素にする」ため、項目そのものにカンマが含まれると正しく分解できません。候補リストにカンマが含まれる可能性がある場合は、区切り文字を" / "などに変える、あるいは別列にID(数値)を保持して表示名は参照で出す、といった設計が安全です。

入力規則の候補の管理は「テーブル+名前定義」が強い

候補が増減する運用では、別シートの候補範囲をExcelテーブル(挿入→テーブル)にしておくと、行追加だけで範囲が伸びます。そのテーブル列を参照する名前付き範囲を入力規則に指定すれば、メンテナンスコストを大きく下げられます。

まとめ

Excelの入力規則(リスト)を複数選択にしたい場合、別シートに候補があること自体よりも、イベントコードの置き場所や構文ミス、EnableEventsの戻し忘れが原因で動かないケースがほとんどです。まずは「入力規則を名前付き範囲で安定化」し、「対象シートのコードモジュールに、事故りにくいWorksheet_Change」を入れることで、カンマ区切り追記と再選択削除(トグル)が実用レベルで動くようになります。

この記事を書いた人

実務の現場で詰まりがちなポイントを地図にするITブログ「IT trip」を運営。Windows/Office(Teams・Excel)からSQL、サーバ運用、ガジェットまで、再現性のある手順と“なぜそうなるか”を丁寧に解説します。読んだらすぐ試せること、そして迷った人の次の一歩が見えることを大切にしています。

コメント

コメントする

目次