Excel (VBA)

Excel VBAに関するフォーラムです。
  • 解決済みのトピックにはコメントできません。
このトピックは解決済みです。
質問

 
(指定なし : Excel 2016)
【確認】UserFormコントロールの動的イベント実装について
投稿日時: 22/07/09 22:30:44
投稿者: たーぼー
投稿者のウェブサイトに移動

お世話になります。
 
開発中に見つけた現象が既知のものか気になって投稿しました。
 
状況再現用の最小構成を以下に示します。
 
<構成>
参照設定の追加
 ・Microsoft Forms 2.0 Object Library
フォームモジュール
 ・UserForm1
クラスモジュール
 ・Class1
 
<コード>
-------------------------------------------------------------
<Class1>
Option Explicit
 
Public WithEvents Button1 As MSForms.CommandButton
 
Private Sub Button1_Click()
 
    Debug.Print "クラスモジュールに書いた処理です"
 
End Sub
-------------------------------------------------------------
<UserForm1>
Option Explicit
 
Private WithEvents myButton As MSForms.CommandButton
Private myClass As Class1
 
Private Sub UserForm_Initialize()
 
    Set myButton = Me.Controls.Add("Forms.CommandButton.1", "myButton", True)
    Set myClass = New Class1
    Set myClass.Button1 = myButton
 
End Sub
 
Private Sub myButton_Click()
 
    Debug.Print "フォームモジュールに書いた処理です"
 
End Sub
-------------------------------------------------------------
 
ユーザーフォームのビジュアルエディタ上には何も配置しなくて構いません。
 
上記構成を作ってからUserForm1をF5キーで起動し、
表示されたボタンを1回クリックすると、イミディエイトウィンドウには
 
> フォームモジュールに書いた処理です
> クラスモジュールに書いた処理です
 
と表示され、2つイベントプロシージャーがワンクリックで実行されたことが分かります。
 
これは、言い換えれば、あるコントロールに、同時に複数のイベントプロシージャーを
紐付けることが可能になったと言えるのではないでしょうか。
 
応用すれば、
・所定のイベントを基本機能として持ち、
 状況に応じて、別の機能を自由に追加できるコントロール
 
・ユーザー側からの操作によって、対応するイベントプロシージャーが切り替わるコントロール
 
などの実装が可能になります。
 
後者は.Netで言えばRemoveHandler、AddHandlerに該当する機能の実現になるかと思います。
 
こういったテクニック(アプローチ)はExcel VBAのユーザーフォーム開発においては
既知のものでしょうか?
 
ネット上で検索しても、コントロール関係の応用処理は
コントロールの配列化あたりしか見当たらなかったので気になりました。

回答
投稿日時: 22/07/10 07:05:02
投稿者: WinArrow
投稿者のウェブサイトに移動

引用:

これは、言い換えれば、あるコントロールに、同時に複数のイベントプロシージャーを
紐付けることが可能になったと言えるのではないでしょうか。
  
応用すれば、
・所定のイベントを基本機能として持ち、
 状況に応じて、別の機能を自由に追加できるコントロール
  
・ユーザー側からの操作によって、対応するイベントプロシージャーが切り替わるコントロール
  
などの実装が可能になります。

 
見た目は、同時に処理さえているようにみえますが、
並行して処理されているわけではないということを認識しておく必要があります。
 
ユーザーフォームモジュールも、クラスモジュールの1つです。
 
コマンドボタンを複数設置した場合、
共通的の部分をクラスモジュールに記述する
といった場合に適用する例が多いです。
 
フォームモジュールに記述すると、ボタンの個数分のプロシジャが必要です。
 
ユーザー側の操作
といっても、それをコーディングしておかなければいけないので、
メリットがあるのかな?(私見)

投稿日時: 22/07/10 08:41:38
投稿者: たーぼー
投稿者のウェブサイトに移動

WinArrow さんの引用:

見た目は、同時に処理さえているようにみえますが、
並行して処理されているわけではないということを認識しておく必要があります。
 
ユーザーフォームモジュールも、クラスモジュールの1つです。
 
コマンドボタンを複数設置した場合、
共通的の部分をクラスモジュールに記述する
といった場合に適用する例が多いです。
 
フォームモジュールに記述すると、ボタンの個数分のプロシジャが必要です。
 
ユーザー側の操作
といっても、それをコーディングしておかなければいけないので、
メリットがあるのかな?(私見)

 
お返事ありがとうございます。
  
紹介したのはあくまでも最小構成であって、元々の発見経緯は
「ビジュアルエディタを一切使わず、コーディングのみでフォームを作る」
という目的でUserFormの構成要素をパーツごとにクラス化している時に発生したものでした。
  
<UseForm1>
FormのInitializeイベント時に、定義済みのFormクラスと紐付け
 ↓
<Formクラス>
クラス内のInitializeイベントでMultiPageを配置→ページも追加し、
各ぺージと、役割毎に用意してある個別のPageクラスを紐付け
 ↓
<Pageクラス>
クラス内のInitializeイベントで実際に使うコントロール郡を配置、イベントを記述
(別ページや、同一ページ内で何度も使いまわすパーツ郡はFrameにして別クラスに定義)
 ↓
<Frameクラス>
・項目ごと(顧客ID、氏名、住所、契約コースなど)に10個ほど
 LabelとTextBoxの組み合わせを準備
・Webブラウザに表示された顧客情報から、それらのTextBoxに
 情報を取得する処理を書いたButton(情報の取得はUIAutomationを利用)
  
のような、ユーザーがWebシステムを操作する際の補助ツールを、
VisualSTudio等のExcel VBE以外の開発環境を用意できない状況で作成しています。
  
実運用に即しての開発になるため、後日フォーム上のコントロールの位置を微調整したい時や
同じ情報を使って別の機能を実現するページを追加したい時などに、
全ページに配置された大量のコントロール郡を、いちいちビジュアルエディタで調整するのは
不便極まりないというか、現実的に無理だったのでこういった開発手法を採用しました。
 
※本当はUserFromのモジュール自体も排除して、クラスに全てを記述したかったです。
 MsForms.UserFromにはShowメソッドが無いので仕方なくモジュールを追加してます・・・
  
このような時、クラスとして定義したFrameに配置したButtonのClickイベントを、
クラスの外側に設置することができないか と試行錯誤している時に、
今回の事象を発見したという次第です。

回答
投稿日時: 22/07/10 10:16:32
投稿者: WinArrow
投稿者のウェブサイトに移動

引用:

「ビジュアルエディタを一切使わず、コーディングのみでフォームを作る」
という目的でUserFormの構成要素をパーツごとにクラス化している時に発生したものでした。

 
すみません。考えていることがよくわかりません。
 
例えば、
ユーザーフォームのコントロール毎に
クラスモジュールを用意するということでしょうか?
 
若し、そのようなことを考えているとしたら、
ユーザーフォームには、コントロールごとにプロシジャが用意できます。
プロシジャは、クラスト同等です。
従って、ユーザーモジュールに、コントロール毎の処理を記述するのも、
クラスモジュールに処理を記述するのも、同じです。
複数のコントロールで共通する処理があれば、サブルーチン化すればよい。
 
クラスモジュール化した場合、他のコントロールとの関連は、どの様に考えているのですか?
クラスモジュールから、他のコントロールを参照するには、それ対応の対策が必要です。
ユーザーモジュール内の処理でしたら、他のコントロ-ルを参照すること、何の用意も必要ありません。

投稿日時: 22/07/10 10:36:28
投稿者: たーぼー
投稿者のウェブサイトに移動

WinArrow さんの引用:

すみません。考えていることがよくわかりません。
 
例えば、
ユーザーフォームのコントロール毎に
クラスモジュールを用意するということでしょうか?
 
若し、そのようなことを考えているとしたら、
ユーザーフォームには、コントロールごとにプロシジャが用意できます。
プロシジャは、クラスト同等です。
従って、ユーザーモジュールに、コントロール毎の処理を記述するのも、
クラスモジュールに処理を記述するのも、同じです。
複数のコントロールで共通する処理があれば、サブルーチン化すればよい。
 
クラスモジュール化した場合、他のコントロールとの関連は、どの様に考えているのですか?
クラスモジュールから、他のコントロールを参照するには、それ対応の対策が必要です。
ユーザーモジュール内の処理でしたら、他のコントロ-ルを参照すること、何の用意も必要ありません。

 
単一のコントロールをクラス化したいのではなく、
「特定の機能を持ったコントロール郡が入ったフレーム」をクラス化して、
複数個所で使いまわすような使い方をしています。
この部分は「やりたい」ではなく、既に実現済みです。
 
別の役割を持った「コントロール郡A」と「コントロール郡B」を
MultiPageのPage1とPage2に配置(Pageそれぞれに、目的に応じた異なる部品も配置)
 ↓
Page1では、「コントロール郡B」内にあるボタンをクリックした時には、
「コントロール郡A」内の情報を組み合わせて、フォーマットAの書式で文字列を生成する
 
Page2では、「コントロール郡B」内にあるボタンをクリックした時には、
「コントロール郡A」内の情報を組み合わせて、フォーマットBの書式で文字列を生成する
 
 
ざっくりまとめると、こんな感じです。
 
このため、「コントロール郡Bにあるボタン_Click」のイベントプロシージャーを
Page1、Page2の情報を定義するクラスモジュール側に記述したいと思っていました。
 
そして、これ自体も、各Pageクラス側にButtonコントロールを定義して、
Set myButton = コントロール郡B.Button1のように記述することで 既に実現済みです。
 
 
この時、コントロール郡B内でもButton_Clickイベントプロシージャーを記述していたため、
両方のプロシージャーの処理が連続で実行される現象が発生した という次第です。
 
 
最初に申し上げている通り、質問内容は
「開発中に見つけたこの現象が既知のものか」ということです。

投稿日時: 22/07/10 10:53:26
投稿者: たーぼー
投稿者のウェブサイトに移動

補足
  
別のページでは「コントロール郡A」が2つ並んでおり、
コントロール郡B内のボタンをクリックした時には
「コントロール郡A」2つ分の情報を組み合わせた処理なども実装しています。
  
また、更に別のページにはそもそも「コントロール郡A」が存在せず、
コントロール郡B内のボタンをクリックした時には全く別の挙動になったりもします。
 
 
コントロール郡B内のボタン側にSelectCaseやIf〜Elseなどで分岐を作る手法もあるかと思いますが、
ページを追加する度に分岐をどんどん増やさなければならず、コードの可読性が下がります。
 
このため、Pageごとに同じコントロール郡のインスタンスを設置し、
イベントだけ個別に定義するようにしています。
これなら実装したページとプロジェクト エクスプローラ上のオブジェクトが
1対1で対応するので可読性、メンテナンス性をキープしたまま開発できると考えています。

回答
投稿日時: 22/07/10 14:47:34
投稿者: hatena
投稿者のウェブサイトに移動

たーぼー さんの引用:
最初に申し上げている通り、質問内容は
「開発中に見つけたこの現象が既知のものか」ということです。

この点について、
私個人で言えば、知っていました。
最初にクラスモジュールでイベントを記述できることを知った時、コントロール自体にイベントを記述した場合どうなるのだろうという疑問が湧いたので、確認しました。
 
ここのVBA板で回答しているようなレベルの方ならたいてい知っているのではないのかな。
 
ただ、必要になる状況になったことはないです。Accessでの開発がメインなので、クラスモジュール自体お世話になることは少ないというのも関係しているとは思いますが。
 
たーぼーさんの使い道を知って、なるほど、そういうことなら使えるなという感想を持ちました。

投稿日時: 22/07/10 17:58:08
投稿者: たーぼー
投稿者のウェブサイトに移動

hatena さんの引用:
ここのVBA板で回答しているようなレベルの方ならたいてい知っているのではないのかな。
 
ただ、必要になる状況になったことはないです。Accessでの開発がメインなので、クラスモジュール自体お世話になることは少ないというのも関係しているとは思いますが。
 
たーぼーさんの使い道を知って、なるほど、そういうことなら使えるなという感想を持ちました。

なるほど、流石に未知の挙動と言うわけでは無いのですね。ありがとうございます。
 
まとめると、
 
コントロールとイベントをUserFormモジュール内に宣言
 → コントロールは単一のものとして扱う
 
コントロールをClassモジュールに、
イベントをFormモジュールに定義
 → コントロールの外観とイベントの配列化が可能
 
コントロールの紐付け処理をClassモジュールAに、
コントロールとイベントをClassモジュールBに定義
 → コントロールの外観のみ配列化、イベントを個別に設定可能
 
コントロールの紐付け処理と共通処理をClassモジュールAに、
コントロールと個別イベントをClassモジュールBに定義
 → コントロールの外観と基本処理のセットを配列化、追加イベントを個別に設定可能
 
という整理になるでしょうか。
 
 
Sub AddHandler_Button1Click(ByRef Button_ As MSForms.CommandButton)
 
    Set Button1 = Button_
 
End Sub
 
のような処理でButton1の振る舞いを自由に切り替えられたりもしますね。
実行途中での変更よりも、初期化時のコントロール設置を柔軟にするのが主になりますが・・・。

回答
投稿日時: 22/07/11 09:17:55
投稿者: Suzu

引用:
このため、Pageごとに同じコントロール郡のインスタンスを設置し、
イベントだけ個別に定義するようにしています。

 
 
「フレームを、各ページ共通で配置したい」のみ であれば
1. フォーム内かつ、マルチページ の範囲外 に フレームを配置
2. フレーム内 に関連づけたい オプションボタン等のコントロールを配置
3. フレームを最前面 になる様、配置調整
4. フレームを含む範囲まで マルチページ の大きさを大きくする
 
で、各ページに同じ フレームが表示される様になります。
(ミソは、フレームを マルチページの配下にしない。
 その為に、フレーム側 を操作するのではなく、マルチページ側の大きさを操作。
 これで、フォーム配下 に フレームとマルチページが同列で存在する事になります。)
 
 
引用:
別のページでは「コントロール郡A」が2つ並んでおり、
コントロール郡B内のボタンをクリックした時には
「コントロール郡A」2つ分の情報を組み合わせた処理なども実装しています。
   
また、更に別のページにはそもそも「コントロール郡A」が存在せず、
コントロール郡B内のボタンをクリックした時には全く別の挙動になったりもします。

 
単にコントロール群 A と B を ページにより 表示/非表示 を変えるなら、
MultiPage_Change() イベント で MultiPage.Value の 値で 表示/非表示 を制御すれば良い。
 
 
同じ群を 同じページに 配置したいならクラス化も「アリ」でしょう。

投稿日時: 22/07/15 23:20:06
投稿者: たーぼー
投稿者のウェブサイトに移動

Suzuさん
 
アドバイスありがとうございます。
マルチページ外への配置も場合によってはアリそうですね。
 
今回の用途だと、純粋にページの数だけ"[コントロール郡]の郡"を複数保持しておく必要があるので
やはりページ内にフレームを配置する方針でいこうと思います。
 
 
皆様へ
 
今回件をきっかけに、あるインスタンスから呼び出し元オブジェクトへの参照を通す手法なども
きちんと整理することができて良い勉強になりました。
 
また何かあればご相談させていただこうと思います。ありがとうございました。