Visual Basic 6.0 初級講座 |
第27回 クラス
今回と次回はクラスの利用・作成方法を解説します。クラスを知らなくてもVBのプログラムはできるのですが、知っているのと知らないのとでは雲泥の差といっていいほどの効率・能率の差を生みます。
1.クラスとは何か
実は「クラスとは何か?」とずばりきかれると私も答えに困ります。とりあえずいえることはクラスはメソッドやプロパティやイベントをもっているということです。たとえば、コマンドボタンはCaptionプロパティやClickイベントなどをそなえているので多分クラスです。ということはVBでプログラムするということはそうだと知らなくてもすでにクラスを利用していることになりますね。
また、クラスはコマンドボタンやテキストボックスのように形のあるものばかりではありません。AppオブジェクトやScreenオブジェクトのように形のないクラスもあります。
Appオブジェクトを知らない人はプログラムのどこでもいいから、MsgBox App.Path と入力してみてください。実行するとプログラムのあるフォルダ名が表示されるでしょう?このように見えないクラスはたくさんあるのです。Screenオブジェクトも その1つです。
簡単に表現するとクラスとはメソッドやプロパティを意味のある単位でまとめている「物」と言ってもいいでしょう。このような「物」をオブジェクトと呼びます。
今回の目標はこのクラスの利用法とクラスの作成法を会得することです。
2.クラスにメソッドを実装する
クラスを作成するには[プロジェクト]メニューから、[クラスモジュールの追加]をクリックします。何か一覧が出る場合には「クラスモジュール」を選択して「開く」をクリックしてください。これで空のクラスができます。
プロジェクトエクスプローラにClass1(Class1)という項目が追加されているのを確認してください。これが空のクラスです。
画像1:クラスを追加したときのプロジェクトエクスプローラの表示。プロジェクトエクスプローラでクラスを選択するとプロパティウィンドウはこの画像のように表示される。
クラスにプログラミングする前に名前をつけておきましょう。ここではプロジェクトエクスプローラでClass1をクリックしてください。そうするとプロパティウィンドウにはClass1のプロパティが表示されます。といってもVB6ではクラスには3つしかプロパティがありません。このうちオブジェクト名をClass1からCTimerに変更してください。今回は時間を測定するストップウォッチのようなクラスを作ろうと思うのでCTimerという名前を採用しました。
さて、実験として簡単なメソッドをクラスにプログラムしましょう。時間を返すメソッドです。VBではTime関数でいつでも時間を取得できるのでクラスにこのようなメソッドを装備してもあまり意味はないのですがまぁ練習ということで。
時間を取得するメソッドは Watch という名前にすることにします。クラスに次のようにプログラムしてください。
Public Function Watch()
As Date
End Function |
■リスト1:クラス側に書くプログラム
見れば分かるのですが普通の関数の作り方と同じです。クラス内でPublicで定義された関数はすべてそのクラスのメソッドとなるのです。
それで、これをフォームから利用するにはどうしたらいいのでしょうか?
まず、フォームにコマンドボタンを1つ貼り付けてください。そのClickイベントプロシージャに次のようにプログラムしてください。
Private Sub Command1_Click() Dim MyClass As CTimer 'A Set MyClass = New CTimer 'B MsgBox MyClass.Watch 'C End Sub |
■リスト2:フォーム側に書くプログラム
これで完成です。プログラムを開始してコマンドボタンをクリックするとちゃんと時間が表示されますね。MyClass.Watchを通じてTime関数が呼ばれていることがわかります。通常の関数と変わりがないのでわからないことはないと思いますが、動作を確認するためにステップ実行してもいいでしょう。
小さなプログラムですが丁寧に見てみましょう。
まず、クラスを利用するにはAの行に書いたように Dim でクラスを表す変数を用意しなければなりません。そうです、クラスはCTimer.Watchのように直接そのメソッドを呼び出すことはできないのです。クラスを利用するためには必ずこの例のようにDim(またはPrivate,Public,Friend,Global)で変数を宣言する必要があります。
変数を宣言しただけではまだ足りません。クラスを利用するためにはさらにクラスを実体化する作業が必要です。この実体化のことを「インスタンシング」 または「インスタンス化」といいます。VBではクラスをインスタンシングするのに New キーワードを使用します。これがBの行です。
このAとBの行はクラスを利用する上での決まり文句のようなものなのでこのまま丸暗記してしまってもいいでしょう。ただ宣言とインスタンシングを別々にするのは面倒くさいという人のためにAとBをまとめて1行で書く方法もあります。次の例のようにDim文でNewを使うのです。
Dim MyClass As New CTimer
場合のよるのかもしれませんが、このように1行で書いた方が実行速度が速くなるようです。
さて、最後のCの行がいよいよさっき作ったWatchメソッドを呼び出しているところです。メソッドの呼び出しはVBで標準的に行われている方法と同じで 実体化されたクラス.メソッド名 という形になります。この場合は MyClass.Watch ですね。
クラスを保存するとき、拡張子が cls のファイルとして保存されます。
今度はプロパティの作成方法を説明します。普段「プロパティ」と呼ばれているものは実はメソッドの一種です。たとえば、Textプロパティについて考えてみます。
Textプロパティを使うと文字列をセットすることもできますし、セットされている文字列を取得することもできます。いまさら言うまでもないことですが、プロパティにはこのように2つの機能があります。
'文字列をセットする。 Text1.Text = "こんにちは" 'セットされている文字列を取得する。 Dim St As String St = Text1.Text |
■リスト3:プロパティには2つの機能がある。
もし、マイクロソフトのプログラマが妙な気を起して文字列のセットと取得をプロパティではなくメソッドで行うことにしていたら、どんな風になっていたでしょうか?ちょっと考えてみてください。
シンプルに考えると文字列をセットするためのSetTextメソッドと、セットされている文字列を取得するためのGetTextメソッドの2つに分かれていたと考えるのが妥当でしょう。次のようなイメージです。
'このコードは実際には動作しません。 '文字列をセットする。 Text1.SetText "こんにちは" 'セットされている文字列を取得する。 Dim St As String St = Text1.GetText |
■リスト4:実際には動作しないコード。もしTextがメソッドだったら。
このSetTextメソッドとGetTextメソッドの宣言は次のようになっているはずです。中身まで考えると大変なのでとりあえず宣言だけですが・・・。
Public Sub SetText(Value
As String) 'ここにSetTextメソッドの中身を書く End Sub |
Public
Function GetText() As
String 'ここにGetTextメソッドの中身を書く End Function |
■リスト5:もしTextプロパティがメソッドだったら、このように2つの宣言に分かれていただろう。
仮の話はここまでです。今プロパティをメソッドで表現した時のことを考えると2つのメソッドに分かれるということを確認しました。
プロパティの話に戻ります。プロパティは形式的にはメソッドと異なりますが2つのメソッドの役割を兼ね備えるものと考えることができます。そのため、プロパティを作成するときには2つのプロシージャを作ります。
以下はTextプロパティの宣言です。こちらも中身まで考えると大変なのでとりあえず宣言だけです。
Public Property
Let Text(Value As
String) 'ここにTextプロパティに値をセットする処理を書く End Property |
Public
Property Get
Text() As
String 'ここにTextプロパティの値を取得する処理を書く End Property |
■リスト6:プロパティの単純な例
今度はちゃんと動作する正式なコードです。どうですか、リスト5にそっくりだと思いませんか?
違いがあるのは、まずプロパティを宣言するときにはキーワードPropertyを指定すること。それから、メソッドは同じ名前のものを2つ宣言することはできませんでしたが、プロパティは2つで1つなので同じ名前にする必要があるということです。
2つのうち、値をセットする役割の方にはキーワードLetをつけて、セットする値を引数として受け取ります。値を取得する役割の方はキーワードGetをつけて戻り値に値を戻すようにします。
理解しにくい場合はリスト5とリスト6をよく見比べてください。
呼び出しの例とも合わせて考えてみましょう。たとえば、以下のプログラムを考えます。
'文字列をセットする。 Text1.Text = "こんにちは" |
■リスト7
これを実行するとLetの方のTextプロパティプロシージャが呼ばれて、引数Valueの値は「こんにちは」になります。
逆も見てみましょう。
'セットされている文字列を取得する。 Dim St As String St = Text1.Text |
■リスト8
これを実行するとGetの方のTextプロパティプロシージャが呼ばれて、戻り値として返した値が変数Stにセットされます。
LetとGetはペアになっているので、Letの引数の型とGetの戻り値の型は必ず同じにしなければなりません。
以上がプロパティの基本的な仕組みです。
他に注意すべきことを簡単に書きます。まず、キーワードLetですが、StringやIntegerなど基本的な型の場合はLetでいいのですが、対象の型がCommandButtonやPictureなどのようなオブジェクトの場合はLetの代わりにSetを使います。このような使い分けは無駄にプログラムを複雑にするものなので、いい感じはしませんがとにかくそういうことになっています。VB.NET (2002)からはSetに統一されました。
それから、読み取りはできるけれど書き込みはできないというプロパティがよくありますが、このようなプロパティはet側だけ作ってLet側またはSet側は作らないようにすることで作成できます。
4.ストップウォッチを作る
基本がわかったところでストップウォッチを作ってみます。
今回はこのストップウォッチに次の表のメソッド・プロパティを装備することにします。
名前 | 種類 | 機能 |
StartWatch | メソッド | 時間の計測を開始する。 |
StopWatch | メソッド | 時間の計測を完了する。計測開始からの経過時間を返す。 |
StartTime | プロパティ | 計測を開始した時間。値の設定も可能。 |
StopTime | プロパティ | 計測を終了した時間。 |
Running | プロパティ | 現在計測中かを返す。 |
まず、基本中の基本 Startメソッドを実装します。次のようにコーディングしてください。
Dim m_StartTime
As Single
'計測を開始した時間 Dim m_Running As Boolean '計測中ならTrue Public Sub StartWatch() m_StartTime = Timer m_Running = True End Sub |
■リスト9
上の2つのDim文はクラスの宣言セクション(どのプロシージャにも属さない一番上の部分)に書いてください。
本体のStartWatchメソッドは Sub で宣言します。これはStartWatchメソッドに戻り値がないからです。Subで宣言してもメソッドとみなされます。
肝心の内容ですが、ここではそのときの時間を変数m_StartTimeに代入して、時間計測中であることを示すためにm_RunningをTrueにするだけです。
なお、ここで使用しているTimer関数はWindowsが起動されてからの秒数をミリ秒(1000分の1秒)単位で返します。ただし、「ミリ秒単位では必ずしも正確な時間を返さない」点と「起動から計測可能時間が経過した後は再び0から計測を開始する」という癖がある点に注意してください。
これと対になるStopWatchメソッドは次のようになります。
Dim m_StopTime
As Single Public Function StopWatch() As Single m_StopTime = Timer m_Running = False StopWatch = m_StopTime - m_StartTime End Function |
■リスト10
StopWatchメソッドではそのときの時間を変数m_StopTimeに代入して、時間計測を終了したことを示すためにm_RunningをFalseにしています。最後に計測を開始してからの経過時間を計算で求めています。
まぁとりたてて難しいことはしていませんね。
ここまでで、クラスを試してみましょう。
フォームにコマンドボタンを2つとラベルを1つはりつけて次のようにプログラムしてください。
Dim Watch As CTimer |
Private Sub Form_Load() Set Watch = New CTimer 'クラスのインスタンシング End Sub |
Private Sub Command1_Click() |
Private Sub Command2_Click() |
Private Sub Form_Unload(Cancel
As Integer) |
■リスト11
とくに解説は必要ないでしょう。Command1で時間計測を開始し、Command2で時間計測を終了して結果をラベルに表示します。やってみてください。
5.プロパティを実装する
では、残ったプロパティを実装しましょう。
まず、Runningプロパティを記述します。このプロパティは時間計測中であればTrueを、そうでなければFalseを返すようにします。そのためユーザーが値を設定することはできない読み取り専用のプロパティとなります。また、StartWatchメソッドとStopWatchメソッドに記述してある変数 m_Running をつかって時間計測中かどうか取得できる点も思い出してください。
以上、勘案するとRunningプロパティは次のように記述できます。
Public Property Get Running()
As Boolean Running = m_Running End Property |
■リスト12
では、同じようにStopTimeプロパティを記述しましょう。このプロパティは時間を計測した結果を保持します。つまり、StopWatchメソッドの戻り値と同じなのですがStopWatchメソッドは1回の計測で1回しか呼び出せない(2回呼び出すと計測の結果が変わってしまうから)のに、StopTimeプロパティは何回でも呼び出せる点が異なります。
正解を見る前に練習のため自分で記述してみるのも面白いでしょう。
正解は次のようになります。
Public Property Get StopTime()
As Single StopTime = m_StopTime - m_StartTime End Property |
■リスト13
単純ですね。
最後にStartTimeプロパティです。このプロパティは計測を開始した時間を保持します。他の2つのプロパティと違って値の設定も可能なようにします。
Public Property Let StartTime(Value
As Single) m_StartTime = Value End Property |
Public Property Get StartTime()
As Single |
■リスト14
Property Getの方は他の2つと同じなので特に解説の必要はないでしょう。もうひとつのProperty Letの方が値を代入したときに呼び出されます。ここでは渡された値をそのままm_StartTimeに代入するだけですがここで関数を呼び出したり複雑な処理をすることもよくあります。
たとえば、フォームのWidthプロパティに値を代入するとフォームのWidthプロパティの値が変わるだけでなく、実際にフォームの横幅が変わりますよね。こういうことをあなたがつくったクラスのプロパティでやろうとおもったらこの、Property Letプロシージャに一連の処理を書くことになります。
6.完成を楽しむ
さて、CTimerクラスは完成しました。完成版の全コードは次のようになります。
Dim m_StartTime As Single
'計測を開始した時間 Dim m_StopTime As Single '計測を終了した時間 Dim m_Running As Boolean '計測中ならTrue |
Public Sub StartWatch() m_StartTime = Timer m_Running = True End Sub |
Public Function StopWatch()
As Single m_StopTime = Timer m_Running = False StopWatch = m_StopTime - m_StartTime End Function |
Public Property Get Running()
As Boolean Running = m_Running End Property |
Public Property Get StopTime()
As Single StopTime = m_StopTime - m_StartTime End Property |
Public Property Let StartTime(Value
As Single) m_StartTime = Value End Property |
Public Property Get StartTime()
As Single StartTime = m_StartTime End Property |
■リスト15
クラスが完成して満足できるのはVBの開発環境内に自分のプログラムが組み込まれたことを実感できる仕組みがあるためです。
たとえば、フォームに Watchと打って、 . をうつとVBの標準のオブジェクトと同じようにインテリセンス機能が働いて入力候補の一覧が表示されるでしょう。これはうれしいですね。
画像2:自作のクラスにもVBのインテリセンス機能が働く。これはうれしい。
さらにオブジェクトブラウザにもちゃんとCTimerが表示されています。確かめてみてください。
オブジェクトブラウザは[表示]メニューから起動できます。F2キーでも起動できます。
ところで、せっかくひとつの機能を持つクラスが完成したので楽しんで見ましょう。VBのDoEventsによる遅延時間を計測してみます。フォーム側に次のようにプログラムしてください。(ここでは詳しい説明は省いています。ここまで読んだ読者なら下のコードを見れば理解できるはずと思います)
Private Sub Command1_Click() Dim K As Long Watch.StartWatch For K = 1 To 100000 K = K + 1 Next K Label1.Caption = Watch.StopWatch End Sub |
Private Sub Command2_Click() Dim K As Long Watch.StartWatch For K = 1 To 100000 K = K + 1 DoEvents Next K Label2.Caption = Watch.StopWatch End Sub |
■リスト16
この実験で100000回のループの中でDoEventsがある場合とない場合とで実行速度にどのくらい差があるのかが分かります。
私のPentiumV 500MHzのマシンでは結果はComman1が0.0078125, Command2が0.3671875でした。もっと性能のいいマシンを使っている方は実行速度が速すぎて 0 と表示されることでしょう。そのときはループの中を複雑にしたり、ループの回数を増やしたりしてみてください。
ところで、いかにDoEventsが遅いかわかっていただけましたか?
7.最後に
さて、最初にクラスを知っているのと知らないのとでは効率・能率に雲泥の差があると書いたのですが、これはWithEvents(ウィズイベンツ)について言っているものです。このWithEventsに関しては次 々回説明する予定です。
しかし、今回説明したことだけ利用して便利なクラスを1度作ってしまえば、他のプログラムで同じクラスをもう一度プログラムする必要がないので能率が上がります。
実際、インターネット上では自作の便利なクラスを公開している人も結構いらっしゃるようです。