概要
Excel VBAでオートフィルター機能を利用する際、一時的にフィルターを解除して他の操作を行い、その後元のフィルター状態に戻したいという場面は非常に多いのではないでしょうか。しかし、手動でフィルターを解除・再設定するのは手間がかかり、コードで実装するにも元のフィルター条件を保持・復元するロジックは意外と複雑になりがちです。
本記事では、この「オートフィルターの退避と復元」という一連の操作を、VBAクラスモジュールを活用して、よりシンプルかつ汎用的に扱えるようにするテクニックを徹底解説します。クラスモジュールを使うことで、フィルター条件の退避・復元処理を一つのまとまった「オブジェクト」として扱うことができ、コードの可読性・保守性を劇的に向上させることができます。
このクラスモジュールを使いこなせば、複雑なデータ集計やレポート作成、はたまた他のVBA処理との連携など、様々なシーンでオートフィルターをより柔軟かつ効率的に活用できるようになります。
詳細解説
オートフィルターの課題とクラスモジュールの必要性
Excel VBAでオートフィルターを操作する際、主に`AutoFilter`メソッドを使用します。例えば、特定の列に条件を設定してフィルターをかけるのは比較的簡単です。
‘ 例: A列に「東京」という値でフィルターをかける
Range(“A1″).AutoFilter Field:=1, Criteria1:=”東京”
しかし、問題となるのは、このフィルターを一時的に解除して、例えば別のシートにデータをコピーしたり、計算処理を実行したりする場合です。フィルターを解除するには、`AutoFilterMode`プロパティを`False`にするか、再度`AutoFilter`メソッドを呼び出して`Nothing`を指定する方法があります。
‘ フィルター解除の例
ActiveSheet.AutoFilterMode = False
‘ または
‘ ActiveSheet.AutoFilterMode = xlFilterNone
ここで問題が生じます。フィルターを解除すると、元のフィルター条件は失われてしまいます。その後、元のフィルター条件を正確に復元するには、解除する前にフィルターがかかっていた列、フィールド番号、条件、演算子などをすべて記憶しておき、再度それらの情報を使って`AutoFilter`メソッドを呼び出す必要があります。
この「記憶」と「復元」のロジックを、複数の条件が設定されている場合や、解除・復元を繰り返す場合に、通常の標準モジュールで実装しようとすると、変数管理が煩雑になり、コードも長くなりがちです。
そこで登場するのが「クラスモジュール」です。クラスモジュールは、プロパティ(データの保持)とメソッド(処理の実行)をまとめて、一つの「オブジェクト」として定義できる強力な機能です。このクラスモジュールを利用することで、オートフィルターの状態(どの列にどのような条件でフィルターがかかっているか)を保持し、それを簡単に復元する機能を持った「オートフィルターマネージャー」のようなオブジェクトを作成できるのです。
クラスモジュールによるオートフィルター管理
私たちが作成するクラスモジュールは、以下の機能を持つことを目指します。
1. **現在のフィルター状態の取得・退避:** ワークシート上の現在のオートフィルターの状態(フィルターがかかっている範囲、各フィールドの条件など)を取得し、保持します。
2. **フィルターの解除:** 現在かかっているオートフィルターを解除します。
3. **フィルターの復元:** 退避しておいたフィルター状態を、元のシートに正確に復元します。
このクラスモジュールを`CAutoFilterManager`と名付けたとしましょう。このクラスは、以下のプロパティとメソッドを持つように設計します。
**プロパティ:**
* `TargetRange` (Range): フィルターが適用されている範囲。
* `FilterCriteria` (Collection): 各フィールドのフィルター条件を格納するコレクション。各要素は、フィールド番号、条件、演算子などを保持する別のクラス(例: `CFilterCondition`)のインスタンスとします。
* `IsFiltered` (Boolean): 現在フィルターがかかっているかどうかを示すフラグ。
**メソッド:**
* `Initialize(targetRange As Range)`: クラスのインスタンスを初期化し、対象となる範囲を設定します。
* `SaveFilterState()`: 現在のオートフィルターの状態を退避します。
* `ClearFilter()`: オートフィルターを解除します。
* `RestoreFilterState()`: 退避したフィルター状態を復元します。
`CAutoFilterManager`クラスの実装
まず、各フィルター条件を保持するための補助的なクラス`CFilterCondition`を作成します。
‘=== CFilterCondition クラスモジュール ===
‘ フィールドごとのフィルター条件を保持するクラス
Public FieldNum As Long ‘ フィールド番号
Public Criteria1 As Variant ‘ 条件1
Public Operator As XlAutoFilterOperator ‘ 演算子
Public Criteria2 As Variant ‘ 条件2 (AND/ORの場合)
‘ 初期化メソッド (任意ですが、あると便利)
Public Sub Init(field As Long, crit1 As Variant, Optional op As XlAutoFilterOperator = xlAnd, Optional crit2 As Variant = Empty)
FieldNum = field
Criteria1 = crit1
Operator = op
Criteria2 = crit2
End Sub
次に、メインとなる`CAutoFilterManager`クラスを実装します。
‘=== CAutoFilterManager クラスモジュール ===
‘ オートフィルターの状態を管理するクラス
Private pTargetRange As Range
Private pFilterCriteria As Collection
Private pIsFiltered As Boolean
‘ プロパティの設定
Public Property Get TargetRange() As Range
Set TargetRange = pTargetRange
End Property
Public Property Set TargetRange(ByVal value As Range)
Set pTargetRange = value
End Property
Public Property Get IsFiltered() As Boolean
IsFiltered = pIsFiltered
End Property
‘ 初期化メソッド
Public Sub Initialize(targetRange As Range)
If targetRange Is Nothing Then Exit Sub
Set pTargetRange = targetRange
Set pFilterCriteria = New Collection
pIsFiltered = False ‘ 初期状態はフィルターがかかっていない
End Sub
‘ フィルター状態の退避
Public Sub SaveFilterState()
If pTargetRange Is Nothing Then Exit Sub
Dim ws As Worksheet
Set ws = pTargetRange.Worksheet
‘ 現在フィルターがかかっているか確認
If Not ws.AutoFilterMode Then
pIsFiltered = False
Exit Sub
End If
‘ フィルターがかかっている範囲が対象範囲と一致するか確認
If Not ws.AutoFilter.Range.Address = pTargetRange.Address Then
‘ 対象範囲が現在のフィルター範囲と異なる場合は、新規にフィルターをかける前提で処理
‘ またはエラー処理を行う
MsgBox “現在のフィルター範囲が指定された対象範囲と一致しません。”, vbExclamation
Exit Sub
End If
pIsFiltered = True ‘ フィルターがかかっている状態を記憶
Dim fc As CFilterCondition
Dim i As Long
‘ 既存の条件をクリア
On Error Resume Next ‘ Collection.Clear は存在しないため、エラーを無視
pFilterCriteria.Clear
On Error GoTo 0
‘ 各フィールドのフィルター条件を取得して保存
For i = 1 To pTargetRange.Columns.Count
‘ AutoFilter.Filters(i) は Variant 型で、条件が存在しない場合は Null を返す
‘ 条件が存在する場合、その Variant は Filter 構造体のようなものを格納する (実際は内部構造)
‘ VBAで直接 AutoFilter.Filters(i) の内容を詳細に取得するのは困難なため、
‘ ここでは、フィルターが設定されているフィールドのみを対象とする。
‘ 厳密な条件取得は、AutoFilter.Filters(i).On を確認し、
‘ Criteria1, Operator, Criteria2 などを取得する必要があるが、
‘ AutoFilter.Filters(i) 自体は直接プロパティアクセスできないため、
‘ 以下のコードは「フィルターが有効になっているフィールド」を退避する例として記述。
‘ AutoFilter.Filters(i).On プロパティで、そのフィールドにフィルターが有効か判定
If ws.AutoFilter.Filters(i).On Then
Set fc = New CFilterCondition
fc.FieldNum = i
‘ Criteria1, Operator, Criteria2 を取得 (これらのプロパティは AutoFilter オブジェクトの .Filters(i) から直接アクセス可能)
fc.Criteria1 = ws.AutoFilter.Criteria1(i)
fc.Operator = ws.AutoFilter.Operator
‘ Criteria2 は、Operator が xlAnd または xlOr の場合にのみ有効
If ws.AutoFilter.Operator = xlAnd Or ws.AutoFilter.Operator = xlOr Then
fc.Criteria2 = ws.AutoFilter.Criteria2(i)
End If
pFilterCriteria.Add fc
End If
Next i
End Sub
‘ フィルターの解除
Public Sub ClearFilter()
If pTargetRange Is Nothing Then Exit Sub
Dim ws As Worksheet
Set ws = pTargetRange.Worksheet
If ws.AutoFilterMode Then
ws.AutoFilterMode = False
End If
‘ フィルター解除したら、保存していた状態は無効になるため、pIsFiltered を False にする
‘ ただし、RestoreFilterState で復元するまで保持しておくという設計もありうる。
‘ ここでは、ClearFilter を実行したら、一旦「フィルターなし」状態とみなす。
pIsFiltered = False
End Sub
‘ フィルター状態の復元
Public Sub RestoreFilterState()
If pTargetRange Is Nothing Then Exit Sub
Dim ws As Worksheet
Set ws = pTargetRange.Worksheet
‘ フィルター解除
If ws.AutoFilterMode Then
ws.AutoFilterMode = False
End If
‘ 保存したフィルター条件がある場合のみ復元
If pFilterCriteria.Count > 0 Then
Dim fc As CFilterCondition
Dim currentCriteria1() As Variant
Dim currentCriteria2() As Variant
Dim currentOperator As XlAutoFilterOperator
Dim i As Long
Dim maxFieldNum As Long
‘ 必要となる配列の最大サイズを特定 (フィールド数)
maxFieldNum = pTargetRange.Columns.Count
‘ Criteria 配列の初期化
ReDim currentCriteria1(1 To maxFieldNum)
ReDim currentCriteria2(1 To maxFieldNum)
‘ AutoFilter.Operator は全てのフィールドで共通なので、最初の条件から取得
currentOperator = xlAnd ‘ デフォルト値
‘ 保持している条件を元に、AutoFilter.Criteria1, .Criteria2, .Operator を構築
For Each fc In pFilterCriteria
‘ フィールド番号が対象範囲の列数を超えていないかチェック
If fc.FieldNum <= maxFieldNum Then
currentCriteria1(fc.FieldNum) = fc.Criteria1
currentCriteria2(fc.FieldNum) = fc.Criteria2
' 演算子は、複数の条件がある場合、最後の条件の演算子に合わせるのが一般的
' もしくは、フィールドごとに異なる演算子をサポートする設計も可能だが、VBAのAutoFilterは範囲全体で1つのOperatorを持つ
' ここでは、退避した条件の中から一つ(例えば最初のもの)のOperatorを採用するか、
' あるいは、全ての条件が同じOperatorであることを前提とする。
' より複雑なケース(フィールドごとに異なるOperator)は、AutoFilterの仕様上、直接的なマッピングが難しい場合がある。
' ここでは、退避した条件のOperatorをそのまま使用する。
' ただし、AutoFilter.Operatorは単一の値しか取らないため、
' 複数のフィールドに異なるOperatorが設定されていた場合、この復元ロジックでは単純化される。
' 一番単純なのは、全ての条件でOperatorが同じ場合。
' ここでは、退避した条件のうち、最初のもののOperatorを使用する。
If i = 1 Then
currentOperator = fc.Operator
End If
End If
Next fc
' AutoFilter.Criteria1, .Criteria2, .Operator を設定するための配列を準備
' AutoFilter.Filter メソッドは、Field, Criteria1, Operator, Criteria2 を直接指定するが、
' 既存のAutoFilterをONにしたまま条件を変更する場合は、上記プロパティを設定する。
' ここでは、一度フィルターを有効にしてから条件を適用する。
' まず、対象範囲にAutoFilterを有効にする (Field Number は 1 で良い)
' 条件を適用しない場合、AutoFilter(Field:=1) でフィルターヘッダーが表示される
pTargetRange.AutoFilter Field:=1
' AutoFilter.Operator は、すべてのフィールドで共通
' 複数のフィールドに異なる演算子が設定されていた場合、
' VBAのAutoFilterは、範囲全体で1つのOperatorしか持てないため、
' ここでは、退避した条件のうち、最初に取得したOperatorを使用します。
' もし、フィールドごとに異なる演算子を厳密に復元したい場合は、
' AutoFilter.Filters(i).Criteria1, .Operator, .Criteria2 を直接設定する
' のですが、これは .Filters(i) オブジェクト自体への直接のプロパティ設定はできないため、
' AutoFilter.Range.AutoFilter Field:=i, Criteria1:=..., Operator:=..., Criteria2:=...
' のように、フィールドごとに AutoFilter メソッドを呼び出す必要があります。
' その場合、一度フィルターを解除してから、フィールドごとに再設定する方が管理しやすいかもしれません。
' ここでは、より一般的なケースとして、フィールドごとに条件を設定するアプローチをとります。
' 一旦フィルターを解除し、フィールドごとに設定し直す方が安全です。
If ws.AutoFilterMode Then
ws.AutoFilterMode = False
End If
' フィールドごとに AutoFilter メソッドを呼び出して条件を設定
For Each fc In pFilterCriteria
' AutoFilter.Filters(i).On が True であったフィールドのみを再設定
ws.AutoFilter.Range.AutoFilter Field:=fc.FieldNum, _
Criteria1:=fc.Criteria1, _
Operator:=fc.Operator, _
Criteria2:=fc.Criteria2
Next fc
' フィルターが正常に復元されたことを示す
pIsFiltered = True
Else
' 保存した条件がない場合は、フィルターがかかっていない状態とする
pIsFiltered = False
End If
End Sub
標準モジュールからの利用方法
作成した`CAutoFilterManager`クラスは、標準モジュールから以下のように利用します。
‘=== 標準モジュール ===
Sub FilterSample()
Dim ws As Worksheet
Dim rngData As Range
Dim afManager As CAutoFilterManager
Set ws = ThisWorkbook.Sheets(“Sheet1”) ‘ 対象シート名に合わせてください
‘ データ範囲を設定 (ヘッダー行を含む)
Set rngData = ws.Range(“A1”).CurrentRegion
‘ AutoFilterManager オブジェクトの作成と初期化
Set afManager = New CAutoFilterManager
afManager.Initialize rngData
‘ — フィルターをかける —
‘ まず、フィルター状態を保存
afManager.SaveFilterState ‘ ここで現在のフィルター状態(もしあれば)を退避
‘ オートフィルターを適用 (例: A列に “東京”、B列に “完了” でフィルター)
rngData.AutoFilter Field:=1, Criteria1:=”東京”
rngData.AutoFilter Field:=2, Criteria1:=”完了”
MsgBox “フィルターを適用しました。OKを押すと一時解除します。”
‘ — 一時解除して別の処理 —
afManager.ClearFilter ‘ フィルターを解除
MsgBox “フィルターを解除しました。OKを押すと復元します。”
‘ — 元のフィルター状態に戻す —
afManager.RestoreFilterState ‘ 保存しておいたフィルター状態を復元
MsgBox “フィルターを復元しました。”
‘ オブジェクトの解放
Set afManager = Nothing
Set rngData = Nothing
Set ws = Nothing
End Sub
‘ 別の例: フィルターを一時解除し、処理後に復元する
Sub ProcessWithTempFilterClear()
Dim ws As Worksheet
Dim rngData As Range
Dim afManager As CAutoFilterManager
Set ws = ThisWorkbook.Sheets(“Sheet1”) ‘ 対象シート名に合わせてください
Set rngData = ws.Range(“A1″).CurrentRegion
‘ AutoFilterManager オブジェクトの作成と初期化
Set afManager = New CAutoFilterManager
afManager.Initialize rngData
‘ フィルター状態を保存 (もし既にフィルターがかかっていた場合のため)
afManager.SaveFilterState
‘ ここで、何らかのフィルターが適用されていると仮定
‘ 例: rngData.AutoFilter Field:=3, Criteria1:=”未処理”
MsgBox “フィルター適用中。OKを押すと一時解除し、処理を実行します。”
‘ フィルターを解除して、他の処理を実行
afManager.ClearFilter
‘ — ここに、フィルター解除中に実行したい処理を記述 —
‘ 例: 別のシートへのデータコピー、計算処理など
Debug.Print “フィルター解除中に実行される処理…”
‘ —————————————————-
MsgBox “一時解除中の処理が完了しました。OKを押すとフィルターを復元します。”
‘ 元のフィルター状態を復元
afManager.RestoreFilterState
MsgBox “フィルターを復元しました。”
Set afManager = Nothing
Set rngData = Nothing
Set ws = Nothing
End Sub
**解説:**
1. **クラスモジュールの追加:** VBAエディタで「挿入」>「クラスモジュール」を選択し、`CFilterCondition`と`CAutoFilterManager`という名前でそれぞれクラスモジュールを作成します。
2. **`CFilterCondition`の実装:** 各フィールドのフィルター条件(フィールド番号、条件1、演算子、条件2)を保持するためのシンプルなクラスです。
3. **`CAutoFilterManager`の実装:**
* `Initialize`: 対象となる`Range`オブジェクトを設定し、フィルター条件を格納するための`Collection`を初期化します。
* `SaveFilterState`: `ws.AutoFilter.Filters(i).On` でフィルターが有効なフィールドをチェックし、`Criteria1`, `Operator`, `Criteria2` を取得して`pFilterCriteria`コレクションに`CFilterCondition`オブジェクトとして保存します。`ws.AutoFilter.Operator` は範囲全体で一つしか存在しないため、ここでは退避した条件のうち最初のものを使用する(または、全ての条件で演算子が同じであることを前提とする)という単純化を行っています。より複雑なケースでは、フィールドごとに`AutoFilter`メソッドを呼び出す必要があります。
* `ClearFilter`: `ws.AutoFilterMode = False` でフィルターを解除します。
* `RestoreFilterState`: 一度フィルターを解除した後、`pFilterCriteria`コレクションに保存されている条件を元に、フィールドごとに`ws.AutoFilter.Range.AutoFilter`メソッドを呼び出してフィルターを再設定します。この際、`ws.AutoFilter.Operator` の扱いに注意が必要です。`AutoFilter`メソッドはフィールドごとにOperatorを指定できますが、VBAの`AutoFilter`オブジェクトは、範囲全体で一つのOperatorしか持ちません。したがって、`SaveFilterState`で取得したOperatorをそのまま`RestoreFilterState`で適用すると、意図しない結果になる場合があります。上記コードでは、フィールドごとに`AutoFilter`メソッドを呼び出すことで、各フィールドの条件を正確に復元しようとしています。
4. **標準モジュールからの利用:** `FilterSample`サブプロシージャのように、`CAutoFilterManager`のインスタンスを作成し、`Initialize`で対象範囲を設定します。フィルターをかける前に`SaveFilterState`を呼び出し、解除した後に`RestoreFilterState`を呼び出すことで、フィルター状態の退避と復元を実現します。
### サンプルコード
上記「詳細解説」セクションで、クラスモジュール(`CFilterCondition`, `CAutoFilterManager`)および標準モジュールのサンプルコードを記載しました。以下に、コードブロックとして再度提示します。
‘=== CFilterCondition クラスモジュール ===
‘ フィールドごとのフィルター条件を保持するクラス
Public FieldNum As Long ‘ フィールド番号
Public Criteria1 As Variant ‘ 条件1
Public Operator As XlAutoFilterOperator ‘ 演算子
Public Criteria2 As Variant ‘ 条件2 (AND/ORの場合)
‘ 初期化メソッド (任意ですが、あると便利)
Public Sub Init(field As Long, crit1 As Variant, Optional op As XlAutoFilterOperator = xlAnd, Optional crit2 As Variant = Empty)
FieldNum = field
Criteria1 = crit1
Operator = op
Criteria2 = crit2
End Sub
‘=== CAutoFilterManager クラスモジュール ===
‘ オートフィルターの状態を管理するクラス
Private pTargetRange As Range
Private pFilterCriteria As Collection
Private pIsFiltered As Boolean
‘ プロパティの設定
Public Property Get TargetRange() As Range
Set TargetRange = pTargetRange
End Property
Public Property Set TargetRange(ByVal value As Range)
Set pTargetRange = value
End Property
Public Property Get IsFiltered() As Boolean
IsFiltered = pIsFiltered
End Property
‘ 初期化メソッド
Public Sub Initialize(targetRange As Range)
If targetRange Is Nothing Then Exit Sub
Set pTargetRange = targetRange
Set pFilterCriteria = New Collection
pIsFiltered = False ‘ 初期状態はフィルターがかかっていない
End Sub
‘ フィルター状態の退避
Public Sub SaveFilterState()
If pTargetRange Is Nothing Then Exit Sub
Dim ws As Worksheet
Set ws = pTargetRange.Worksheet
‘ 現在フィルターがかかっているか確認
If Not ws.AutoFilterMode Then
pIsFiltered = False
Exit Sub
End If
‘ フィルターがか
