カテゴリー
Microsoft

【Excel VBA】ワークシートに入力したマスタデータを VBA から簡単に利用できるようにしたクラスのコード!

追記。改善版を作りました♪こちらのコードの方がすっきりとしております。


エクセルワークシートにマスタデータを表現いたしました。

これを、VBA に取り込んで、VBA の他のコードから呼び出して検索して値を返すクラスを作りたいですの!

そのようにすることで、エクセルのワークシートから値を取得して何かの関数で絞り込んで、、、などと意識することなくマスタデータを取り出せるようにいたします♪

ポイント

  • マスタデータをユーザ定義型の Collection で扱おうと考えたが使えなかった
  • そこでクラス内にマスタの項目をプロパティとして定義する。そして自分自身のクラスをインスタンス化してマスタデータをプロパティに設定することでデータを扱えるようにする。
  • 自分自身のクラスには、データ構造以外に関数も含まれる。
  • 本来であれば構造を扱うクラス、処理を扱うクラスに分けるべき。
  • しかし、VBA にはクラス宣言のコードがないため、1つのファイルに両方を持たせるためにこれらを混在させた。

エクセルデータ

次のエクセル表をマスタデータとして扱うことといたします。

  • シート名: Sheet1
  • 1行目もエクセルに含める。
  • A1 セルから始まる。
No 曜日 時刻(以降) 時刻(未満) 料金
1 月,火,水,木,金 9:00:00 12:00:00 1000
2 月,火,水,木,金 13:00:00 18:00:00 2000
3 月,火,水,木,金 18:00:00 21:00:00 3000
4 9:00:00 12:00:00 1200
5 13:00:00 18:00:00 2200
6 18:00:00 21:00:00 3200
7 9:00:00 12:00:00 1500
8 13:00:00 18:00:00 2500
9 18:00:00 21:00:00 3500

VBA コード

  • マスタデータを、mcolReaded と mcolFiltered の 2 つのコレクションに保持させている。
  • mcolReaded にはエクセルから読み込んだデータをそのまま変更しないで保持させている。
  • mcolFiltered には条件で絞り込んで残ったデータを残している。
  • 何度でも絞り込めるように、結果の取得が終わったら mcolFiltered に mcolReaded をコピーしてリセットしている。
  • Init メソッドでやっていること
    1. ワークシートからデータを読み込み、
    2. 行ごとにループするときに自分自身のクラスをインスタンス化してプロパティに対応するエクセルデータを設定し、
    3. インスタンスをコレクションに追加している。
  • Filter メソッドでの判定について
    • 列項目分だけ判定しているため、増減があれば If の判定内容も合わせるようにコードを書き直す必要がある。
    • If 内のすべての要素が評価されるため、False となった時点で判定を止めたい場合は If の中身ごとメソッドに切り出せば良さそう。
    • 曜日は InStr メソッドで、含まれているかどうかを判定
    • 時刻は比較演算子で大小判定
      Option Explicit
      
      Private mcolReaded As Collection
      Private mcolFiltered As Collection
      
      ' 自身のクラスをモデルとして扱いたい。
      ' インスタンス化したときに値を設定できるように、Public に設定
      Public mlngNo As Long
      Public mstrWeekday As String
      Public mdtmFrom As Date
      Public mdtmTo As Date
      Public mlngPrice As Long
      
      ''' <summary>
      ''' コンストラクタ
      ''' </summary>
      Private Sub Class_Initialize()
        Set mcolReaded = New Collection
        Set mcolFiltered = New Collection
      End Sub
      
      ''' <summary>
      ''' 初期化処理を実行します。
      ''' </summary>
      ''' <param name="strTartgetWorksheet">捜査対象ワークシート</param>
      Public Sub Init(ByVal strTartgetWorksheet As String)
        ' 変数をセット
        Dim objSheet As Worksheet: Set objSheet = Worksheets(strTartgetWorksheet)
        With objSheet
          ' 下端の行番号、右端の列番号を取得
          Dim lngMaxRow As Long: lngMaxRow = .Cells(Rows.Count, 1).End(xlUp).Row
          Dim lngMaxCol As Long: lngMaxCol = .Cells(1, Columns.Count).End(xlToLeft).Column
          ' マスタデータ部分を取得。1行目はヘッダーのため無視
          Dim objMaster As Range
          Set objMaster = .Range(.Cells(2, 1), Cells(lngMaxRow, lngMaxCol))
        End With
        ' マスタデータのクラスコレクション生成
        Dim i As Long
        For i = 1 To objMaster.Rows.Count
          Dim udtRow As PriceTable: Set udtRow = New PriceTable
          With udtRow
            .mlngNo = objMaster(i, 1)
            .mstrWeekday = objMaster(i, 2)
            .mdtmFrom = objMaster(i, 3)
            .mdtmTo = objMaster(i, 4)
            .mlngPrice = objMaster(i, 5)
          End With
          Call mcolReaded.Add(udtRow)
        Next i
        ' フィルタ対象用にデータをコピー
        Set mcolFiltered = mcolReaded
      End Sub
      
      ''' <summary>
      ''' 引数を条件として絞り込んだ料金を返却します。
      ''' 結果が複数ある場合は、最初の料金を返却します。
      ''' 絞込結果が 0 件の場合は、0 を返却します。
      ''' </summary>
      ''' <param name="strWeekday">曜日</param>
      ''' <param name="dtmTime">時刻</param>
      ''' <returns>料金</returns>
      Public Function GetPrice( _
        ByVal strWeekday As String, _
        ByVal dtmTime As Date) As Long
      
        Call Filter(strWeekday, dtmTime)
      
        GetPrice = GetPriceAndReset
      End Function
      
      ''' <summary>
      ''' 絞込処理を実行します。
      ''' </summary>
      ''' <param name="strWeekday">曜日</param>
      ''' <param name="dtmTime">時刻</param>
      Private Sub Filter( _
        ByVal strWeekday As String, _
        ByVal dtmTime As Date)
      
        Dim colResults As Collection
        Set colResults = New Collection
      
        Dim target As PriceTable
        For Each target In mcolFiltered
          If Instr(target.mstrWeekday, strWeekday) > 0 And _
            (target.mdtmFrom <= dtmTime And target.mdtmTo > dtmTime) Then
            Call colResults.Add(target)
          End If
        Next target
      
        Set mcolFiltered = colResults
      End Sub
      
      ''' <summary>
      ''' 絞り込まれた金額を返却します。
      ''' その後、メモリに読み込んだマスタの状態をフィルタ前に戻します。
      ''' 結果が複数ある場合は、最初の料金を返却します。
      ''' 絞込結果が0件の場合は、0を返却します。
      ''' <returns>料金</returns>
      Private Function GetPriceAndReset() As Long
        GetPriceAndReset = 0
      
        If mcolFiltered.Count > 0 Then
          GetPriceAndReset = mcolFiltered(1).mlngPrice
        End If
      
        ' マスタデータのコレクションをリセット
        Set mcolFiltered = mcolReaded
      End Function
      
      Public Function ToString() As String
        Dim s As String
        Dim v As PriceTable
        For Each v In mcolFiltered
          s = s & _
          v.mlngNo _
          & ", " & v.mstrWeekday _
          & ", " & v.mdtmFrom _
          & ", " & v.mdtmTo _
          & ", " & v.mlngPrice & vbCrLf
        Next v
        ToString = s
      End Function
      

動きの確認用コード

Option Explicit

Public Sub Test()
  Dim pt As PriceTable
  Set pt = New PriceTable
  Call pt.Init("Sheet1")

  Debug.Print pt.ToString

  Debug.Print "予想: 月、21:00 ⇒ 検索結果無しで0円。結果: " & pt.GetPrice("月", #9:00:00 PM#) & "円"
  Debug.Print "予想: 月、10:15 ⇒ 1000円。結果: " & pt.GetPrice("月", #10:15:00 AM#) & "円"
  Debug.Print "予想: 火、13:00 ⇒ 2000円。結果: " & pt.GetPrice("火", #1:00:00 PM#) & "円"
  Debug.Print "予想: 火、12:00 ⇒ 2000円。結果: " & pt.GetPrice("火", #1:00:00 PM#) & "円"
  Debug.Print "予想: 水、18:00 ⇒ 3000円。結果: " & pt.GetPrice("水", #6:00:00 PM#) & "円"
  Debug.Print "予想: 木、9:00 ⇒ 1000円。結果: " & pt.GetPrice("木", #9:00:00 AM#) & "円"
  Debug.Print "予想: 金、17:59 ⇒ 2000円。結果: " & pt.GetPrice("金", #5:59:59 PM#) & "円"
  Debug.Print "予想: 土、20:59 ⇒ 3200円。結果: " & pt.GetPrice("土", #8:59:59 PM#) & "円"
  Debug.Print "予想: 土、11:59 ⇒ 1500円。結果: " & pt.GetPrice("日", #11:59:59 AM#) & "円"
End Sub

結果

1, 月,火,水,木,金, 9:00:00, 12:00:00, 1000
2, 月,火,水,木,金, 13:00:00, 18:00:00, 2000
3, 月,火,水,木,金, 18:00:00, 21:00:00, 3000
4, 土, 9:00:00, 12:00:00, 1200
5, 土, 13:00:00, 18:00:00, 2200
6, 土, 18:00:00, 21:00:00, 3200
7, 日, 9:00:00, 12:00:00, 1500
8, 日, 13:00:00, 18:00:00, 2500
9, 日, 18:00:00, 21:00:00, 3500

予想: 月、21:00 ⇒ 検索結果無しで0円。結果: 0円
予想: 月、10:15 ⇒ 1000円。結果: 1000円
予想: 火、12:00 ⇒ 2000円。結果: 2000円
予想: 水、18:00 ⇒ 3000円。結果: 3000円
予想: 木、9:00 ⇒ 1000円。結果: 1000円
予想: 金、17:59 ⇒ 2000円。結果: 2000円
予想: 土、20:59 ⇒ 3200円。結果: 3200円
予想: 日、11:59 ⇒ 1500円。結果: 1500円

ユーザ定義型が使えないとわかるまで調べたことまとめ

ユーザ定義型、Type が使えれば、自分自身のクラスをインスタンス化する必要もなく、もう少しすっきりとしたコードが書けたと存じます><。悔しいということもあり、諦めるまでにわかったことを残しておきますの♪

コンパイルエラー:
パブリック オブジェクト モジュールで定義されたユーザー定義型に限り、変数に割り当てることができ、実行時バインディングの関数に渡すことができます。

  • クラスモジュールでは、Public の Type を使用できない。次のエラーとなった。標準モジュールでは Public の Type の使用可能

コンパイルエラー:
オブジェクト モジュール内では、パブリック ユーザー定義型は定義できません。

おわりに

ワークシートから読み込んだデータをどのようにメモリに保持するとよいかという点で、苦労いたしました。

Range は 2 次元配列の形でデータを取得できるため、1 次元目を別の配列に追加することで絞り込んで、、、などと操作を行いたかったのですけれども、できませんでしたの><。

次のページを参考にして、なんとか取り込んだ 2 次元配列のまま操作できないかしらと思ったのですけれども、途中で方針を転換いたしました><。

2 次元配列のままでの操作の難易度が高そうと感じ始めた頃、ユーザー定義型、つまり Type の存在を知りました!

配列ではデータを扱いづらいですし、クラスモジュールにマスタのモデルを定義しますと複数のファイルに別れてしまいますし、、、と感じた欠点を Type は補うことができますの!

と思って試しましたけれども、ダメでしたの><。

結局、1つのクラスにモデルも処理も定義し、自身をインスタンス化することで実現いたしました♪試行錯誤、時間がかかってしまいますのが難点ですけれども><、楽しいですの♪

以上です。

コメントを残す