ポイント
Iterator クラスでは、もれなく反復するために、最低次の機能があればよい。
- HasNext 関数を用意し、次の要素があるかを判定できる機能
- Next 関数を用意し、次の要素を取得できる機能
前提・要望
- エクセルの Sheet1 に表がある。
- 1行目はヘッダー、2行目以降がデータ
- 2列目には何種類かのデータが入っている。
- 2列目でフィルタをかける。
- フィルタの選択肢はいくつかあり、1つずつ選択してフィルタ表示するのをすべての選択肢で行う。
- 以上のことを、VBA のコードで実現したい。
エクセルデータ
「Sheet1」という名前で、次のデータを対象といたしました。1行目もエクセルシートに記載しております。
| No | 都道府県 |
|---|---|
| 1 | 北海道 |
| 2 | 青森県 |
| 3 | 岩手県 |
| 4 | 秋田県 |
| 5 | 岩手県 |
| 6 | 秋田県 |
| 7 | 秋田県 |
| 8 | 青森県 |
| 9 | 岩手県 |
| 10 | 秋田県 |
これが Iterator への入力データとなります。
2 列目には都道府県が4種類、「北海道 」「青森県」「岩手県」「秋田県」、入っております。
この2列目でフィルタし、4種類を1つずつ選んでフィルタリング表示するのを VBA コードで実現したいですの♪
VBA コード
まずは、ThisWorkbook.cls です。ここで Iterator をインスタンス化し、使用しております。
Option Explicit
' エクセル列の選択肢を順番に選んでフィルタを避けたい。ループするときにはイテレータを使いたい。
Sub test()
Dim fi As FilterIterator
Set fi = New FilterIterator
Call fi.Init("Sheet1", 2)
' フィルタ対象があるかぎりループ
Dim filtered As Range
Dim i As Long
Dim item As Variant
Do While (fi.HasNext)
' フィルタをかけ、Range を取得
' ここにデバッグポイントを置き、エクセルシートの様子をみると状況がよく分かる。
Set filtered = fi.NextItem
For Each item In filtered
Debug.Print item
Next item
Loop
' 後処理
Selection.AutoFilter
End Sub
つづいて、Iterator クラス本体です。
Collection の要素数を調べたり、Collection の要素を取得したりすることで、HasNext 関数と NextFilter 関数の機能を実現しております。
Collection といえば要素数を意識しなくとも For Each でもれなく要素を取得できることが特徴ですけれども、For Each は使用しておりませんの♪
Option Explicit
' 対象ワークシート
Private mobjSheet As Worksheet
' フィルタ対象列番号
Private mlngCol As Long
' データ範囲の最大行数
Private mlngMaxRow As Long
' データ範囲の最大列数
Private mlngMaxCol As Long
' 要素位置
Private mlngIndex As Long
' 重複なしのフィルタ選択肢
Private mobjList As Collection
''' <summary>
''' 初期化処理を実行します。
''' </summary>
''' <param name="strSheet">操作対象ワークシート</param>
''' <param name="lngCol">フィルタ対象列番号</param>
Public Sub Init(ByVal strSheet As Worksheet, ByVal lngCol As Long)
Set mobjSheet = Worksheet(strSheet)
mlngCol = lngCol
mlngMaxRow = mobjSheet.mobjSheetCells(Rows.Count, 1).End(xlUp).Row
mlngMaxCol = mobjSheet.mobjSheetCells(1, Columns.Count, 1).End(xlToLeft).Column
mlngIndex = 1
Call SetNoDuplicateCollection
End Function
''' <summary>
''' 次要素があるかどうかを判定します。
''' </summary>
''' <returns>次要素がある場合は True、ない場合 False</returns>
Public Function HasNext() As Boolean
Dim blnRes As Boolean
If mobjList.Count > mlngIndex - 1 Then
blnRes = True
Else
blnRes = False
End If
HasNext = blnRes
End Function
''' <summary>
''' 次要素を取得します。
''' </summary>
''' <returns>フィルタリングされた Range</returns>
Public Function NextFilter() As Range
Dim strFilterKey As String
strFilterKey = mobjList.item(mlngIndex)
' フィルタをかけて表示
mobjSheet.Range("A1").AutoFilter Field:=mlngCol, Criterial:=strFilterKey
' 表示されているデータを取得(ヘッダ行は除く)
Dim objTarget As Range
Set NextFilter = Range(Cells(2, 1), Cells(mlngMaxRow, mlngMaxCol)) _
.SpecialCells(xlCellTypeVisible)
mlngIndex = mlngIndex + 1
End Function
' 重複なしの Collection を生成してプロパティに設定
Private Sub SetNoDuplicateCollection()
' 対象シートを設定
mobjSheet.Select
' 重複なしとして絞り込む対象列を取得(ヘッダ行は除く)
Dim objTarget As Range
Set objTarget = Range(Cells(2, mlngCol), Cells(mlngMaxRow, mlngCol))
' 重複を回避して、目的の変数を生成
' 重複買い費用の変数
Dim objNoDuplicate As Object
Set objNoDuplicate = CreateObject("Scripting.Dictionary")
Dim objItem As Range
' 返却用の変数
Dim objResult As Collection
Set objResult = New Collection
' 重複位を判定し、初めてであれば返却値に追加'
For Each objItem In objTarget
If Not objItem = Empty Then
If Not objNoDuplicate.Exists(objItem.Value) Then
objNoDuplicate.Add Key:=objItem.Value, item:=Null
objResult.Add item:=objItem.Value
End If
End If
Next objItem
Set mobjList = objResult
End Sub
結果
フィルタした状態を Range で取得して、Debug.Print で出力いたしました。
イミディエイトウィンドウの表示は次のようになりました。
1 北海道 2 青森県 8 青森県 3 岩手県 5 岩手県 9 岩手県 4 秋田県 6 秋田県 7 秋田県 10 秋田県
2列目の都道府県の種類ごとにフィルタリングされた様子がよくわかります♪成功ですの♪
なお、デバッグポイントを設定して、フィルタが順番にかかる様子を見ると、もっと状態をはっきりと把握できます!おすすめですの♪
おわりに
VBA にはイテレータが備わっていないようですの><。ですけれども、クラスは使える。。。
であれば、Iterator パターンのデザインパターンを作ることができるのではないかしら!そう思ったのがきっかけでしたわ。
幸い、sawadybomb さまが次のページで詳しく例を示してくださいました!
これは、『増補改訂版Java言語で学ぶデザインパターン入門』著: 結城浩、の内容をとてもよく実現しております。後から本を読み返してそのことに気が付きましたの♪
今回、わたくしたちのコードでは、インターフェースや Aggregater は作成いたしませんでした。
- 他でも Iterator を使う箇所がなかったということ、
- エクセルシートにフィルタをかけることが主目的で、フィルタ済みの Range は実は要らなかった。つまり NextFilter の返却値は不要で、Aggregator を作る必要がなかったということ、
- インターフェースや Aggregater に分けると抽象度が上がって理解が難しくなった(わたくしたちの頭が悪いのですの><)といこうこと、
といった理由からですわ。
それでも、Iterator パターンに必要なのは
- HasNext 関数での次があるかの判定
- Next 関数での次の要素の取得
の 2 つと存じますので、これに絞って実装いたしました♪
以上です。

「【Excel VBA】イテレータを作って、列の選択肢を順番に選んでフィルタをかけるコード」への1件の返信
[…] 【Excel VBA】イテレータを作って、列の選択肢を順番に選んでフィルタをかけるコード | oki2a24 […]