ロゴ(青) 擬似からの脱却 ロゴ(緑)

  【 擬似からの脱却 】  [ Breakthrough in the Pseudo-Control Arrays ]

[ clsBpca の 軌跡 ] [ 前頁 , 次頁 , §1 , §2 , §3 , §4 , §5 , §6 , §7 , §8 , §9 , §10 , §11 , §12 ]
[ 汎用クラス , トグルラベル クラス , Focus クラス , クラス アドイン , カレンダークラス ] [ 質問はメール]

  ====================================================================
  §5  クラスモジュールの利用
  ====================================================================

2005/ 3/11  解説文・クラスソースにおいて、オブジェクト定義時での[New]指定 を止めて、Initialize 時に
                  インスタンス生成(Set ステートメント)するように修正しました。
2020/10/20  [ Property Let / Set ] の違いの解説を追加しました。


(前節より‥‥‥)
      そんな事より、「使い方」教えて、使い方!  ヽ(-.-;)

  それでは、前節までに書いた「クリックイベントプロシジャー と 共通サブ」である
 
> Private Sub cmdSun_Click()
>   Call cmdWeek_Click_Sub(1)
> End Sub
>
> Private Sub cmdMon_Click()
>   Call cmdWeek_Click_Sub(2)
> End Sub
> ( 以下、省略 )
>
> Private Sub cmdWeek_Click_Sub(ByVal Index As Integer)
>   Dim vntWeekName As Variant
>   vntWeekName = Array("", "", "", "", "", "", "", "")
>
>   MsgBox vntWeekName(Index) & "曜日ボタンがクリックされました(" & Index & ")"
>   If (colWeekBtn(Index).BackColor = vbButtonFace) Then
>     colWeekBtn(Index).BackColor = vbRed
>   Else
>     colWeekBtn(Index).BackColor = vbButtonFace
>   End If
> End Sub
 
の部分をクラスモジュールで定義してみます。

  先ず、VBEの[挿入]メニューでクラスモジュールを1つ挿入します。モジュール名が[ Class1 ]
で作られていますので、ここでは[ clsCmdWeek ]と変えておきます。この名前が、実際に、このクラ
スを使用/宣言する時の名前
になります。

     

     

  次に、一元化する為に、引き継がなければならない情報を決めます。この場合は、
        ・ コマンドボタンそのもの
        ・ そのコマンドボタンの曜日を表す( = Index )
の2つが必要になります。ここで、当然の疑問として(ん? 湧かない? 湧いてくださいよ)
        今までの例のように[ Index ]の引き継ぎのみでは何故ダメなのか?
        同じ様に下記の[ Index ]を使った Controls を使えば出来るのでは?

 
> If (Controls("cmdWeek" & Index).BackColor = vbButtonFace) Then
 
というのが出て来ると思います。

  先ず、今までは「コードを書いて来た場所」が【 UserForm モジュールの中 】であったという事
です。『 Controls ( "cmdWeek" & Index ) 』というのは、実際には前節で説明したように
 
 Me.Controls("cmdWeek" & Index)
 
という記述で [ Me. ] を省略(省略時解釈で [ Me. ]が有るものとされる)していた書き方だった
という事です。ここで [ Me ]とは、「このコードが書かれているモジュールのオブジェクト」を指し
ます。書いている場所はUserFormモジュールですから、このステートメントの意味は
        自分( UserForm )の上に載っているコントロールコレクションの
        中から[ "cmdWeek" & Index ]という名前のオブジェクトを参照

というものになります。UserFormに書いているコードだからこそ、Controls で「UserFormに載って
いるオブジェクト」を参照できていた訳です。

  ところが、今回、コードを書いている場所はクラスモジュール内です。前節で説明したように、ク
ラスそのものには実体がありません。実体がありませんので、その上に載るような( コントロール )
オブジェクトも有りません。まして、これから参照しようとしているのは、他のモジュール( UserForm )
の管理下にあるオブジェクトですから、
 
 Controls("xxxx") または Me.Controls("xxxx")
 
では参照しようがありません。ですから、処理して貰う コマンドボタンそのものを引き継ぐ必要が
あります。
    (補) クラスモジュール内で[ Controls ]が書けないという訳ではありません。
          [ Controls ]を使う方法はありますが、それは後で説明します。

これで、
      > ・ コマンドボタンそのもの
      > ・ そのコマンドボタンの曜日を表す値( = Index )

の2つの引継ぎが必要である事が判りましたので、次は、このクラスに必要な「機能」を考えます。
必要な機能は
        ・ コマンドボタンのオブジェクトを受け取る
        ・ そのコマンドボタンの曜日を表す値( = Index )を受け取る
        ・ コマンドボタンのクリックイベントを検知して処理する
の3つです。「ん? 最初の2つは既に↑で引き継ぐ事にしたんだから、それで良いんじゃ?」と
思う人も居るかもしませんね。でも幾ら人間が「これを引き継がせましょう」と叫んだところで、コン
ピュータには通じませんから、「引き継がせる仕組み(機能)」を1つ1つ用意しなければなりませ
ん。その仕組みを用意する方法の検討を「これから考えてみましょう」という事です。

      > ・ コマンドボタンのオブジェクトを受け取る
      > ・ そのコマンドボタンの曜日を表す値( = Index )を受け取る

を組み込むには、[ Property Let ]というステートメントでプロシジャーを定義します。これによって、
        クラスが値を受け取るプロパティ
というものが、このクラスに作られます。[ Let ]とは、このクラスに対して利用側が「値を代入する」
という意味です。

実際のコードは
 
 Private MyCtrl As MsForms.CommandButton
 Private MyIndex As Integer


 Public Property Let Item(NewCtrl As MsForms.CommandButton)
   Set MyCtrl = NewCtrl
 End Property

 Public Property Let Index(NewIndex As Integer)
   MyIndex = NewIndex
 End Property
 
となります(プロシジャーレベルの変数を用意して、そこに受け取った内容を保存するコードです)。
() Property Let のオブジェクト版 として Property Set というものもあり、その意味からすれば、上記 Item プロパティ は
      「Property Set」 を使用するべきでしょうが、ここでは敢えて Let を使用しています (Let  / Set の違いは 後述 )。


Item および Index が、このプロパティの名前です。外から使ってもらうものですから、当然 [ Public ]で宣言します。
これは[利用側 クラス]へと、値を格納して貰う為のインターフェースです。

逆に、クラス内に格納してある値を[クラス 利用側]へと取り出すインターフェースには[ Property Get ]という
プロシジャーを使用します。[ Get ]とは、このクラスから利用側が「値を受け取る」という意味です。そのコードは
 
 Public Property Get Item() As MsForms.CommandButton
   Set Item = MyCtrl
 End Property

 Public Property Get Index() As Integer
   Index = MyIndex
 End Property
 
となります。

  LetGet は対であり、同じ名前のプロパティは、Let/Get ともに[As 句]の属性を同じにする
必要があります。Let と Get の定義有無によって
    ・ Let のみ定義    ⇒    「値の設定のみ可能」なプロパティ
    ・ Get のみ定義    ⇒    「値の取得のみ可能」なプロパティ
    ・ Let と Get 両方を定義    ⇒    「値の設定と取得が可能」なプロパティ
となります。

  利用側(今回はUserForm モジュール)では、次のようにして、このクラス および プロパティを使います。
 
 -- cmdWeek1 cmdWeek7 という名前の場合 --

 Private cmdWeekBtn(1 To 7) As clsCmdWeek

 Private Sub UserForm_Initialize()
   Dim i As Integer
   For i = 1 To 7
     Set cmdWeekBtn(i) = New clsCmdWeek    ' インスタンスの生成
     With cmdWeekBtn(i)
       .Item = Me.Controls("cmdWeek" & i)
       .Index = i
     End With
   Next i
 End Sub



 -- cmdSun cmdSat という名前の場合 --

  ※ 上のように[ Controls ]によるループ処理が使えませんので、コーディングを
     簡素化する為に、3節で説明した「コレクション」を通してループさせます。
  ※「moug スキルアップ講座:クラスモジュールを使った究極のVBAプログラミング」
     内の[6頁 リスト9]のマクロがこのタイプです。


 Private colWeekBtn As Collection
 Private cmdWeekBtn(1 To 7) As clsCmdWeek

 Private Sub UserForm_Initialize()
   Dim i As Integer
   Set colWeekBtn = New Collection    ' インスタンスの生成
   With colWeekBtn
     .Add cmdSun
           :
     .Add cmdSat
   End With

   For i = 1 To 7
     Set cmdWeekBtn(i) = New clsCmdWeek    ' インスタンスの生成
     With cmdWeekBtn(i)
       .Item = colWeekBtn(i)
       .Index = i
     End With
   Next i
 End Sub
 


これで、7つのコマンドボタンオブジェクトと、その番号( Index )がクラスに登録されます。

  ここで、ふと疑問に思うのは(ん? ここでも湧かない? 火にくべてでも、沸(湧) かせてください)
        クラスの中には、保存する変数を「ひとつ」しか用意していないのに、そこへ7回も
        繰り返し代入したら、最後の7番目のものしか残らないのでは?

というものではないでしょうか。

  しかし、前節で説明したように、クラスというのは、ただの雛形(型紙)です。実体に割り当てて
初めて意味を持ちます。では、今回の場合、実体とは「何か」といえば
 
> Private cmdWeekBtn(1 To 7) As clsCmdWeek
 
という配列定義のオブジェクト変数です。配列ですから、名前はひとつでも領域は「7つ」確保
してあります。この「7つ」の領域に、それぞれ、[ clsCmdWeek ]という雛形を当て嵌めている訳
です。つまり
 
> Private MyCtrl As MsForms.CommandButton
> Private MyIndex As Integer

 
も、実体「ひとつ」につき「ひとつ」割り当てられており、結果「7つ」のコマンドボタンが収まる場
所は、各々「ひとつ」ずつ計7つ用意されているという事になります。

  次に3番目の機能である
        > ・コマンドボタンのクリックイベントを検知して処理する
を組み込みます。先ずは、とにもかくにも、システムに対して「イベントの通知を送って下さい」と
いう定義をする必要があります。これには、
 
> Private MyCtrl As MsForms.CommandButton
 
を少し変えて
 
 Private WithEvents MyCtrl As MsForms.CommandButton
 
という風に、[ WithEvents ] キーワードを付け加えます

これによって、何が出来るかと言えば、
 
 Private Sub MyCtrl_Click()
 End Sub
 
という風に[ オブジェクト名 + "_Click" ] という名前で Sub プロシジャーを、このモジュール内に
用意しておけば、システムが、クリック動作検知時にこの[ MyCtrl_Click ]を起動してくれるという
ものです。これがイベントプロシジャーです。

  (注) 一寸判り難い言い回しになりますが、[ "Click" ] というスペルそのものが 「クリックイベント」 を呼び込む
        のではありません。この場合、MsForms.CommandButton というクラス定義の中で、
            クリック動作を検出した時には、利用側の [ "Click" ] というスペルのプロシジャーへ繋げましょう。
        という定義がされているので、[ "Click" ] というスペルのプロシジャーを用意しておけばクリックイベントが
        処理できるという意味合いになります(もし、"ABC" というスペルで定義されていたら、マウスクリックの
        イベントプロシジャーを記述する時には[ MyCtrl_ABC ( ) ] などという風になった事でしょう)。
        ( 参照 : §9 「イベント定義方法の解説」 部分

       
このイベントプロシジャーに、共通サブにあった処理を組み込めば、
 
 Private Sub MyCtrl_Click()
   Dim vntWeekName As Variant
   vntWeekName = Array("", "", "", "", "", "", "", "")

   MsgBox vntWeekName(MyIndex) & "曜日ボタンがクリックされました(" & MyIndex & ")"
   If (MyCtrl.BackColor = vbButtonFace) Then
     MyCtrl.BackColor = vbRed
   Else
     MyCtrl.BackColor = vbButtonFace
   End If
 End Sub
 
となります。このコードだけ見ると、[ MyIndex ] [ MyCtrl ]の情報が引き継がれていないように感じられますが、
前述のように『Index 』『Item 』というプロパティを通して、このクラス(の実体)にある モジュールレベル変数へ
引き継がれていますので大丈夫です。

  「後で説明します」と書いた↓ですが
        >(補)クラスモジュール内で[Controls]が書けないという訳ではありません。
        >  [Controls]を使う方法はありますが、それは後で説明します。

[ Me.Controls ( xxxx ) ]という書き方では利用元のコントロールを参照できないというだけです。
[ Userform オブジェクト.Controls ( xxxxx ) ]と書くならば使えます。つまり、Item プロパティで
CommandButton オブジェクトを受け取ったのと同じように、UserForm オブジェクトを受け取る
プロパティを組み込んでやれば良い訳です。しかし、既にコマンドボタンを直接受け渡しする仕
組みが可能なのに敢えて回りくどい方法を取る事には意味が無いと言えますので、ここでは組み
込みません。

  全てを纏めると、下記のようなコーディングになります。
 
 === UserForm モジュール =========================

 Private colWeekBtn As Collection
 Private cmdWeekBtn(1 To 7) As clsCmdWeek

 Private Sub UserForm_Initialize()
   Dim i As Integer
   Set colWeekBtn = New Collection    ' インスタンスの生成
   With colWeekBtn
     .Add cmdSun
            :
     .Add cmdSat
   End With

   For i = 1 To 7
     Set cmdWeekBtn(i) = New clsCmdWeek    ' インスタンスの生成
     With cmdWeekBtn(i)
       .Item = colWeekBtn(i)
       .Index = i
     End With
   Next i
 End Sub

 Private Sub UserFom_Terminate()  ' 終了前にオブジェクト変数を解放します
   Set colWeekBtn = Nothing
   Erase cmdWeekBtn    ' 配列変数の場合はErase命令で解放します
 End Sub

 ' ※ UserFormモジュール内にClickイベント記述が無い



 === クラスモジュール ( clsCmdWeek ) =============================

 Private WithEvents MyCtrl As MsForms.CommandButton
 Private MyIndex As Integer

 Public Property Let Item(NewCtrl As MsForms.CommandButton)
   Set MyCtrl = NewCtrl
 End Property

 Public Property Let Index(NewIndex As Integer)
   MyIndex = NewIndex
 End Property

 Private Sub MyCtrl_Click()
   Dim vntWeekName As Variant
   vntWeekName = Array("", "", "", "", "", "", "", "")

   MsgBox vntWeekName(MyIndex) & _
                    "曜日ボタンがクリックされました(" & MyIndex & ")"
   If (MyCtrl.BackColor = vbButtonFace) Then
     MyCtrl.BackColor = vbRed
   Else
     MyCtrl.BackColor = vbButtonFace
   End If
 End Sub


以上のところまで済めば、
        ・ コントロールオブジェクトへのアクセスは
               コレクションや[ Controls ] を通して、見た目、配列処理を実現
        ・ クリックイベント処理のコードは
               [ clsCmdWeek ] クラスモジュール内の1箇所に記述があるだけ
という風になって、VBの場合と同じように
        【 コントロールの配列化 & イベント記述の一元化
が達成できた事になります。ただし、これは本当にVBのような「コントロール配列」による仕組み
ではなく、コーディング手法の駆使によって実現した代替策なので、VBAの世界では、この手法
の事を
        擬似コントロール配列
と呼んでいます。  や、やっとですか〜  (-_-)zzz  こら、起きろ〜

一般的に、クラスモジュールを使った擬似コントロール配列の『解説』と言えば、ここまでの説明で終わって
      います(ただし、ここまで 「微に入り細に入り」 説明しているのは無いと思いますけど‥‥‥ ) 。

しかし、「擬似コントロール配列」は、やはり【擬似】であって、未だ未だ「プログラミングする上で
色々な問題点」を抱えています。


[ この場所へのリンク ]

  --- Property Let  と  Property Set  について ---    ( 2020/10/20 追記 )

  Property Let / Property Set は共に
       利用側 から クラスモジュール へ 「値」 を引き継ぐ為のインターフェース
  を定義する際に使用するステートメントです。

  Set はオブジェクト( コマンドボタン 等 )、Let はオブジェクト以外( 数値や文字列 等 ) という住み分けになっています。
  しかし、実際のところ Let でも オブジェクト変数の引継ぎが問題なく行えます( この解説では前記の通り Set ではなく
  Let を使用して定義しています)。

  前記のクラスモジュールで Property Set を使用した場合は以下のようになります。

    Private MyCtrl As MsForms.CommandButton

    Public Property Set Item(NewCtrl As MsForms.CommandButton)
        Set MyCtrl = NewCtrl
    End Property

    Public Property Get Item() As MsForms.CommandButton
        Set Item = MyCtrl
    End Property

  このように クラスモジュール側では単に Property Let  を  Property Set に書き換えるだけです。他は何も変わりません。

  利用側では下記のように Item プロパティへの代入の際にも Set ステートメントが必要になります( Set を付けないとエラー
  になります)。それ以外の違いはありません。

    For i = 1 To 7
        Set cmdWeekBtn(i) = New clsCmdWeek
        With cmdWeekBtn(i)
            Set .Item = Me.Controls("cmdWeek" & i)
            .Index = i
        End With
    Next i


  尚、この例の Item プロパティ からの値の取得( Property Get )では、変数への代入の際に Set ステートメントが必要です。
  これは相方が Let であろうが Set であろうが変わりません。 オブジェクト変数への代入ですから当然ですね。

    Dim obj As MsForms.CommandButton
    Set obj = cmdWeekBtn(2).Item


  ( まとめ )
  プロパティが 『オブジェクト』であることを明確に示し、使用する際にも 『オブジェクト』であることを明確に認識して使う、という
  設計思想で クラス を定義する場合には Property Set で記述すれば良いと思います。そこまで厳格にしなくてもよい
  ならば、 オブジェクト に対しても Property Let で十分です。
 

--------------------------------------------------------------------------

(-_ゞゴシゴシ  お疲れ様でした〜♪ まぁ、ここらで一服、粗茶でもどうぞ♪  ( ^‐^)_且~~

ここから先が、いよいよ『擬似からの脱却』の本編になります。

これだけ長くても、今までのは全部、単なる前振りかい   (#ノ-_-)ノ⌒┻┻
それで‥‥、「残っている問題点」って何!  (-"-#)

  ====================================================================
  次節 : §6  「擬似コントロール配列」で残る問題点
  ====================================================================

[ 前頁 , 次頁 , §1 , §2 , §3 , §4 , §5 , §6 , §7 , §8 , §9 , §10 , §11 , §12 ]
[ 汎用クラス , トグルラベル クラス , Focus クラス , クラス アドイン , カレンダークラス ] [ 質問はメール]


[ Home へ戻る ]

ロゴ(ゴールド) ロゴ(ゴールド)

角田 桂一 Mail:addinbox@h4.dion.ne.jp CopyRight(C) 2004 Allrights Reserved.