[[20190108115512]] 『コマンドボタンのイベントをクラスで一括処理』(隠居じーさん) ページの最後に飛ぶ

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

 

『コマンドボタンのイベントをクラスで一括処理』(隠居じーさん)

 ○  Excel Q&Aサロン(VBA)で教えていただいた事を流用させていただき
    シート上にマクロ分岐のメニューを作成しようとしています。
http://excelfactory.net/excelboard/excelvba/excel.cgi?mode=all&namber=188364&rev=0
 ○ 今年はクラスを勉強したいと思っています。

 シート名、Sheet1、上にアクテイブXのコマンドボタンを8個配置(VBA)
イベントをクラスで纏め、
アクテイブ(フォーカスをもつ?)なコマンドボタンを赤色に
その他を既定値、若しくは白にしたいのですが。うまく
配色出来ません。なにせ初めてクラス、作りましたので。
基本が間違っているかもしれません。
クリック、とキーダウンは動いています。
※使用方法についてもアドバイスお願いします。
不安点。。。
ループ回しているのですが、testpが実行された時点で
抜けているのか(多分抜けていない気がするのですが^^;)
抜けていないのかよくわかりません。???

 下記に作成したコードを貼り付けますので、ご教授戴ければ幸いです
宜しくお願い致します
つっこみ大歓迎です ^^

 作成したクラス、myButton
Option Explicit
Public WithEvents btn As MSForms.CommandButton
Private btnno As Long
Private s1 As Worksheet
Private id As Long
Private Sub btn_click()
    Application.Run "Module1.testp", Right(btn.Name, 1)
End Sub
Private Sub btn_GotFocus()
    btn.BackColor = &H1000FF
End Sub
Private Sub btn_LostFocus()
    btn.BackColor = &HFFFFFF
End Sub
Private Sub btn_KeyDown(ByVal KeyCode As MSForms.ReturnInteger, ByVal Shift As Integer)
    Set s1 = Worksheets("Sheet1")
    id = Right(btn.Name, 1)
    If Shift = 0 Then
        Select Case KeyCode
        Case vbKeyDown
            KeyCode = 0
            s1.OLEObjects("CommandButton" & (id Mod 8 + 1)).Activate
        Case vbKeyUp
            KeyCode = 0
            s1.OLEObjects("CommandButton" & IIf(id = 1, 8, id - 1)).Activate
        Case vbKeyReturn
            KeyCode = 0
            Application.Run "Module1.testp", id
        End Select
    End If
End Sub
Private Sub Class_Terminate()
    Set btn = Nothing
    Set s1 = Nothing
End Sub
 標準モジュール
Option Explicit
Public Declare Function GetAsyncKeyState Lib "User32.dll" (ByVal vKey As Long) As Long
Sub main()
    Dim s1 As Worksheet
    Dim i As Long
    Dim btn(1 To 8) As myButton
    Set s1 = Worksheets("Sheet1")
    For i = 1 To 8
        Set btn(i) = New myButton
        Set btn(i).btn = s1.OLEObjects(i).Object
    Next
    s1.OLEObjects(1).Activate
    Do
        If GetAsyncKeyState(27) <> 0 Then
            Exit Do
        End If
        DoEvents
    Loop
End Sub
Sub testp(ByVal id As Long)
    MsgBox id
End Sub

 *******************************
もしお試し戴けるなら、コマンドボタン配置コードです。^^;
シート名、Sheet1が対象です。

 Private Sub Ax_Sh_Set()
    Dim i As Long, br As Range, btn()
    Dim sbr
    Dim menustr
    menustr = Array("1: DB 入力", "2: 印   刷", "3: 印刷プレビュー", _
                    "4: EXCELに戻る", "5: 終 了", "6: CSVダンプ", _
                    "7: 予 備 A", "8: 予 備 B")
    sbr = Array("B3:J4", "B7:J8", "B11:J12", "B15:J16", "L3:T4", "L7:T8", "L11:T12", "L15:T16")
    With Worksheets("Sheet1")
        If .OLEObjects.Count > 0 Then
            Call btn_delete
        End If
        For i = 0 To UBound(menustr)
            Set br = .Range(sbr(i))
            ReDim Preserve btn(i)
            Set btn(i) = .OLEObjects.Add(ClassType:="Forms.CommandButton.1", _
                                         Left:=br.Left, Top:=br.Top, Width:=br.Width, Height:=br.Height)
        Next
        For i = 0 To UBound(btn)
            btn(i).Object.Caption = menustr(i)
            btn(i).Object.Font.Size = 16
            btn(i).Object.BackStyle = 1
        Next
    End With
End Sub
Private Sub btn_delete()
    Dim btn As Object
    For Each btn In ActiveSheet.OLEObjects
        btn.Delete
    Next
End Sub

< 使用 Excel:Excel2016、使用 OS:Windows10 >


 私も以前ここで勉強させていただいた時に教えていただいたことを掻い摘んでツッコミ入れさせてもらいますね。

 ■あんまり得意じゃないけど、btnはMSForms.CommandButtonオブジェクトなわけですよね?
 であればGotFocusというイベントはないと思うのですが・・・。
https://kosapi.com/post-4082/

 参照先の掲示板を斜め読みしましたが、色を変える部分はClickイベントとして紹介されていたように
 見えます。

 ■話を戻して、隠居じーさんさんが「アクティブ」と考える状態がわかりません。
 1)マウスをのせたとき?
 2)クリックした処理中?
 3)トグル方式?
http://kabu-macro.com/word/ta-to/toggle_button.html

 1だとすると、MouseOverイベントがなかった記憶があるので、かなり難しいと思います。(MouseMoveイベントで代用?)
 2の場合、クリックイベントに色を付け、他のボタンの色を消す処理をすればよいかと思います。
 3の場合、最初からトグルボタンコントロールを使ったほうが自然かもしれません。

 ■別件で、hatenaさんが提案されていた「使いまわしのきくもの」について、コードを拝見するに
 標準モジュールとクラスモジュールの結合度が高いように思います。
     Application.Run "Module1.testp", Right(btn.Name, 1)
                      ~~~~~~~~~~~~~~この部分とか
 下線部分を含めて、クラスオブジェクトに渡しておけば、呼び出す側(シートのボタン)から
 「クリックしたらどのプロシージャを動かすか」がわかっていいですよね。
http://www.kogures.com/hitoshi/webtext/kj2-module/index.html

 以上、気になった点でした。
(稲葉) 2019/01/08(火) 15:44

 GetFocusイベントは、OLEObjectのイベントであり、MSForms.CommandButton型にはないです。

 じゃあ、btnをOLEObject型にすればいいじゃないか、となりますが、
 実際やってみると、実行時エラーになります。

 ネットで検索すると、どうもWithEventsが付いているとダメらしい。
 【Accessing OLEObject events in Excel VBA using custom class】
https://stackoverflow.com/questions/10761973/accessing-oleobject-events-in-excel-vba-using-custom-class

 【このコンポーネントは、このイベント セットをサポートしていません (エラー 459)】
https://docs.microsoft.com/ja-jp/office/vba/language/reference/user-interface-help/this-component-doesn-t-support-this-set-of-events-error-459?f1url=https%3A%2F%2Fmsdn.microsoft.com%2Fquery%2Fdev11.query%3FappId%3DDev11IDEF1%26l%3Dja-JP%26k%3Dk(vblr6.chm1000459)%3Bk(TargetFrameworkMoniker-Office.Version%3Dv16)%26rd%3Dtrue
 ObjectEvent を発生させるために必要な型情報を実行時に使用できないため、
 プライベートの UserControl では WithEvents はサポートされない。

 どうも、理屈通りにはいかないようです。

 KeyDownイベントで、どのボタンをアクティブにするかは分かっているので、
 逆算して、どのボタンがそれまでアクティブだったかも判断できると思いますので、
 KeyDownイベントの方に色の着脱の仕組みを盛り込んだらどうでしょうか?

( 半平太) 2019/01/08(火) 16:58


 稲葉さま
早々のご返信ありがとうございます。
■1:
やはり! w ^^; 無いのでしょうね。、ただ、シートモジュールで【】▼オブジェクトを
commandbutton1にして【】プロシージャー▼をクリックすると有って
Private Sub CommandButton1_GotFocus()
End Sub
が自動で記述されたりするもので、でもなぜかclassモジュールだと稲葉さま、のご指摘通り
表示一覧には有りませんでした。
■2:
矢印キーで選択、リターンが押せる状態を想定いたしておりました。
■3:
すうごぉ〜く勉強になりそうですね。チラ見しかできていませんが今から良く読んでみます。
サイト紹介有難うございました。
クラス側に掘りこんじゃへってことでしょうかね。

 イベント。。。ループなど必要ないですよね。
シートモジュールににイベントをボタン(8)*(4イベント、
クリック、ゴットフォーカス、ロストフォーカス、キーダウン、分、
記述方法では思い通りのものが出来ております。
クラス使用分は
ループ外せば動きません。
いま何かきつねにつつまれたような錯覚に陥っています^^;
クラスの書き方、使い方。。。あっていますでしょうか。な〜んか変だな。。。
とか思い、自信が持てない状況です。A^_^;
配色についてはなにか他の方法を考えてみます。
今赤色のボタン以外全て色を変えるとか。。。
貴重なお時間をおさきいただき有難うございます。
(隠居じーさん) 2019/01/08(火) 17:02

 半平太さま
いつもおせわになります。
>>KeyDownイベントの方に色の着脱の仕組みを盛り込んだらどうでしょうか?
はい
やってみます。
解説サイトのご紹介、有難う御座います。
いまからゆっくり読んでみますね。
とりいそぎ
お礼まで。
有難うございました。m(__)m

(隠居じーさん) 2019/01/08(火) 17:06


 >クラス使用分は
 >ループ外せば動きません。
 >いま何かきつねにつつまれたような錯覚に陥っています^^;

 シートにあるボタンは、常時、実存していますが、
 クラスのボタンは、mainが動いている間(=ループが回っている間)だけ
 存在しているものですからねぇ・・

 まぁ、myBtnの変数宣言をプロシージャレベルからモジュールレベルへ格上げ(?)すれば、存在はキープされます。

 ただし、以前、別のトピックで話題になった通り、
 「意図しないタイミングで保持されていた Public 変数の値が破棄される」ことがあります。

 なので、常に実存している、とまでは言えないのが悩みの種です。

( 半平太) 2019/01/08(火) 19:10


 半平太さん
 勉強になりました。
 >じゃあ、btnをOLEObject型にすればいいじゃないか、となりますが、
 >実際やってみると、実行時エラーになります。
 私の指摘はいつも的外れですね。

 隠居じーさんさん
 ちょっと思い出しながら自分ならこうするかなというの作りました。
 だけど、シートモジュールレベルの変数って、よからぬタイミングで消えた記憶があるので
 たぶん、本番では使わないかな・・・

 楽しい課題をありがとうございました。

    '◆シートモジュール
    Option Explicit
    Private dic As Object
    '============================================================
    'シートがアクティブになった時に、シートレベル変数のdicにクラスを配列として入れる
    '============================================================
    Private Sub Worksheet_Activate()
        Dim i As Long
        Dim obj As OLEObject
        Set dic = CreateObject("Scripting.Dictionary")
        i = 1
        For Each obj In Me.OLEObjects
            dic.Add i, New clsButton
            With dic(i)
                Set .obj = obj.Object         'OLEオブジェクトをクラスに登録
                Set .Parent = Me              'このシートをクラスに登録
                .Method = "CallBack"          'このシートのプロシージャをクラスに登録
                .ID = i                       'ボタンのナンバーをクラスに登録
                .Caption = obj.Object.Caption 'ボタンのキャプションをクラスに登録
            End With
            i = i + 1
        Next obj
    End Sub

    '============================================================
    'シートがアクティブじゃなくなった時に、dicとクラスを開放する
    '============================================================
    Private Sub Worksheet_Deactivate()
        Set dic = Nothing
    End Sub

    '============================================================
    'クラスから呼び出されるプロシージャ
    '============================================================
    Public Sub CallBack(ParamArray arg() As Variant)
        'arg引数表
        '0:イベント名
        '1:ID
        '2:コントロールオブジェクト
        '3:コントロールキャプション
        '4:キー
        Select Case arg(0)
            Case "Click"
                MsgBox arg(3) & "が押されました"
            Case "KeyDown"
                Call KeyDownEvent(arg(4), arg(1))
        End Select
    End Sub

    '============================================================
    'キーダウンイベントの処理
    '============================================================
    Private Sub KeyDownEvent(k As Variant, ID As Variant)
        Dim moveID As Long
        Select Case k
            Case vbKeyLeft:   If ID > 4 Then moveID = -4
            Case vbKeyRight:  If ID < 5 Then moveID = 4
            Case vbKeyUp:     If Not (ID = 1 Or ID = 5) Then moveID = -1
            Case vbKeyDown:   If Not (ID = 4 Or ID = 8) Then moveID = 1
            Case vbKeyReturn
                With dic(ID)
                    Call CallBack("Click", ID, .obj, .Caption)
                End With
        End Select
        dic(ID).obj.BackColor = &HFFFFFF
        With dic(ID + moveID).obj
            .Activate
            .BackColor = &H1000FF
        End With
    End Sub

    '◆クラスモジュール 名前clsButton
    Option Explicit
    Public WithEvents obj As MSForms.CommandButton

    '============================================================
    '共通の設定
    '============================================================
    Public Parent  As Object
    Public ID      As Long
    Public Method  As String
    Public Caption As String

    '============================================================
    'イベント一覧
    '============================================================
        'CallBack引数表
        '0:イベント名
        '1:ID
        '2:コントロールオブジェクト
        '3:コントロールキャプション
        '4:キー
    Private Sub obj_Click()
        CallByName Parent, Method, VbMethod, "Click", ID, obj, Caption
    End Sub

    Private Sub obj_KeyDown(ByVal KeyCode As MSForms.ReturnInteger, ByVal Shift As Integer)
        CallByName Parent, Method, VbMethod, "KeyDown", ID, obj, Caption, KeyCode
    End Sub

    '============================================================
    '使った後の処理
    '============================================================
    Private Sub Class_Terminate()
        Set obj = Nothing
        Set Parent = Nothing
    End Sub

(稲葉) 2019/01/08(火) 19:12


 半平太さん 、稲葉さん
再度、有難う御座います。
半平太さん
>>GetFocusイベントは、OLEObjectのイベントであり、MSForms.CommandButton型にはないです。
ご紹介いただいた英語サイトは。。。眺めるだけで終了致しました。^^;
クラスでは使えないと言う事で理解致します。
ループの件、解りやすいご説明ありがとうございます。
稲葉さん、コード、有難う御座います。
ゆっくり、謹んで拝見させて戴きます。
まだ試せておりませんが、明日、試して見ます。

 本校の過去ログの(クラスの件)
稲葉さんと、半平太さん、ichinoseさん等とのやりとりも拝見させていただきましたが
正直、私のレベルではついていけませんでした。ただ、Public変数消え去り対策になると推測(わたしの)される、参照設定、せずにアドインを使う方法はすごく興味があります。
どぉするのでしょうか。
消え去り対策としては参照設定でも十分なのでしょうか。
グローバル変数消え去り問題、はクラスモジュールも対象外では無いのでしょうか。
質問ばかりで済みません。お手すきの時にでも、ご教授賜れば幸甚です。
m(_ _)m

(隠居じーさん) 2019/01/08(火) 22:42


 (隠居じーさん) 2019/01/08(火) 22:42の
>>グローバル変数消え去り問題、はクラスモジュールも対象外では無いのでしょうか。
については
過去ログ調べていましたら
マイクロソフト様の説明引用箇所に下記の様な文言があったかと記憶しております。
>補足 : この文書で説明する Public 変数とは、
>すべてのモジュール レベル変数および静的変数を指します。
ということは
クラスよ、おまえもか!
との理解でよろしいのでしょうか。
だとすると
^^;www
クラスはメンバ変数ってモジュールレベルそのものなのでは。。
今回のような場合とかユーザーフォームを使う場合、イベント処理もどっさり。。。
こわいですね〜おそろしいですね〜
とりあえず
以前、別のトピックで教えていただいた
アドイン、参照設定で対応しようと思います。
え、でもすると
アドインにクラス書かないといけないのでしょうか
(。。。危うきに。。。とか)隠居じーさん 
                              ↑
 漠然と【え!消えるの】と危機感はあるものの、何がどう危ないのかあまりわかっていないA^_^;でした。
洋上で羅針盤をなくし、漂う小舟をなにとぞ港まで。。。。。 m(__)m

(隠居じーさん) 2019/01/09(水) 07:52


質問のクラスモジュールのコードですが、
Set s1 = Worksheets("Sheet1")
というようにシートを参照していますが、そうすると、
Sheet1 でしか使えません。
また、ボタンも8個固定という前提のコードになっています。

これでは、クラスにする意味がほとんどないです。
Sheet1モジュールにコードを記述したほうがよほどシンプルかつメンテナンスしやすいです。

クラスにするからには、複数のブック、シートで利用できて、ボタンの数も自由に設定できるようにした方がいいでしょう。

今回の要件は、
シート上のActiveXコマンドボタンをグループ化して、その中の一つだけを選択できるようにする。
選択はクリックだけでなくキーボードの上下キーでも移動できるようにして、
Enterキーでクリックと同じ処理を実行する。
ということですよね。

VBA - VBAのボタンの色に関して|teratail
https://teratail.com/questions/162580#reply-243205

上記の私の回答をもとにキーボードでも選択できるように改修してみました。

 ◆クラスモジュール selectButton
Option Explicit
Private OleObj As OLEObject
Private WithEvents btn As MSForms.CommandButton
Private Slected_ As Boolean    '選択
Private ParentGroup_ As SelectButtonGroup
Private Index_ As Long

 'クラスにコマンドボタンと属する親グループ(SelectButtonGroup)を登録する。
Public Sub setBtn(ByVal Obj As OLEObject, ByVal Parent As SelectButtonGroup, Index As Long)
    Set OleObj = Obj
    Set btn = Obj.Object
    btn.BackColor = vbButtonFace
    Set ParentGroup_ = Parent
    Let Index_ = Index
End Sub
 '選択状態の設定
Public Property Let Slected(ByVal new_Slected As Boolean)
    If Slected_ <> new_Slected Then
        Slected_ = new_Slected
        If Slected_ Then
            btn.BackColor = vbRed
            OleObj.Activate
        Else
            btn.BackColor = vbButtonFace
        End If
    End If
End Property
Private Sub btn_Click()
    ParentGroup_.SelectIndex = Index_
End Sub
Private Sub btn_KeyDown(ByVal KeyCode As MSForms.ReturnInteger, ByVal Shift As Integer)
    If Shift = 0 Then
        Select Case KeyCode
        Case vbKeyDown
            KeyCode = 0
            ParentGroup_.SelectIndex = Index_ + 1
        Case vbKeyUp
            KeyCode = 0
            ParentGroup_.SelectIndex = Index_ - 1
        Case vbKeyReturn
            KeyCode = vbKeySpace
        End Select
    End If
End Sub

 ◆クラスモジュール SelectButtonGroup
Option Explicit
Private BtnGrp As Collection
Private SelectIndex_ As Long '選択されているボタンのインデックス
 '選択されるボタンを設定
Public Property Let SelectIndex(ByVal newIndex As Long)
    If SelectIndex_ > 0 Then
        BtnGrp(SelectIndex_).Slected = False
    End If
    If newIndex < 1 Then
        SelectIndex_ = BtnGrp.Count
    ElseIf BtnGrp.Count < newIndex Then
        SelectIndex_ = 1
    Else
        SelectIndex_ = newIndex
    End If
    BtnGrp(SelectIndex_).Slected = True
End Property
 '選択されているボタンのインデックスを取得
Public Property Get SelectIndex() As Long
    SelectIndex = SelectIndex_
End Property
 'コマンドボタンをクラスに登録
Public Sub Add(ByVal OleObj As OLEObject)
    Dim selBtn As selectButton
    Set selBtn = New selectButton
    selBtn.setBtn OleObj, Me, BtnGrp.Count + 1
    BtnGrp.Add selBtn
End Sub
Private Sub Class_Initialize()
    Set BtnGrp = New Collection
End Sub
Private Sub Class_Terminate()
    Dim selBtn As selectButton
    For Each selBtn In BtnGrp
        Set selBtn = Nothing
    Next
    Set BtnGrp = Nothing
End Sub

 '◆ThisWorkbookモジュール
Option Explicit
Private MenuBtnGrp As SelectButtonGroup
Private Sub Workbook_BeforeClose(Cancel As Boolean)
    Set MenuBtnGrp = Nothing
End Sub
Private Sub Workbook_Open()
    Dim i As Long
    With Worksheets("Sheet1")
        Set MenuBtnGrp = New SelectButtonGroup
        For i = 1 To 8
            MenuBtnGrp.Add .OLEObjects("CommandButton" & i)
        Next
    End With
    MenuBtnGrp.SelectIndex = 1
End Sub

 '◆Sheet1モジュール
Option Explicit
Private Sub CommandButton1_Click()
    MsgBox "コマンド1"
End Sub
Private Sub CommandButton2_Click()
    MsgBox "コマンド2"
End Sub
Private Sub CommandButton3_Click()
    MsgBox "コマンド3"
End Sub

(hatena) 2019/01/09(水) 10:29


 hatenaさん
おはようございます。^^
コードありがとうございます。
>>今回の要件は、
につきましてはご指摘のとおりです。

 いま
稲葉さんのコードを印刷して拝見いたしておりました。
コールバック関数、さらにそのサブルーチンからコールバックを再帰、等
感嘆、感激しておりました。何とかソースは追えるものの、いざ自分が
コーディング!となれば、なかなか思いつきません。^^;
すごく勉強になります。
後ほど
hatenaさんのコードをゆっくり、勉強させて戴きます。
取り急ぎ御礼まで。
半平太さま、稲葉さま、hatena様、
お忙しい中、貴重な情報を賜り恐縮でございます。
深く感謝もうしあげます。ありがとうございました。

(隠居じーさん) 2019/01/09(水) 11:18


 >クラスよ、おまえもか!
 >との理解でよろしいのでしょうか。

 その通りだと思います。

 >クラスはメンバ変数ってモジュールレベルそのものなのでは。。
 >今回のような場合とかユーザーフォームを使う場合、イベント処理もどっさり。。。
 >こわいですね〜おそろしいですね〜

 ちょっと意味が分からないですが、心配し過ぎじゃないですか?

 > 漠然と【え!消えるの】と危機感はあるものの、

 何せ、「意図しないタイミングで・・」と言っているんですから、漠然とならざるを得ないです。

 ichinoseさんのアドバイスは、このスレですよね?

 『モジュール内での変数について』(ゆき夫) 
 [[20100731000846]]
 http://www.excel.studio-kazu.jp/kw/20100731000846.html

 ichinoseさんの対策はおおむね以下かと。
 ---------------------------------------------------------------
  一般的な対策としては、できるだけ実データで保持する。
  例えば、シートオブジェクトであっても、シート名をどこかに(EX.セル)文字データとしてメモしておけば、
  必要な時に再構築(再参照)できる。

  上述代替方法が無い場合、
  (例えば、イベントを有しているオブジェクト等)

  「別ブック内に変数だけを配置する」

  Book1がメインプログラムがあるブックに対し、 Book2が変数だけを配置するブックだとすると、

  Book2のThisWorkbookのモジュールに

  Private bk as workbook

  Book1にて

  Sub AppSet() 
     Set Workbooks("Book2").bk=thisworkbook
     MsgBox workbooks("book2").bk.name
  end sub

  いまところ、変数を別ブックに配置し、そのブックは、非表示又は、専用アドインブックにしておく
  ことで値を保持してくれています。

  これで大丈夫という結論ではありませんが、別ブックに変数を配置する方法も試してみてください。
 -------------------------------------------------------------

 以上に従うと、

 1.別ブックを作る(仮にブック名を shelter とする)

 2.shelterブックのThisWorkbookモジュールに以下の変数宣言をする
    Public bk As Workbook
        ↑
   ※ichinoseさんの記述ではPrivateですが、それでは動かないのでPublicにする

 3.上記ブックをアドインブックとして保存する。

 4.エクセルのオプションでshelterのアドインを有効にする

 5.本体のプログラムがある方に

    Sub AppSet()
        Set Workbooks("shelter.xlam").bk = ThisWorkbook '自ブックのオブジェクトをshelterの変数に格納する
    End Sub

 これを実行して置けば、絶対大丈夫とは言わないが、
 こちらのブックのモジュールレベルの各変数の値は勝手に消失しない

 ・・と言っているのではないか・・

 そう考えないと、ThisWorkBookを退避用に格納する意味が分からなくなります。
 (ThisWorkbookは常に実存するので、消える訳ないので)

 本人に解説してもらえるといいんですが、最近、ichinoseさんの訪問は無いですからねぇ・・

 まぁ、効果の程も確かめようがないので、
 今、考えついたもっと確実なアイデア・・
           ↓
 よく使うコマンドボタン(無ければ、「開始」とか「リセット」とかを新設して)、
 その特殊ボタンは従来通りの方法でのイベントも処理させ、
 そこでクラス配列を再組成させるように組んどけば確実。

 ユーザーなんて、おかしくなると「あれ?」とか言いながら、そこらじゅうの
 ボタンをクリックし始めるので、その動物的習性を利用する。

( 半平太) 2019/01/09(水) 13:44


 >ユーザーなんて、おかしくなると「あれ?」とか言いながら、そこらじゅうの
 >ボタンをクリックし始めるので、その動物的習性を利用する。
 今年の初笑い!

 たまに挙動がおかしくなるプログラムがあるので仕込んでみます。

(稲葉) 2019/01/09(水) 14:29


 半平太さん
【ichinoseさんの対策】解説ありがとうございます。
もういちど読み直してみます。

 わたしもおかしく成ったらやたらボタン押したりしてます、@@:(笑)

 プログラムそのものをこっそり、再起動させる。?
でしょうか(開始メソッドをもう一度呼び込むとか)

(隠居じーさん) 2019/01/09(水) 15:25


 皆様、おはようございます^^
稲葉さま、hatena様、ご提示いただいた、コード、使わせていただきました。
さすがですね。感動です。
Callbyname って知りませんでした。^^;
今後、クラスの作成にあたり、よきお手本とさせていただきます。
矢印キーの左、右でも左右に上下で1〜8,8〜1、循環、見たいに
お二方の矢印キーの動きを合わせたような動きに変更してみます。
ご教示いただきました、全ての皆様にあらためてお礼申し上げます、ありがとうございました。
確かにこれだとボタンがもっと増えても、シートモジュールに、山ほどイベントプロシジャーを
書かなくてすみますね。少しですがクラスの便利さみたいなのが解ったような気がします。
半面、難しさも。。。

m(_ _)m

(隠居じーさん) 2019/01/10(木) 10:58


 いいお手本が得られて良かったですね。

 ところで、このオブジェクトはどうなっちゃたんですか?
          ↓
 Dim btn(1 To 8) As myButton

 それがないと、私には分かりにくくてしょうがないんですけど。
  コレクションに入れたぁ? でも分かりにくいことに変わりはないなぁ・・

 コレクションに入れないといけないんですかね?

( 半平太) 2019/01/10(木) 17:43


 ↑
 すみませーん。 m(__)m

 私が分かりにくいだけかも知れないので、無視してください。

( 半平太) 2019/01/10(木) 18:06


 コレクションに入れる必要、まったくありませんでした。
 おっしゃる通り、型宣言でクラスを指定したほうが、可読性よくなりました。

 ご指摘ありがとうございます。
(稲葉) 2019/01/10(木) 18:21

クラスにするメリットはなんでしょうか。

隠居じーさんWrote

確かにこれだとボタンがもっと増えても、シートモジュールに、山ほどイベントプロシジャーを 書かなくてすみますね。

ということですね。

今回の質問は下記のような仕様です。
シート上のActiveXコマンドボタンをグループ化して、その中の一つだけを選択できるようにする。
選択はクリックだけでなくキーボードの上下キーでも移動できるようにして、
Enterキーでクリックと同じ処理を実行する。

この仕様の場合、キーボードイベントとクリックイベントに結構な量のコードを記述する必要があります。

稲葉さんのクラスは、このコードをクラスからのコールバック関数としてシートモジュールに記述することで一つに纏めてます。
同じ仕様のボタングループを別のシートやブックで使用したい場合、その都度、コールバック関数を記述する必要があります。

このコードもクラスの方にいれておけば、シートモジュールには、
クラスに登録するコードだけですみます。
私のクラスはこれを目指して作成しました。
このような設計にしておけば、もし仕様の変更、機能の追加があったとき、クラスモジュールだけを変更すればすみます。

逆に言えば、この仕様のボタンを一か所だけでしか使わないのなら、クラスにするメリットはほとんどありません。

キーボードイベントに記述するコードをSubプロシージャとして取り出して、
キーボードイベントからCallするようにするぐらいでいいかと思います。
その方か分かりやすいと思います。

ボタングループ内のボタンの数は自由に設定できるようにするには、
ボタンのクラスと、それを複数登録するボタングループのクラスが必要になりました。
そのために、クラスの構成はかなり複雑になりました。

あちこちで使いまわさないと割にあわないとも言えます。

ちなみに、私の場合、Workbook_Open でクラスを登録するようにしましたが、
稲葉さんのは、Worksheet_Activate でクラス登録してます。

私もどちらにしようか迷ったのですが、
このボタンを配置したシートはメニュー的なもので、ここで処理を選択して他のシートで作業して、
また、ここに戻って処理を選択するという使い方かと思いましたので、
戻るたびに初期化されるのはちょっと違うかなという気がしたので ThisWorkbookの方に記述しました。
(hatena) 2019/01/10(木) 22:13


 hatenaさん
こんばんは ^^
当初クラスを一度作成してみようと思った動機がご推察の様な事でしたので。
あまり、深く、考えていませんでした。
確かに、どのシートにも、対応出来、個数にも対応可能だと、一段と汎用性はアップ、
どのBOOKのどのシートでも、メニューがすぐに作れますね。
詳細にわたる、解りやすいご説明、並びに、クラス設計の大切さも教えていただき。
ありがとうございます。大変勉強になります。
私のスキルでは、仮に設計段階で分かっていたとしても、組めなかったと思います。
クラスの中で他のクラスを使おうとは多分考えが及ばなかったのではと思います。
当初、チラッと。。。シート名、と、個数と、表示タイトルをパラメーターにとる
メニュー自動作成クラス。。。え! そんなの、いきなり、無理無理
無謀なことを考えるでない。。。( ̄▽ ̄)。。。
とか、頭の片隅をよぎっていました。(笑)

 また、とんでもないクラス(専門家の方がみれば、腹抱えて笑い出すよ〜なの)
作るかもしれませんが、こちらで、アップさせていただく
機会などありましたら。突っ込みお願いいたします。ありがとうございました。
感謝致します。m(_ _)m

(隠居じーさん) 2019/01/10(木) 23:30


 隠居じーさん(敬称略で呼ばせていただきます。)

 こちらこそ、興味深い題材を提供していただいて、
楽しく取り組めました。
作成するなかで、いろいろ勉強にもなりました。
感謝します。

 メニュー自動作成クラス。。。私もチラッと思いました。

 具体的には、SelectButtonGroup クラスに
ParentSheetプロパティとCreateMenuメソッドを追加して、
下記のようなコードで生成して登録する感じかなぁ?

 Private MenuBtnGrp As SelectButtonGroup

 Private Sub Workbook_Open()
    Set MenuBtnGrp = New SelectButtonGroup
    MenuBtnGrp.ParentSheet = Worksheets("Sheet1")
    MenuBtnGrp.CreateMenu "1: DB 入力", "B3:J4"
    MenuBtnGrp.CreateMenu "2: 印   刷", "B7:J8"
    '中略
    MenuBtnGrp.SelectIndex = 1
End Sub

 簡単に実装できそうですので、ぜひ、トライしてみてください。
(hatena) 2019/01/11(金) 00:33

 稲葉さん

 指摘とまで受け取られると恐縮です。

 隠居じーさんの目線(考え方)で考えれば、 
 これを生かして話が進められないか、と思ったことから発しています。
  ↓
 Dim btn(1 To 8) As myButton

 クラスの話になると、必ず首をつっこんでくる人種がいるんですが(まぁ、私もその一人ですけど)
 今回は、誰も出てこないですね。鬼籍に入っちゃったんですかねぇ。

 その人種は必ず、小難しい理屈をこねて、クラスの敷居を高くしちゃうんですよねぇ。

 私のスタンスは「標準モジュールやシートモジュールなどに便利なモジュールがもう1種類追加されただけ。」
 あれこれMustを並べるより、自分で使いこなせるようになってもらうのが先決、と思っています。

 hatenaさんの「あちこちで使いまわさないと割にあわない」且つ分かりにくいもののなんて掲示してどうすんの?
 と思っていたら、隠居じーさんの構想にも有った、と聞いて、凄い読みだなぁと思っています。

 ・・にしても、これはがっかりだなぁ。結局、そのイベント使うの? と思っちゃいます。
         ↓
 > '◆Sheet1モジュール
 >Option Explicit
 >Private Sub CommandButton1_Click()
 >    MsgBox "コマンド1"
 >End Sub

 なら、クラスを使う意味を感じないなぁ、稲葉さんのコールバック方式の方がしっくり来る、私としては。

( 半平太) 2019/01/11(金) 08:17


 おはようございます。
多分
> '◆Sheet1モジュール
 >Option Explicit
 >Private Sub CommandButton1_Click()
 >    MsgBox "コマンド1"
 >End Sub
は
わたしに、わかりやすくするために、分岐はこんな感じでなので。
後は、クラスに入れるなり、そのまま、残り(3個分記述なので)5個分コード書いて使うなり
好きなよ〜にしろ。
って
意味なのかなって思っていました。
hatenaさんにお聞きしないと真実はわかりませんが。。。^^;
さて
メニュー自動作成クラス。。。
私にとっては今もってエベレスト登山!みたいなものですが
hatenaさん、稲葉さん、から戴いたコード、並びにヒント、半平太さんに教えていただいた。
モジュールレベルの変数対策をよく勘案、テストなどして、気長に、挑戦してみます。
今年は研究課題が出来、楽しめそぉです。
でわ
m(_ _)m

(隠居じーさん) 2019/01/11(金) 09:10


 半平太さん

 遠回しにいわれて気づかなければそれまでですけど、答えがあっていたようでよかったです。

 >あれこれMustを並べるより、自分で使いこなせるようになってもらうのが先決、と思っています。
 こちらは同感です。
 先生方には感謝いたしております。

 隠居じーさんさん
 >わたしに、わかりやすくするために、分岐はこんな感じでなので。
 >後は、クラスに入れるなり、そのまま、残り(3個分記述なので)5個分コード書いて使うなり
 >好きなよ〜にしろ。
 違うと思いますよ。

 クリックイベントはこれだけで、クラスモジュールから他モジュールへの接点は設定されていませんから、
 おそらく私のコードを組み合わせて、

コールバックくらい自分で完成させなさい

 という無言の激励だと思います。

 クリックイベント
 >Private Sub btn_Click()
 >   ParentGroup_.SelectIndex = Index_
 >End Sub

 クラスから他モジュールへの接点
 コマンドボタンしか渡してないので、イベントを拾っても、クラスモジュールからは、どのモジュールの
 どのプロシージャを実行しなければいけないかわかりません。
 >       Set MenuBtnGrp = New SelectButtonGroup
 >       For i = 1 To 8
 >           MenuBtnGrp.Add .OLEObjects("CommandButton" & i)
 >       Next

(稲葉) 2019/01/11(金) 10:28


 おはようございます。
> > '◆Sheet1モジュール
>  >Option Explicit
>  >Private Sub CommandButton1_Click()
>  >    MsgBox "コマンド1"
>  >End Sub
>  なら、クラスを使う意味を感じないなぁ、稲葉さんのコールバック方式の方がしっくり来る、私としては。

 この部分に関しては、使いまわせるものではない、ので、コールバック方式にしなくてもいいのでは、
という感じでそうしました。クラスは、共通のイベントはクラス側に書いて、個別のイベント処理は
このように使う側で書けるのがメリットの一つと思っています。

稲葉さんは、CallByName でクラスのカスタムイベントを実装していましたが、
実は、RaiseEventステートメント でカスタムイベントを実装する方法があります。
これを使うともっとシンプルにコーディング可能です。

 VBA クラスモジュールに自作のEventを実装する - t-hom’s diary
https://thom.hateblo.jp/entry/2016/10/13/005105

 ただ、私のクラスは、 selectButtonGroup と selectButton の2段構成になっていますので、
難易度は高くなりますので、実装するのは控えてました。

 話題になったので、私のクラスに クリックイベントを実装してみます。

 selectButtonGroup と selectButton の2段構成になっていますが、
selectButton はコレクションに格納するのでそこでイベントは実装できません。

 selectButtonGroup にカスタムイベントを実装して、
selectButton のクリックイベントから、それをコールするようにします。

 クラスモジュール selectButtonGroup
Option Explicit
Private BtnGrp As Collection
Private SelectIndex_ As Long    '選択されているボタンのインデックス
Public Event Click() 'クリックイベントの実装

 '子クラスから呼ばれるイベント励起メソッド
Public Sub RaiseClick()
    RaiseEvent Click
End Sub

 以下略

 これでselectButtonGroupにカスタムイベントが実装できます。

クラスモジュールselectButtonには、※のコードを追加します。

Private Sub btn_Click()

    ParentGroup_.SelectIndex = Index_
    ParentGroup_.RaiseClick '※親クラスのクリックイベントを呼び出す
End Sub

'ThisWorkbook モジュール
Option Explicit
Private WithEvents MenuBtnGrp As SelectButtonGroup 'WithEvents を付ける

Private Sub MenuBtnGrp_Click()

    MsgBox MenuBtnGrp.SelectIndex & "番目のメニューがクリックされました。"
End Sub

 通常のイベントプロシージャと同様に、

 Private Sub クラス変数名_イベント名()

 という記述になります。

 「「あちこちで使いまわさないと割にあわない」且つ分かりにくいものの」
という指摘はごもっともです。
しかし使いこなせれば、それをうわまわるメリットもあると思っていますし、
別の掲示板でのやりとりからの
流れてこちらにきているのですが、それを踏まえて、
すぐには理解できなくても、今後の役にたつことは
あるのかなと思ってコード提示しました。
(hatena) 2019/01/11(金) 10:40

 こんにちは ^^
>>コールバックくらい自分で完成させなさい
はは〜〜〜〜 m(_ _)m
おおせのままに。
hatenaさん
さらなる、アドバイス。ありがとうございます。
さまざまな方法があるのですね。
いずれにしましても。まずは
諸先生のロジックのさらなる研究から始めようとおもいます。A^_^:

(隠居じーさん) 2019/01/11(金) 13:05


 hatenaさん

 > 「あちこちで使いまわさないと割にあわない」且つ分かりにくいもの

 すみません、隠居じーさんにはニーズがあるとのことなので、割に合うクラスと認識を変えております。

 ・・で、'ThisWorkbook内の自作イベントプロシージャで全ボタンに対して
 柔軟な処理ができる仕掛けになったので、しっくり来ています。

 大変失礼いたしました。 m(__)m

( 半平太) 2019/01/11(金) 14:48


コメント返信:

[ 一覧(最新更新順) ]


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