和風スパゲティのレシピ

日本語でコーディングするExcelVBA

簡単で便利なクラスを作って学ぶVBAクラスモジュール入門

VBAの勉強を進めていくと、難敵として立ちはだかるのが「クラスモジュール」です。

勉強しようと思ったけどよくわからなくて断念したり、
一通り構文は見たけど使いどころがわからなかったり。


このクラスモジュールが難敵になるのは、
何が出来てどう便利なのかがわからない
からなのが原因じゃないかと推測します。


目的がわからないまま手段を学んでもちんぷんかんぷんですからね。


さてこの「使いどころがわからない」という謳い文句は、
プロシージャ分割(Sub/Functionの使い方)の記事でも述べました。


www.limecode.jp

↑こちらの記事にも書きましたが、

プロシージャがないと実装できない処理はない
⇒ 必要に迫られないので使いどころがわからない

ではプロシージャとは何のためにある機能なのかというと、
「コードを書きやすく&読みやすくするため」にある。

というのがプロシージャ分割の目的でしたね。



実はクラスモジュールの目的もまったく一緒です。


クラスモジュールがないと実装できない処理はありません。

クラスを使う必要に迫られることはないので、
必要になったら使おうと思うと、一生使うことがありません。


ですが、クラスモジュールを使うと「コードを書きやすく&読みやすく」なります。



最初に答えを書いておきますと、
クラスモジュールとは「コードを整理整頓する道具」なのです。


ここから先、クラスモジュールを使ってコードを整理整頓する様子をお見せします。

簡単に作れて、かつ結構便利なクラスをサンプルにしましたので、
クラス=難しいという固定観念は一旦忘れて、
「コードを整理整頓する」んだと思って眺めてみてください。

たぶん想像の100倍くらい簡単だと思いますよ。



では始めます。


※ 注:クラスを使った整理整頓は、プロシージャ分割を使った整理整頓の延長にあります。
Functionをあまり使わない方や、マクロを基本ひとつのSubプロシージャに書いていてプロシージャ分割に慣れていない方は、クラスより先にプロシージャ分割に慣れておくと学習がスムーズに進みますので、よろしければ先ほどの記事からご覧ください。

クラスを作る元となるコード

クラスの目的は「整理整頓」ということで、
どんな処理でも

  • クラスを使ったバージョン
  • クラスを使わないバージョン

を作ることができます。


「クラスがないと実装できない処理はない」わけですから当然ですね。


ということで、クラスを使わない=整理整頓する前のコードをまずは作りましょう。

テーマは「処理の進捗をステータスバーに表示する」コードです。


ステータスバーとは、Excelの左下にある

ステータスバーサンプル

↑これのことですね。

ここに画像の様に進捗を表示する機能を作って、
時間のかかるマクロに付けると結構便利です。


人間は進捗が遅いことよりも進捗がわからないことの方にストレスを感じる

らしいですからね。


そのコードがこちらです↓

Sub 時間がかかる処理()
    
    Dim 全処理数 As Long: 全処理数 = 100
    Dim 現在処理数 As Long
    
    Dim i As Long
    For i = 1 To 100
        
        ' ここに時間のかかる処理
        
        現在処理数 = 現在処理数 + 1
        Application.StatusBar = 現在処理数 & " / " & 全処理数 & " " & (現在処理数 / 全処理数) * 100 & "%"
    Next
    
End Sub

ステータスバーの表示コードは結構簡単なので、
知らなかった方は覚えてしまってください。

Application.StatusBar = 表示したいテキスト

だけで実装ができます。

今回はこのテキストに「456 / 2000 23%」のような値を代入しています。


さてこのコードをクラスを使って整理整頓してみましょう。

クラスを使ったコード

クラスモジュールを使う前に、簡単に機能を説明をしておきます。

クラスモジュールを使うと「同じ仲間の変数と関数をまとめる」ことができます。


今回はコード中に登場する「ステータスバーの仲間たち」をまとめることになりますので、そんなつもりで眺めてください。


まずは簡単に、「変数の仲間たち」をクラスにしてみましょう。

ステータスバーの仲間たちは、

Dim 全処理数 As Long
Dim 現在処理数 As Long

この2人ですね。


早速クラスモジュールを挿入して名前を付けます。

クラスモジュールはモジュールの名前をクラスの名前として使いますので、
標準モジュール以上に、ちゃんと名前をつける必要があります。


ということで、

モジュール名

「Classステータスバー」と名付けました。


中身のコードは変数2つをまとめたいということなので、

Option Explicit

Public 全処理数 As Long
Public 現在処理数 As Long

DimをPublicに変えて変数宣言します。


これで完成です。



え!?これだけ!?

って思ったかもしれませんがこれだけです。
想像の100倍簡単だったと思います。


ひとまずこのクラスを使うと、コードをこんな風に整理整頓できます↓

Sub 時間がかかる処理()
    
    Dim clsステータスバー As New Classステータスバー
    clsステータスバー.全処理数 = 100
        
    Dim i As Long
    For i = 1 To 100
        
        ' ここに時間のかかる処理
        
        clsステータスバー.現在処理数 = clsステータスバー.現在処理数 + 1
        Application.StatusBar = clsステータスバー.現在処理数 & " / " & clsステータスバー.全処理数 & " " & (clsステータスバー.現在処理数 / clsステータスバー.全処理数) * 100 & "%"
    Next
    
End Sub

 
コードを見比べるとわかりますが、
2つあった変数宣言(Dim)が1つになり、

Dim clsステータスバー As New Classステータスバー

と、ステータスバークラスの変数を1つだけ宣言しています。

Newはまあおまじないだと思っておけばOKです。



以降のコードはこのステータスバークラスの変数を使っており、

  • 全処理数 ⇒ clsステータスバー.全処理数
  • 現在処理数 ⇒ clsステータスバー.現在処理数

という風に書き換わっています。


ちなみに入力時は、

クラスの入力候補

と、クラスの変数名. まで打てば選択肢から選んで入力ができます。


なんとなく便利そうなのは伝わりましたか?


まだこれだとそこまで便利!とまではいきませんが、
クラスでやりたいことは、実はこれがすべてです。


変数の仲間たちを1つのでっかい変数にまとめることで、
どの変数がどの処理のためのものかを明確にしてくれるのがクラス
です。


今回のコードでは

' ここに時間のかかる処理

この部分を書くとき、ステータスバー用の変数を使ってしまう恐れがなくなりますし、
 

Application.StatusBar =

この部分を書くとき、違う変数を使ってしまうミスを減らしてくれます。


このようにクラスモジュールとは、
同じ目的を持った仲間の変数と関数をまとまる」仕組みです。

仲間の変数と関数を集めて、
ひとつのでっかい変数を作る」機能だと思ってもいいです。


クラス = ( ´∀`)人(´∀` )ナカーマ
ということで、実は学校の「クラス」と同じ意味なんですよ。


もちろんクラスモジュールには今回使った「Public変数」以外にもたくさんの機能を持っていますが、
どこまで行っても目的は「仲間をまとめる」ことで変わりません。


その目的を意識できるだけで、クラスの勉強が非常に楽になりますので、
ぜひとも心に刻み込んで、この先も読み進めてください。

クラスにはプロシージャもまとめることができる

さて作ったクラスを進化させましょう。

上のコードでは「Public変数2個」のクラスでしたが、
ただ変数をまとめるだけなら構造体(Type)でもできますからね。


変数に加えて関数もまとめることができるのがクラスの真の力です。


ということで、元のコード中に登場したステータスバーの仲間を探してみましょう。

現在処理数 = 現在処理数 + 1
Application.StatusBar = 現在処理数 & " / " & 全処理数 & " " & (現在処理数 / 全処理数) * 100 & "%"

このコードたちもステータスバーの仲間ですね。

これらをクラス内に持っていきましょう。


移動したクラスの中身がこちらです↓

Option Explicit

Public 全処理数 As Long
Public 現在処理数 As Long

Sub 進捗を表示する()
    Application.StatusBar = 現在処理数 & " / " & 全処理数 & " " & (現在処理数 / 全処理数) * 100 & "%"
End Sub

Sub 進捗を1進める()
    現在処理数 = 現在処理数 + 1
End Sub

そのままコードをSubプロシージャにしただけですね。


この時、標準モジュールはこう書けるようになっています↓

Sub 結構時間がかかる処理()
    
    Dim clsステータスバー As New Classステータスバー
    clsステータスバー.全処理数 = 100
        
    Dim i As Long
    For i = 1 To 100
        
        ' ここに時間のかかる処理A
        
        Call clsステータスバー.進捗を1進める
        Call clsステータスバー.進捗を表示する
    Next
    
End Sub

 
クラス内で宣言したSubプロシージャは、

Call クラスの変数名.プロシージャ名

と、変数と同じように呼び出すことができています。


この標準モジュールのコードを見ると、
クラスの力がわかるのではないでしょうか?


プロシージャ分割の力で「処理に名前がついている」のですが、
その処理がどの仲間の処理なのかまで丸わかりです。

しかもその処理で使う変数たちも中身にまとめられているので、
変数を取り違える心配もナシ!


プロシージャ分割を使っての整理整頓では、

Call ステータスバーに進捗を表示する(全処理数, 現在処理数)

と、プロシージャ名の主語と、渡す引数で表現するのが限界でした。
(これでも十分読みやすいですけどね)


しかしクラスモジュールを使うことで、

Call clsステータスバー.進捗を表示する

と、使う変数はクラス内に内包、処理の主語はクラスの変数名が担ってくれています。


どの変数がどの処理のためのものかだけでなく、
どの処理が何の目的のためのものかまで表してくれるのです。


他にもメリットは盛りだくさんで、
読むときだけでなく、書くときも非常に便利で、

関数を追加した入力候補

こんな風に変数とプロシージャを選択肢から選べます。


さらにもう一つ大事なメリットですが、
このコードを別のマクロでも使いたいと思ったら、

モジュール名

このモジュールをマウスでドラッグ&ドロップするだけなんです。
ステータスバー関連コードが一式揃ってますからね。

すごく便利!


クラスの機能は他にもたくさんあるのですが、
実は「Public変数とSub/Functionプロシージャ」だけでもこんなに便利です。

もう一度見てみるとわかりますが、

Public 全処理数 As Long
Public 現在処理数 As Long

Sub 進捗を表示する()
    Application.StatusBar = 現在処理数 & " / " & 全処理数 & " " & (現在処理数 / 全処理数) * 100 & "%"
End Sub

Sub 進捗を1進める()
    現在処理数 = 現在処理数 + 1
End Sub

すごく簡単ですよね?

Public変数とSub/Functionプロシージャしか使わないクラスは、
標準モジュールの知識だけで普通に作れます。


想像していた100倍簡単でしょう?


すぐにでも使い始めることができると思いますので、
簡単な仲間たちからクラスにしてみてください。

※ クラス=Propertyプロシージャを使うものと捉えている人をよく見かけますが、
 別にクラスにPropertyプロシージャは必須ではありません。
 VBAにおいては、全く使わなくても意外と問題のない機能です。

 


以上がクラスモジュールの目的です。


クラスモジュールとは同じ目的を持った仲間の変数と関数をまとめる仕組みです。

仲間の変数と関数を集めてひとつのでっかい変数を作る機能とも言えます。


ここから先、クラスの機能を学んでいくと思いますが、
どこまで行っても目的はこれ1本ですのでそれを忘れないでいてください。

便利にカスタマイズしたクラス

さて本題の目的に関する解説は終わりましたが、
せっかくPublic変数とSub/Functionだけでも十分便利と書きましたので、
今回のクラスをもっと便利にしてみましょう。

ステータスバーによくある

  • 進捗を進める数の指定
  • プログレスバー(■■□□□□□□□□)
  • キリ番だけ表示を更新する機能

あたりを実装してみます。


ちょっと面倒に見えますが、
一度苦労すれば、次からはドラッグドロップだけで済むのがクラスの良さですからね。


こんな標準モジュールを目的にしましょう。

Sub 時間がかかる処理()
    
    Dim stBar As New Classステータスバー
    stBar.全処理数 = 1000
        
    Dim i As Long
    For i = 1 To 1000
        
        ' ここに時間のかかる処理
        
        Call stBar.進捗をn進める(1)
        If stBar.Isキリ番(100) Then Call stBar.進捗を表示する
    Next

End Sub

表示はこんな感じになります。


プログレスバーサンプル


これを実装するクラスの中身がこちら

Option Explicit

Public 全処理数 As Long
Public 現在処理数 As Long

Sub 進捗を表示する()
    Application.StatusBar = 現在処理数 & " / " & 全処理数 & " " & (現在処理数 / 全処理数) * 100 & "%" & プログレスバー
End Sub

Sub 進捗をn進める(n As Long)
    現在処理数 = 現在処理数 + n
End Sub

Function Isキリ番(区切り値 As Long) As Boolean
    Isキリ番 = 現在処理数 Mod 区切り値 = 0
End Function

Private Function プログレスバー() As String
    Dim ■の数 As Long: ■の数 = 現在処理数 / 全処理数 * 10
    プログレスバー = WorksheetFunction.Rept("■", ■の数) _
                      & WorksheetFunction.Rept("□", 10 - ■の数)
End Function

' コードテンプレート
'    Dim stBar As New Classステータスバー
'    stBar.全処理数 = 1000
'
'    Dim i As Long
'    For i = 1 To 1000
'
'        Call stBar.進捗をn進める(1)
'        If stBar.Isキリ番(100) Then Call stBar.進捗を表示する
'    Next

結構万能で便利そうなクラスが出来ました。


実際に使った時のコードをコメントアウトして中身に入れておくと、
仕様書代わりにもなっておススメです。


繰り返しになりますが、使いまわしたいときは

モジュール名

これを持っていくだけですので、
試しに使ってみてください。



このコードを眺めていると、なんとなくクラスの使い方がわかるのではないかと思います。


Public変数とSub/Functionだけでもこれくらいはこなしてくれますので、
あまり構えずに、クラスのトライしてもらえると嬉しいです。

まとめ

以上でクラスモジュール入門を終わります。

  • クラスモジュールは「同じ目的を持つ仲間の変数・関数をまとまる」仕組み
  • 仲間の変数と関数を集めて「ひとつのでっかい変数を作る」機能とも言える
  • Public変数とSub/Functionプロシージャだけで十分便利

がポイントでしたね。


クラスモジュールが使えるようになると、
コードが整理整頓出来て、書きやすく、読みやすく、使いまわしやすくなります。

といっても、実際に書き始めると、どんな設計のクラスにするかで悩んで、
はじめはうまくいかないかもしれません。

特に「どこまで標準モジュールに書いてどこまでクラスモジュールに書くか」は、
永遠のテーマといってもいいかもしれません。


ですが、慣れてくれば自分が考えるマクロの全体像をコードに表現させる、強力な武器になります。

冒頭の通り、「必要になったら使う」ではいつまでたっても出番が訪れませんので、
簡単なコードでいいのでクラスにトライしてみてください。

おまけ:用語集

慣れてきてから学んでもいいと思いますが、
いろいろなコードと解説を読むときに手助けになるので、
軽く用語の定義にも触れておきます。


偉い人が怒る説明かもしれませんので、偉い人は見ないでください。
なんとなくの定義だと思って読んでください。



クラスを呼び出した

Dim clsステータスバー As New Classステータスバー

このコードを「クラスの変数を宣言した」と記述してきましたが、
このクラスの変数のことを「インスタンス」と呼びます。

  • Classステータスバー という名前のクラスモジュールがいわゆる「クラス」
  • As New クラス名 で宣言する変数が「インスタンス」

です。

Newすることを、インスタンスを生成すると言ったりします。


「クラスが設計図で、インスタンスはクラスを元に作った実体」
みたいに説明されることが多いですね。


インスタンスは変数のようなものなので、

Dim clsステータスバー1 As New Classステータスバー
Dim clsステータスバー2 As New Classステータスバー

みたいに同じクラスから複数のインスタンスを作れます。


これが俗にいう「クラスは複製できて便利」という話ですが、
それをPRしすぎて、かえって混乱を招いている気もします。

今回の様に、インスタンスが1個しかないクラスも十分便利ですからね。


真の目的は「仲間をまとめること」ですので。



そしてこの重要なテーマ「仲間をまとめる」ことを、
広義に「カプセル化」と呼びます。


今回はステータスバーという1つのカプセルを作ったイメージですかね。

狭義だと「もっとちゃんと作らないとカプセル化とは呼べないよ」といううるさいお偉いさんもいます。


そして最後に、

クラスやインスタンスなど「仲間が集まってできたモノ」をオブジェクトと呼び、

オブジェクトづくりを頑張ってきれいなコードを書くことを、
オブジェクト指向プログラミングと呼びます。


ただし「オブジェクトづくり頑張ってるね」条件が3つあって、
内2つがVBAにはそもそも機能がないので、
クラスモジュールをどんなに頑張ってもVBAはオブジェクト指向プログラミング言語ではありません。


ちなみにVBAでも辛うじてできる頑張りが「隠蔽」で、
それをやるために必要なのがPropertyプロシージャだったりします。


この辺をなんとなく抑えておけば、
VBAを使う上で困ることはないような気がします。

抑えておかなくても、困ることはないような気もします。


あとはプログラマになってオブジェクト指向言語を扱うときに勉強すればいいと思いますが、気が向いたら覚えてあげてください。




さて、最後におまけのおまけです。


この記事をここまで読むにあたって、
クラス・変数・プロシージャ名が英語だったら読みやすかったか?
というのを、ちょっと考えてみてください。



プロシージャ分割やクラスといった「コードを読みやすくる技術」は、
コードを文章の様に構成する力が必要になります。

この文章構成に、母国語はとても力強い味方になってくれます。


いずれマスターした後で実際に使うコードはさておき、
こういった「学習」においては、母国語こそが最強の武器です。


日本語変数・関数名のご使用を、是非ご検討ください。

www.limecode.jp