クラス・モジュールの邪道かも知れない使い方

シートがたくさんあるブックをVBAで操作するとき、いつもシートの指定がめんどくさいなあと思っていました。

まあ、ちょい書きのマクロだったらシートの指定なんか雑でもいいんですけど、そこそこの規模のものになると、厳密に指定しておかないと後でわけが分からなくなりますからね。

毎回毎回

Thisworkbook.Worksheets("HogeHogeHoge")

とか、いちいち書いてられっかっての。

……というわけで、

クラス・モジュール使えばいいんじゃね?

とか思いついたわけですよ。

f:id:akashi_keirin:20170225135102j:plain

たとえば、こんなブックがあったとする。シート名は通常分かりやすさを優先してこんな風に日本語で付けることが多いと思うが、これだとコード上でシートを指定するときに、いちいち全角/半角を切り替えるという面倒が生ずる。1回や2回なら何とも思わないが、度重なるとうっとうしいことこの上ない。

Public Const SCHEDULE_MASTER As String = "予定マスタ"
Public Const EXTRACT_DATA As String = "予定抽出"
Public Const PLACE_DATA_MASTER As String = "場所マスタ"
Public Const PERSON_DATA_MASTER As String = "人物マスタ"
Public Const DATE_DATA_MANAGER As String = "日付データ管理"
Public Const DAILY_SCHEDULE As String = "一日の予定"
Public Const WEEKLY_SCHEDULE As String = "週間予定"
Public Const EIGHT_WEEKS As String = "8週間予定"
Public Const UNWRITTEN_CONTENTS As String = "未転記"

まず、シート名は定数に放り込んである。標準モジュールの宣言セクションに書いている。まあ、これは不要だったかも知れない。

んで、クラス・モジュールに次のコードを書く。
※オブジェクト名は「TargetSheets」にしています。

Option Explicit
'クラスフィールド……(1)
Private scheduleMaster_ As Worksheet
Private extractSchedule_ As Worksheet
Private placeMaster_ As Worksheet
Private personMaster_ As Worksheet
Private dateManager_ As Worksheet
Private dailySchedule_ As Worksheet
Private weeklySchedule_ As Worksheet
Private eightWeeks_ As Worksheet
Private unwrittenContents_ As Worksheet

'アクセサ……(2)
Public Property Get scheduleMaster() As Worksheet
    Set scheduleMaster = scheduleMaster_
End Property
Public Property Get extractSchedule() As Worksheet
    Set extractSchedule = extractSchedule_
End Property
Public Property Get placeMaster() As Worksheet
    Set placeMaster = placeMaster_
End Property
Public Property Get personMaster() As Worksheet
    Set personMaster = personMaster_
End Property
Public Property Get dateManager() As Worksheet
    Set dateManager = dateManager_
End Property
Public Property Get dailySchedule() As Worksheet
    Set dailySchedule = dailySchedule_
End Property
Public Property Get weeklySchedule() As Worksheet
    Set weeklySchedule = weeklySchedule_
End Property
Public Property Get eightWeeks() As Worksheet
    Set eightWeeks = eightWeeks_
End Property
Public Property Get unwrittenContents() As Worksheet
    Set unwrittenContents = unwrittenContents_
End Property
'コンストラクタ……(3)
Private Sub Class_Initialize()
    Set scheduleMaster_ = ThisWorkbook.Worksheets(SCHEDULE_MASTER)
    Set extractSchedule_ = ThisWorkbook.Worksheets(EXTRACT_DATA)
    Set placeMaster_ = ThisWorkbook.Worksheets(PLACE_DATA_MASTER)
    Set personMaster_ = ThisWorkbook.Worksheets(PERSON_DATA_MASTER)
    Set dateManager_ = ThisWorkbook.Worksheets(DATE_DATA_MANAGER)
    Set dailySchedule_ = ThisWorkbook.Worksheets(DAILY_SCHEDULE)
    Set weeklySchedule_ = ThisWorkbook.Worksheets(WEEKLY_SCHEDULE)
    Set eightWeeks_ = ThisWorkbook.Worksheets(EIGHT_WEEKS)
    Set unwrittenContents_ = ThisWorkbook.Worksheets(UNWRITTEN_CONTENTS)
End Sub

初心者の私には、VBAのクラス・モジュールのコンストラクタって何の役にたつのかもう一つよく分からない。何で引数を持たせられない仕様なんでしょうねえ???

ま、それはさておき、今回の例の場合は珍しくコンストラクタが使える。なんせ、ThisWorkbookのそれぞれのシートを格納することははじめから分かっとるわけだから。

「クラス・モジュールって何???」な人には、いきなり見たこともないようなコードが大量に並んでいるのを見て軽く引いたかも知れないが、よく見たら同じようなコードが並んでいるだけだということに気づくと思う。コードの説明は後にして、とりあえずここまで下ごしらえをしておけば、標準モジュールの宣言セクションに

Public ts As TargetSheets

と書いてTargetSheets型のクラス変数をPublicで用意して、

任意のプロシージャ内で

Set ts = New TargetSheets

としてやれば、TargetSheetsクラスのインスタンスが生成されて、以後変数tsをTagetSheetsクラスのインスタンスとして使用できる。

……と言っても「クラス・モジュールって何???」な人にはピンと来ないでしょうね。

短く言えば、変数「ts」がプロパティとしてこの例の場合だと8つのシートを持っているかのように扱えるということ。

その辺は、実際試してもらったらすぐに分かると思う。それより何より、とにかく便利なのは、

f:id:akashi_keirin:20170225141400j:plain

こんな風にインテリセンスが働くこと!

クラス・モジュールのコードを書くときに、リスト中の(1)、(2)のところで分かりやすいプロパティ名にしておけば、シートの指定がめちゃくちゃ楽になるんですわ。

たとえば、普通だったら、

このブックの「予定マスタ」シートのA1セルを選択したい!

ってときには、

ThisWorkbook.Worksheets("予定マスタ").Range("A1").Select

とコーディングしていたのが、TagetSheetsクラスのインスタンスを生成した後だったら、

ts.scheduleMaster.Range("A1").Select

と書くだけで済む(しかも、「scheduleMaster」については、「s」を入れた時点でリスト表示されるので、実際の入力は[Tab]を押すだけ)、ということなんです。さらに、クラス変数をPublicで宣言しているので、どのモジュール・プロシージャでも使える! ある程度以上ややこしいマクロを作るときに、これは強力。

何種類もシートがあって、操作対象を切り替える回数が多い(しかも、そのことについて「チッ、めんどくせーなー」と感じている)のなら、試してみる価値はあると思う。

ちなみに、上記のクラス・モジュールのコードですが、(1)、(2)については、コチラもどうぞ。

「scheduleMaster」プロパティを例にとると、scheduleMasterプロパティの値(「予定マスタ」シートのこと)は、Private変数「scheduleMaster_」が保持していて、外部から「scheduleMasterプロパティの値を寄こせ!」という問い合わせが来たら(=「ts.scheduleMaster.~~」の部分が実行されたら)、(2)でProperty Getが呼び出されて、scheduleMasterプロパティにscheduleMaster_の値がセットされる、という流れ。今回の例だと、Property Letがないので、インスタンス生成時にコンストラクタでThisWorkbook.Worksheets("予定マスタ")がセットされた後はscheduleMasterプロパティが書き換えられることはない、すなわち「ts.scheduleMaster」は常に「予定マスタ」シートを指すということになる。

なんか、説明が難しくなってしまったけれど、実際に使ってみたらすぐに理解できると思う。

私も、Javaの入門書を読んでいるだけのときはクラスとインスタンスってよく分からなかったんだが、実際に自分でクラスを作ってインスタンス化してみるとたちどころに理解できたので。

さて、最後に(3)。これはいわゆる「コンストラクタ」というやつで、インスタンス生成時に実行される部分。VBAの場合、引数を渡すことができないので、いまいち使いどころがよく分からないんだが、先述のとおりこの場合は各プロパティにセットすべき値が決まっているので、ここでやってしまう。

 

……とまあ、素人考えでこんな風に使っているんですが、たぶん、タイトルにも書いたように邪道なんでしょうねえ。

何より、このクラスに持たせるメソッドがまるで思いつかない……。良かったら誰かアドバイスください。