「通常の」モジュールではなくVBAユーザーフォームにコードを配置することには欠点がありますか?
これは簡単な質問かもしれませんが、webとstackoverflowを検索しているときに、それに対する明確な答えは見つかりませんでした。
Background: Excel-VBAでデータベースのフロントエンドアプリケーションを開発しています。別のフィルターを選択するには、別のユーザーフォームがあります。一般的なプログラム設計の方が良いと思います:(1)制御構造を別のモジュールに配置する OR (2)次のコードを配置するuserformまたはuserformのアクション。
例を挙げましょう。フィルターとフォームをトリガーするActive-Xボタンがあります。
バリアント1:モジュール
コマンドボタンで:
Private Sub CommandButton1_Click()
call UserInterfaceControlModule
End Sub
モジュール内:
Sub UserInterfaceControllModule()
Dim decisionInput1 As Boolean
Dim decisionInput2 As Boolean
UserForm1.Show
decisionInput1 = UserForm1.decision
If decisionInput1 Then
UserForm2.Show
Else
UserForm3.Show
End If
End Sub
バリアント1では、制御構造は通常のモジュール内にあります。また、次に表示するユーザーフォームに関する決定は、ユーザーフォームから切り離されています。次に表示するユーザーフォームを決定するために必要な情報は、ユーザーフォームから取得する必要があります。
バリアント2:ユーザーフォーム
CommadButtonで:
Private Sub CommandButton1_Click()
UserForm1.Show
End Sub
Userform1の場合:
Private Sub ToUserform2_Click()
UserForm2.Show
UserForm1.Hide
End Sub
Private Sub UserForm_Click()
UserForm2.Show
UserForm1.Hide
End Sub
バリアント2では、制御構造は直接ユーザーフォームにあり、各ユーザーフォームにはその後の内容に関する指示があります。
私は方法2を使用して開発を開始しました。これが誤りであり、この方法に重大な欠点がある場合は、それよりも早く知りたいです。
免責事項私は 記事 ビクターK リンク先 と書きました。私はそのブログを所有し、それが対象としているオープンソースのVBIDEアドインプロジェクトを管理しています。
どちらの選択肢も理想的ではありません。基本に立ち返って。
異なるフィルターを選択するには、異なる(sic)ユーザーフォームがあります。
仕様では、ユーザーがさまざまなフィルターを選択できる必要があることを要求しており、UserForm
を使用してUIを実装することを選択しました。これまでのところ、とても良い...そしてそれはそこからすべて下り坂です。
プレゼンテーションの懸念事項以外の原因となるフォームを作成することは、よくある間違いであり、その名前にはSmart UI[anti-] pattern、およびそれの問題はそれがスケールしないということです。これは、プロトタイピング(つまり、「うまくいく」ようにすばやく作業する-恐ろしい引用に注意してください)に最適であり、何年にもわたって維持する必要のあるものにはあまり適していません。
おそらくこれらのフォームを見て、160のコントロール、217のイベントハンドラ、および3つのプライベートプロシージャがそれぞれ2000行のコードで終了します。これはSmart UIのスケーリングが非常に悪く、それが唯一の可能性ですその道の結果。
ご覧のとおり、UserForm
はクラスモジュールです。objectのblueprintを定義します。オブジェクトは通常instantiatedになりたいですが、誰かが_MSForms.UserForm
_のすべてのインスタンスに事前宣言されたIDを付与するという天才的なアイデアを持っていました。つまり、基本的に無料でグローバルオブジェクトを取得します。
すごい!番号?番号。
_UserForm1.Show decisionInput1 = UserForm1.decision If decisionInput1 Then UserForm2.Show Else UserForm3.Show End If
_
_UserForm1
_が「X'd-out」の場合はどうなりますか?または、_UserForm1
_がUnload
edの場合は?フォームがQueryClose
イベントを処理していない場合、オブジェクトは破棄されますが、これはdefault instanceであるため、VBAは直前に、自動的に/サイレントに新しいインスタンスを作成しますコードは_UserForm1.decision
_を読み取ります-その結果、_UserForm1.decision
_の初期グローバル状態は何でも取得できます。
それがdefault instanceではなく、QueryClose
が処理されなかった場合、破棄されたオブジェクトの_.decision
_メンバーにアクセスすると、古典的なrun- nullオブジェクト参照にアクセスすると、エラー91が発生します。
_UserForm2.Show
_と_UserForm3.Show
_はどちらも同じことを行います:発火して忘れます-何が起こっても、それが何であるかを正確に知るには、フォームのそれぞれのコードでそれを掘り下げる必要があります後ろに。
言い換えると、フォームはshowを実行しています。彼らは、データを収集し、そのデータを提示し、ユーザー入力を収集し、そしてそれを使って実行する必要があるあらゆる作業を行う責任があります。これが「スマートUI」と呼ばれる理由です。UIはすべてを認識しています。
もっと良い方法があります。 MSFormsは.NETのWinForms UIフレームワークのCOM祖先であり、祖先がその.NET後継者と共通しているのは、有名なModel-View-Presenter(MVP )パターン。
それがdataです。基本的に、それはフォームからアプリケーションロジックが知る必要があるです。
UserForm1.decision
_それで行こう。新しいクラスを追加し、それをFilterModel
などと呼びます。非常に単純なクラスである必要があります:
_Option Explicit
Private Type TModel
SelectedFilter As String
End Type
Private this As TModel
Public Property Get SelectedFilter() As String
SelectedFilter = this.SelectedFilter
End Property
Public Property Let SelectedFilter(ByVal value As String)
this.SelectedFilter = value
End Property
Public Function IsValid() As Boolean
IsValid = this.SelectedFilter <> vbNullString
End Function
_
必要なのはそれだけです。フォームのデータをカプセル化するクラスです。クラスは、いくつかの検証ロジックなどを担当できますが、collectデータではなく、ユーザーへのpresentではありません。consumeそれもしません。isデータです。
ここにはプロパティは1つしかありませんが、さらに多くのプロパティが考えられます。フォーム上の1つのフィールド=> 1つのプロパティと考えてください。
モデルは、フォームがアプリケーションロジックから知る必要があるものでもあります。たとえば、フォームにいくつかの可能な選択を表示するドロップダウンが必要な場合、モデルはそれらを公開するオブジェクトになります。
それがあなたのフォームです。これは、コントロールについての知識、modelへの書き込みおよび読み取りを担当します。それがすべてです。ここではダイアログを見ています。それを表示し、ユーザーが入力して閉じ、プログラムがそれに作用します-フォーム自体はデータでdo何もしません収集します。モデルはそれを検証し、フォームは無効にすることを決定する場合があります Ok モデルがそのデータが有効で問題ないと言うまでボタンをクリックしますが、いかなる状況でもUserForm
はワークシートから読み取りまたは書き込みを行います。データベース、ファイル、URLなど。
フォームのコードビハインドは非常に単純です。UIをモデルインスタンスに結び付け、必要に応じてボタンを有効または無効にします。
覚えておくべき重要なこと:
Hide
、しないでくださいUnload
:ビューはオブジェクトであり、オブジェクトは自己破壊しません。QueryClose
を再び処理して、自己破壊的なオブジェクトを回避します(フォームの「Xアウト」はインスタンスを破壊します)。この場合、コードビハインドは次のようになります。
_Option Explicit
Private Type TView
Model As FilterModel
IsCancelled As Boolean
End Type
Private this As TView
Public Property Get Model() As FilterModel
Set Model = this.Model
End Property
Public Property Set Model(ByVal value As FilterModel)
Set this.Model = value
Validate
End Property
Public Property Get IsCancelled() As Boolean
IsCancelled = this.IsCancelled
End Property
Private Sub TextBox1_Change()
this.Model.SelectedFilter = TextBox1.Text
Validate
End Sub
Private Sub OkButton_Click()
Me.Hide
End Sub
Private Sub Validate()
OkButton.Enabled = this.Model.IsValid
End Sub
Private Sub CancelButton_Click()
OnCancel
End Sub
Private Sub UserForm_QueryClose(Cancel As Integer, CloseMode As Integer)
If CloseMode = VbQueryClose.vbFormControlMenu Then
Cancel = True
OnCancel
End If
End Sub
Private Sub OnCancel()
this.IsCancelled = True
Me.Hide
End Sub
_
それが文字通りフォームのすべてです。 データがどこから来たのか、それをどうするのかを知る責任はありません。
それがドットをつなぐ「接着剤」オブジェクトです。
_Option Explicit
Public Sub DoSomething()
Dim m As FilterModel
Set m = New FilterModel
With New FilterForm
Set .Model = m 'set the model
.Show 'display the dialog
If Not .IsCancelled Then 'how was it closed?
'consume the data
Debug.Print m.SelectedFilter
End If
End With
End Sub
_
モデル内のデータがデータベースまたはワークシートから取得する必要がある場合は、それだけを行うクラスインスタンス(はい、anotherオブジェクト!)を使用します。
呼び出しコードは、ActiveXボタンのクリックハンドラで、プレゼンターをNew
- ingし、そのDoSomething
メソッドを呼び出すことができます。
これは、OOPについて知っていることのすべてではありません(インターフェイス、ポリモーフィズム、テストスタブ、ユニットテストについては触れていません))が、客観的にスケーラブルなコードが必要な場合は、 MVPのうさぎの穴を掘り下げ、真にオブジェクト指向のコードがVBAにもたらす可能性を探りたいと思います。
コード(「ビジネスロジック」)は、フォームのコードビハインドでは、belongではなく、数年にわたって拡張および維持されることを意味するコードベースではありません。
「バリアント1」では、モジュール間を行き来し、プレゼンテーションの懸念がアプリケーションロジックと混在しているため、コードを追跡するのは困難です。指定されたボタンAまたはボタンBを表示するために他のどのフォームが押されたかを知るのはフォームの仕事ではありません。代わりに、それはpresenterにユーザーが何をすべきかを知らせ、それに応じて行動するべきです。
「バリアント2」では、すべてがユーザーフォームのコードビハインドに隠されているため、コードを追跡するのは困難です。そのコードに掘り下げない限り、アプリケーションロジックがわからないので、purposelyプレゼンテーションとビジネスロジックの問題が混在しています。それはexactly「スマートUI」アンチパターンが行うことです。
つまり、バリアント1はバリアント2よりもわずかに優れています。これは、少なくともロジックがコードビハインドに含まれていないためですが、runningの代わりに/ **/runningであるため、依然として「スマートUI」です。何が起こっているのかを発信者に伝える。
どちらの場合も、フォームのデフォルトインスタンスに対するコーディングは有害です。これは、状態をグローバルスコープに配置するためです(誰でもデフォルトインスタンスにアクセスし、コード内のどこからでもその状態に対して何でも実行できます)。
フォームをオブジェクトと同じように扱います。インスタンス化してください!
どちらの場合も、フォームのコードはアプリケーションロジックと緊密に結びついており、プレゼンテーションの懸念と絡み合っているため、現在の状況の1つの側面さえカバーする単一の単体テストを書くことは完全に不可能です。 MVPパターンを使用すると、コンポーネントを完全に分離し、インターフェースの背後でそれらを抽象化し、責任を分離し、すべての機能をカバーし、仕様を正確に文書化する数十の自動ユニットテストを記述できます。コードは独自のドキュメントになります。