Visual Basic 6.0 初級講座
VB6対応

 

Visual Basic 中学校 > VB6 初級講座 >

第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 という名前にすることにします。クラスに次のようにプログラムしてください。

VB6対応

Public Function Watch() As Date

Watch = Time()

End Function

■リスト1:クラス側に書くプログラム

見れば分かるのですが普通の関数の作り方と同じです。クラス内でPublicで定義された関数はすべてそのクラスのメソッドとなるのです。

それで、これをフォームから利用するにはどうしたらいいのでしょうか?

まず、フォームにコマンドボタンを1つ貼り付けてください。そのClickイベントプロシージャに次のようにプログラムしてください。

VB6対応

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 のファイルとして保存されます。

 

3.クラスにプロパティを実装する

 

今度はプロパティの作成方法を説明します。普段「プロパティ」と呼ばれているものは実はメソッドの一種です。たとえば、Textプロパティについて考えてみます。

Textプロパティを使うと文字列をセットすることもできますし、セットされている文字列を取得することもできます。いまさら言うまでもないことですが、プロパティにはこのように2つの機能があります。

VB6対応

'文字列をセットする。
Text1.Text = "こんにちは"

'セットされている文字列を取得する。
Dim St As String
St = Text1.Text

■リスト3:プロパティには2つの機能がある。

もし、マイクロソフトのプログラマが妙な気を起して文字列のセットと取得をプロパティではなくメソッドで行うことにしていたら、どんな風になっていたでしょうか?ちょっと考えてみてください。

 

シンプルに考えると文字列をセットするためのSetTextメソッドと、セットされている文字列を取得するためのGetTextメソッドの2つに分かれていたと考えるのが妥当でしょう。次のようなイメージです。

VB6対応

'このコードは実際には動作しません。

'文字列をセットする。
Text1.SetText "こんにちは"

'セットされている文字列を取得する。
Dim St As String
St = Text1.GetText

■リスト4:実際には動作しないコード。もしTextがメソッドだったら。

 

このSetTextメソッドとGetTextメソッドの宣言は次のようになっているはずです。中身まで考えると大変なのでとりあえず宣言だけですが・・・。

VB6対応

Public Sub SetText(Value As String)

    'ここにSetTextメソッドの中身を書く

End Sub
Public Function GetText() As String

    'ここにGetTextメソッドの中身を書く

End Function

■リスト5:もしTextプロパティがメソッドだったら、このように2つの宣言に分かれていただろう。

仮の話はここまでです。今プロパティをメソッドで表現した時のことを考えると2つのメソッドに分かれるということを確認しました。

プロパティの話に戻ります。プロパティは形式的にはメソッドと異なりますが2つのメソッドの役割を兼ね備えるものと考えることができます。そのため、プロパティを作成するときには2つのプロシージャを作ります。

以下はTextプロパティの宣言です。こちらも中身まで考えると大変なのでとりあえず宣言だけです。

VB6対応

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をよく見比べてください。

呼び出しの例とも合わせて考えてみましょう。たとえば、以下のプログラムを考えます。

VB6対応

'文字列をセットする。

Text1.Text = "こんにちは"

■リスト7

これを実行するとLetの方のTextプロパティプロシージャが呼ばれて、引数Valueの値は「こんにちは」になります。

逆も見てみましょう。

VB6対応

'セットされている文字列を取得する。

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メソッドを実装します。次のようにコーディングしてください。

VB6対応

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メソッドは次のようになります。

VB6対応

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つはりつけて次のようにプログラムしてください。

VB6対応

Dim Watch As CTimer
Private Sub Form_Load()

    Set
Watch = New CTimer        'クラスのインスタンシング

End Sub

Private Sub Command1_Click()

    Watch.StartWatch

End Sub

Private Sub Command2_Click()

    Dim
Result As Single
   
Result = Watch.StopWatch
    Label1.Caption = Result            '結果をラベルに表示

End Sub

Private Sub Form_Unload(Cancel As Integer)

    Set
Watch = Nothing                'クラスをメモリから開放

End Sub

■リスト11

とくに解説は必要ないでしょう。Command1で時間計測を開始し、Command2で時間計測を終了して結果をラベルに表示します。やってみてください。

 

.プロパティを実装する

 

では、残ったプロパティを実装しましょう。

まず、Runningプロパティを記述します。このプロパティは時間計測中であればTrueを、そうでなければFalseを返すようにします。そのためユーザーが値を設定することはできない読み取り専用のプロパティとなります。また、StartWatchメソッドとStopWatchメソッドに記述してある変数 m_Running をつかって時間計測中かどうか取得できる点も思い出してください。

以上、勘案するとRunningプロパティは次のように記述できます。

VB6対応

Public Property Get Running() As Boolean

   
Running = m_Running

End Property

■リスト12

では、同じようにStopTimeプロパティを記述しましょう。このプロパティは時間を計測した結果を保持します。つまり、StopWatchメソッドの戻り値と同じなのですがStopWatchメソッドは1回の計測で1回しか呼び出せない(2回呼び出すと計測の結果が変わってしまうから)のに、StopTimeプロパティは何回でも呼び出せる点が異なります。

正解を見る前に練習のため自分で記述してみるのも面白いでしょう。

正解は次のようになります。

VB6対応

Public Property Get StopTime() As Single

    StopTime = m_StopTime - m_StartTime

End Property

■リスト13

単純ですね。

最後にStartTimeプロパティです。このプロパティは計測を開始した時間を保持します。他の2つのプロパティと違って値の設定も可能なようにします。

VB6対応

Public Property Let StartTime(Value As Single)

    m_StartTime = Value

End Property

Public Property Get StartTime() As Single

    StartTime = m_StartTime

End Property

■リスト14

Property Getの方は他の2つと同じなので特に解説の必要はないでしょう。もうひとつのProperty Letの方が値を代入したときに呼び出されます。ここでは渡された値をそのままm_StartTimeに代入するだけですがここで関数を呼び出したり複雑な処理をすることもよくあります。

たとえば、フォームのWidthプロパティに値を代入するとフォームのWidthプロパティの値が変わるだけでなく、実際にフォームの横幅が変わりますよね。こういうことをあなたがつくったクラスのプロパティでやろうとおもったらこの、Property Letプロシージャに一連の処理を書くことになります。

 

.完成を楽しむ

 

さて、CTimerクラスは完成しました。完成版の全コードは次のようになります。

VB6対応

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による遅延時間を計測してみます。フォーム側に次のようにプログラムしてください。(ここでは詳しい説明は省いています。ここまで読んだ読者なら下のコードを見れば理解できるはずと思います)

VB6対応

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が遅いかわかっていただけましたか?

 

.最後に

 

さて、最初にクラスを知っているのと知らないのとでは効率・能率に雲泥の差があると書いたのですが、これはWithEvents(ウィズイベンツ)について言っているものです。このWithEventsに関しては次 々回説明する予定です。

しかし、今回説明したことだけ利用して便利なクラスを1度作ってしまえば、他のプログラムで同じクラスをもう一度プログラムする必要がないので能率が上がります。

実際、インターネット上では自作の便利なクラスを公開している人も結構いらっしゃるようです。