[[20140926184519]] 『クラスモジュールの勉強』(稲葉) ページの最後に飛ぶ

[ 初めての方へ | 一覧(最新更新順) | 全文検索 | 過去ログ ]

 

『クラスモジュールの勉強』(稲葉)

[[20140926115512]]

 ↑のスレッドで便利そうだなと思い、クラスの勉強も兼ねて、
 Mookさんのコードを手本にポップアップメニュー表示用のクラスを書いてみました。

 で、プロパティも一つもなくこれクラスにする必要ないんじゃない?と何となく思えてきて・・・

 そこで、クラスはどのような処理をするときに向いているのか教えていただけませんか?

 自分で分かる範囲で、抽象的ですがこんなことが便利なんじゃないの?と思っています。
 1)インスタンスを複数持つことが出来る
   値を入れる他、入れた値を計算し、結果を持つことが出来るなどコレクション等のオブジェクトを拡張したよう
   な独自のオブジェクトが作れる

 2)ループの処理をまとめられるので視認性がよくなる?

 しかし具体的にどういうときに使ったらいいのか分かりません。

     '標準モジュール
    Sub test()
        Dim Pmenu As clsPopup
        Set Pmenu = New clsPopup
        With Pmenu
            .AddItem Title:="選択A", 子ID:=0, Action:="A", FaceID:=0
            .AddItem Title:="選択B", 子ID:=0, Action:="B", FaceID:=0
            .AddItem Title:="選択C", 子ID:=0, Action:="C", FaceID:=0
            .AddPop Title:="選択D", 子ID:="D"
            .AddPop Title:="選択E", 子ID:="E"
            .AddItem Title:="選択D-1", 子ID:="D", Action:="D1", FaceID:=0
            .AddItem Title:="選択E-1", 子ID:="E", Action:="E", FaceID:=0
            .AddItem Title:="選択D-2", 子ID:="D", Action:="D2", FaceID:=0
            .Rgst
        End With
    End Sub
    Sub Separator(ByVal GetData)
        Select Case GetData
            Case "A"
                ActiveCell.Value = 10
            Case "B"
                ActiveCell.Value = 100
            Case "C"
                ActiveCell.Value = 1000
        End Select
    End Sub

 'シートモジュール
    Private Sub Worksheet_BeforeRightClick(ByVal Target As Range, Cancel As Boolean)
       If Target.CountLarge <> 1 Then Exit Sub
       test
       Cancel = True
    End Sub

 'クラスモジュール
    'clsPopup
    Private Popup As Object
    Private 子 As Collection
    Private Sub Class_Initialize()
        Set Popup = CommandBars.Add(Position:=msoBarPopup)
        Set 子 = New Collection
    End Sub
    Public Sub Rgst()
        Popup.ShowPopup
        Popup.Delete
    End Sub
    Public Sub AddItem(ByVal Title As String, ByVal 子ID As String, ByVal Action As String, Optional ByVal FaceID As Long = 0)
        Dim tmpPop As Object
        If FaceID = 0 Then FaceID = 483
        If 子ID = "0" Then
            Set tmpPop = Popup
        Else
            On Error GoTo 0
            Set tmpPop = 子(子ID)
            If Err > 0 Then MsgBox "存在しないIDです": Exit Sub
        End If
        With tmpPop
            With .Controls.Add
                 .Caption = Title
                 .OnAction = "'Separator """ & Action & """ '"
                 .FaceID = FaceID
            End With
        End With
    End Sub
    Public Sub AddPop(ByVal Title As String, ByVal 子ID As String)
        With Popup
            On Error Resume Next
            子.Add .Controls.Add(Type:=msoControlPopup), CStr(子ID)
            If Err > 1 Then MsgBox "既に子IDが追加されています。": Exit Sub
            On Error GoTo 0
            子(子ID).Caption = Title
        End With
    End Sub

 で実際使って行きたいと思いますので、コードもこうした方がいいんじゃない?など
 色々な書き方を教えていただけたらありがたいです。

< 使用 Excel:Excel2007、使用 OS:WindowsXP >


 とりあえず、雑感まで。

 面白そうですけれど、メニューの構築の部分をどう考えるかでしょうか。
 このあたりは ichinose さんが得意そうですね。

 知らないだけかもしれませんが、メニューの途中に挿入するのが出来れば
 ある程度 Class 化のメリットが出来そうな気がします。
 イメージとしては XML のツリー操作のようなイメージでしょうか。

 あるノード以下の追加、削除、変更、コピー などがあると便利そうです。

(Mook) 2014/09/26(金) 21:39


 Additemではコントロールを追加せず、配列に引数とIdを振って、
 ならび順は別の変数に設定し、Rgstで一気にコントロール追加してしまえば
 並び順の問題解決できそうです!

 そうすれば、Rgst時に引数でId渡して、そのIdのコントロールだけ実装なども出来そうですね

 XMLのツリー操作は、、、分からないので勉強しておきます!

(稲葉) 2014/09/26(金) 22:00


  >プロパティも一つもなくこれクラスにする必要ないんじゃない?と何となく思えてきて・・・
  >そこで、クラスはどのような処理をするときに向いているのか教えていただけませんか?

  クラスモジュールって、オブジェクトの設計書で、そのオブジェクトとは、
  データとアルゴリズムを隠蔽化したもの ですよね?

  このクラスとかオブジェクト指向などという言葉を耳にする以前から、このような構造のプログラムを
  作るようにしなさい という指導を受けていました。

  モジュールを評価する指標に 強度と結合度 というものがあります。
  強度を強く、結合度が弱くなるようにモジュールを作りなさいという設計法があります。
  (マイヤーズの複合設計法)。この設計法で、

 検索してみると、強度の例で情報的強度というモジュールの構造を紹介していますが、これが正にこのオブジェクトの定義
 そのものの構造を指していて、強度の強いモジュールだと評価しています。

 以下は、私がプログラマ一年生のときに最初に作らされた情報的強度のモジュールです。
 実際には、VBAでは、ありませんが、VBAで記述すれば、このようなものだという例です。
 内容は、テキストファイルの読み込みを一括管理するモジュールです。

 Option Explicit
 Private fno As Long
 Function f_open(ByVal fname As String) As Long
    fno = FreeFile()
    On Error Resume Next
    Open fname For Input As #fno
    f_open = Err.Number
    On Error GoTo 0
 End Function
 Function f_get(mydata As Variant) As Variant
    On Error Resume Next
    If EOF(fno) Then
       f_get = 1
    Else
       Line Input #fno, mydata
       f_get = Err.Number
    End If
    On Error GoTo 0
 End Function
 Sub f_close()
    On Error Resume Next
    Close #fno
    On Error GoTo 0
 End Sub

 Fnoというデータを共有する処理をこのモジュールで一括管理するようにしています。

 この構造を情報的強度のモジュールと評価しています。
 稲葉さんが提示押されたクラスモジュールの構造と同じですよね?
 データとアルゴリズムを纏めて一つのモジュールにしたもの
 言い換えれば、「データとアルゴリズムを隠蔽化したもの」ですよね?

 この構造になっていれば、クラスモジュールに入れとけば、便利です。
 理由は、仰れているように、インスタンスを複数持つことが出来る
 これだけとっても、同時に複数のオブジェクトを作成したい時など、クラスにしておけば
 配列を作成するだけで済みます。クラスを使わないで同じことをしようとすれば、アルゴリズムが
 複雑になりますからねえ!!

 ですから、現時点でプロパティがないというのは、関係ないと思いますよ

 このPopupメニューについては、ユーザーフォームのテキストボックス等では、
 右クリックで切り取り、コピー、貼付け というメニューが出ませんよね?
これを実現させるために

[[20100930194658]] 

 こんな投稿をしたことがありました。

 コードを拝見して、さすがですね、私は、大筋良いと思いますよ!!

 細かいところで気になった点は・・・、

 1 Rgstメソッドについて

        Popup.ShowPopup
        Popup.Delete
 とありますが、何故? という印象をもちました。
 せっかく愛をはぐ組んできた男女が 一夜明けたら 別れの朝(高橋真梨子)が聞こえてくるような印象が頭を駆け巡りました。
 例題コードでは 100件ぐらい登録すると、表示時に一瞬ためらった後の表示のような間がありました。

 ユーザーフォーム表示中に使うのとは、違ってクラスモジュールの心配は、理由が不明な変数の初期化というバグ(仕様)が発見されていることです。
 コードでActivexコントロールをシートに生成した後など変数が初期化されるという現象が確認されています。

 変数の初期化については、私も何度か理由が不明な事象で確認しています。
よって、あまり長い時間インスタンスを保持した状態にしないという仕様にしているのかもしれません。
 私は、これに関しては、クラスモジュールをアドン化してしまう方法をとっています。
 運用もそのアドインに対して参照設定はしない手法です。
 この方法だと今のところ、変数の初期化の再現がありません。

 いずれにせよ、ちょっとこの一回でさよなら という仕様が気になりました。

 2 AddItemメソッドの オプション Action について

  .OnAction = "'Separator """ & Action & """ '"
 このコードですが、呼び出すモジュールをSeparator と限定してしまうと
 このSeparatorというプロシジャーとの結合度が強くなり、汎用性が狭まりますから、
 全部をパラメータ化してしまえば どうでしょうか?

 細かい事ですが、ちょっとだけ気になりました。

( ichinose) 2014/09/27(土) 11:00


 ichinoseさん
 コメントありがとうございます。
 返事が遅くなり申し訳ございません。

 >マイヤーズの複合設計法
 こちら検索してみました。
 自分は正直申しますと、文系且つメーカー職なのでしっかり勉強したことがなく、ただ
 「みんなが便利になればいいなー」程度の気持ちで取り組んでいます。
 ですので職業プログラマさんに申し訳ないと思いつつ質問させていただいています。

 本題ですが、オブジェクト指向という括りの中の、構造化プログラミングがわかってないと、効果的な
 オブジェクト指向プログラムは書けない、という理解に落ち着きました。
 これを理解したうえで、
 >2 AddItemメソッドの オプション Action について
 の「結合度が強い」は、戻り値や引数が他のプロシージャ(Separator)に依存しているので、
 なるべく依存しないように工夫しましょうということ、、、でいいんですかね?

 >[[20100930194658]]  
 >こんな投稿をしたことがありました。
 こちらも拝見いたしました。
 参照設定で「システム レジストリへのアクセスでエラーが発生しました。」
http://blog.livedoor.jp/ikasabo/archives/51594432.html
 で転んで、
 「Microsoft Forms2.0 Object Library」が見つからず
http://www.relief.jp/itnote/archives/017881.php

 やっと試せるぞ! と思ったら
http://office.microsoft.com/ja-jp/excel-help/HA010342745.aspx#BMcontrols_on_the_forms_toolbar 現在参照不可

 2010では結局できないみたい?・・・
 別PCの2007で試します!

 >1 Rgstメソッドについて
 >変数の初期化については、私も何度か理由が不明な事象で確認しています。
 これは初めて知りました(汗
 そんなにたくさん入れるつもりではなかったので、考慮に入れる必要はなさそうです。
 各々のPC環境に依存しないブックを目指しているので、ブックごとにクラス設定したいと思います。
 Mookさんのご指摘と合わせて、もう一度書き直してみます。

 ありがとうございました!

(稲葉) 2014/09/28(日) 00:08


[[20100930194658]]

 このスレッドで紹介させていただいたおかげでバグが発見できました。修正しました。
 ここで久しぶりに確認しなかったら、気がつかなかったかもしれません。

 ただ、

 >2010では結局できないみたい?
 修正前でも Excel2010でも例として投稿したコード(シート上にActiveXコントロールのテキストボックス
 を使った例)は、正常に作動していますよ!!

 これ、アドインにしているので、同時に開いている二つのブックがあった場合、また、それぞれが
 ActiveXコントロールのテキストボックスを使用している場合、切り取り、コピー、貼付けメニューを
 表示しなければならないのですが、その箇所に不具合がありました。(修正済み)

 >Sheet1にコントロールツールボックスのテキストボックスを二つ作成してください(TextBox1、TextBox2)。

 が、わかりにくかったでしょうか?

 これ、ActiveXコントロールのテキストボックスです。

 これをご紹介したのは、アドイン化した例と もう一つ CommandbarButtonがクリックされた時の
 処理が稲葉さんの事例とは違っていたので 参考・比較していただくために投稿しました。

 稲葉さん事例だと、クリックした場合は、OnActionプロパティを使ってその後の処理をさせる
 プロシジャー名を登録していますよね?

 投稿したcpypst.xlaでは、CommandbarButtonが提供しているイベントMouseUpで処理を行っています。
 しかも、このcpypst.xlaは、機能的に切り取り、コピー、貼付け という機能に特化しているので、
 すべてアドイン側(クラスモジュール内)で処理しています。

 >マイヤーズの複合設計法
 ソフトウエア関係のマニュアルって、わかりづらいですよね!! 私も書物だけでは、理解は出来なかったと思います。 
 その手の会社ですから、教えてくれる方がいましたからねえ

 クラスモジュールを使う場合、この強度(情報的強度)と結合度を意識すれば、良いオブジェクトが作成できると思いますよ

 >「結合度が強い」は、戻り値や引数が他のプロシージャ(Separator)に依存しているので、
 >なるべく依存しないように工夫しましょうということ、、、でいいんですかね?

 そういうことになります。汎用的なメニューなので 
 項目が決定した後の処理は、このオブジェクトを使用している
 元のプログラムが決めればよいですよね?

 >1 Rgstメソッドについて

 変数の初期化を気にしていないのだとすれば、

  Popup.ShowPopup
  Popup.Delete

 せっかく Addpop Additemを使って構築したメニューを 一回の表示で削除してしまうのは、
 どうしてなのでしょうか?

 クラスのTerminateイベントなどで 削除するのがよいと思いますけどね!!

 ということを

 > せっかく愛をはぐ組んできた男女が 一夜明けたら 別れの朝(高橋真梨子)が聞こえてくるような印象が頭を駆け巡りました。

 なんて記述したのですが、わかりにくかったですね、失礼しました。

 色々、私にもメリットがありました。

( ichinose) 2014/09/28(日) 07:53


 >>2010では結局できないみたい?
 >修正前でも Excel2010でも例として投稿したコード(シート上にActiveXコントロールのテキストボックス
 >を使った例)は、正常に作動していますよ!!
 修正前はなぜか右クリックが反応しておりませんでしたが、修正後で動きました。
 理屈は、すみません、もっと基本を勉強しないとすぐに理解できそうにありません。

 しかし、
 >稲葉さん事例だと、クリックした場合は、OnActionプロパティを使ってその後の処理をさせる
 >プロシジャー名を登録していますよね?
 >投稿したcpypst.xlaでは、CommandbarButtonが提供しているイベントMouseUpで処理を行っています。
 この点については理解できました!
 ありがとうございます。

 >ソフトウエア関係のマニュアルって、わかりづらいですよね!! 私も書物だけでは、理解は出来なかったと思います。 
 >その手の会社ですから、教えてくれる方がいましたからねえ
 >クラスモジュールを使う場合、この強度(情報的強度)と結合度を意識すれば、良いオブジェクトが作成できると思いますよ
 職場内では私以外VBA触れないので、独自オブジェクトを使ったメンテナンスフリーのプログラムを残して
 いきたいと思います!

 ご指摘いただいた別れの朝と結合度の2点について、自分なりに書き直してみました。
 Mookさんから頂いた指摘については、コレクション化した情報の順序でネスト先の順序も考慮しようとして
 どのようにコレクションに情報を入れておくか混乱してしまったので、後日また考え直します!

 ActionにStringでプロシージャ名と引数渡そうとすると、「"」ばっかりになり、視認性が悪くなってしまう
 ので、動的配列として渡そうと考えました。
 この考え方はありですかね・・・?
 TextToColumnsのFieldInfoのイメージです。

 'クラスモジュール
    'clsPopup
    Private Popup As Object
    Private 子 As Collection
    Private Sub Class_Initialize()
        Set Popup = CommandBars.Add(Position:=msoBarPopup)
        Set 子 = New Collection
    End Sub
    Private Sub Class_Terminate()
        '//別れの朝回避
        Popup.Delete
    End Sub
    Public Sub Rgst()
        Popup.ShowPopup
    End Sub

    Public Sub AddItem(ByVal Title As String, ByVal 子ID As String, ByVal Action As Variant, Optional ByVal faceID As Long = 0)
        Dim tmpPop As Object
        '//Action = Array(プロシージャ名,引数,引数,引数・・・)
        Dim Proc As String, Arg As String, Act
        For Each Act In Action
            If Proc = "" Then
                Proc = "'" & Act
            Else
                Arg = Arg & IIf(Arg = "", "", ",") & Chr(34) & Act & Chr(34)
            End If
        Next Act
        Proc = Proc & " " & Arg & " '"

        '//エラー処理
        If faceID = 0 Then faceID = 483
        If 子ID = "0" Then
            Set tmpPop = Popup
        Else
            Set tmpPop = 子(子ID)
            If Err > 0 Then MsgBox "存在しないIDです": Exit Sub
        End If

        '//コンントロールの追加
        With tmpPop
            With .Controls.Add
                 .Caption = Title
                 .OnAction = Proc
                 .faceID = faceID
            End With
        End With
    End Sub
    Public Sub AddPop(ByVal Title As String, ByVal 子ID As String, Optional ByVal Nest As String = "")
        Dim tmpPop As Object
        If Nest = "" Then
            Set tmpPop = Popup
        Else
            On Error Resume Next
            Set tmpPop = 子(Nest)
            If Err > 1 Then MsgBox "ネストさせる子IDがありません": Exit Sub
            On Error GoTo 0
        End If
        With tmpPop
            On Error Resume Next
            子.Add .Controls.Add(Type:=msoControlPopup), CStr(子ID)
            If Err > 1 Then MsgBox "既に子IDが追加されています。": Exit Sub
            On Error GoTo 0
            子(子ID).Caption = Title
        End With
    End Sub

    Dim Pmenu As clsPopup
    '標準モジュール
    Sub test()
        If Pmenu Is Nothing Then
            Set Pmenu = New clsPopup
            With Pmenu
                .AddItem Title:="色塗り", 子ID:=0, Action:=Array("abc", 50), faceID:=0
                .AddPop Title:="入れ子1", 子ID:="D"
                .AddPop Title:="入れ子2", 子ID:="E", Nest:="D"
                .AddItem Title:="計算", 子ID:="D", Action:=Array("aiu", 10, 20), faceID:=0
                .AddItem Title:="ハロウィン", 子ID:="E", Action:=Array("XYZ", "とりっくおあとりーと"), faceID:=0
                .AddItem Title:="メニュークリア", 子ID:=0, Action:=Array("メニュー刷新"), faceID:=0
                .Rgst
            End With
        Else
            Pmenu.Rgst
        End If
    End Sub
    Sub メニュー刷新()
        Set Pmenu = Nothing
    End Sub
    Sub abc(ID As Long)
        ActiveCell.Interior.ColorIndex = ID
    End Sub
    Sub XYZ(ID As String)
        ActiveCell.Value = ID
    End Sub
    Sub aiu(a As Long, b As Long)
        MsgBox a + b
    End Sub

( 稲葉) 2014/09/28(日) 10:12


コメント返信:

[ 一覧(最新更新順) ]


YukiWiki 1.6.7 Copyright (C) 2000,2001 by Hiroshi Yuki. Modified by kazu.