web-dev-qa-db-ja.com

VBA-モードレスUserFormインスタンスを適切に破棄します

イントロ:

私は、ユーザーフォームを表示することがベストプラクティスであることを認識しています。

  • ユーザーフォームコード内でQueryCloseを処理します(If CloseMode = vbFormControlMenu ...
  • その中でUnload Meを実行せず、臆病なMe.Hide命令のみを実行します([x] -itとCancel = Trueを介した最終的な自己破壊を防止した後)
  • [クラス]コード内で関連する変数/ [プロパティ]を設定する(例:.IsCancelled=True
  • 呼び出しコードによってUFをアンロードできるようにするため。

便利なリンク

優れた概要 "UserForm1.Show?"https://rubberduckvba.wordpress.com/2017/10/25/userform1-showにあります/ および多数の例でSO回答(Mathieu Guindon別名Mat's MugおよびRubberDuckへのthx)。

さらなる選択(►2019年5月1日現在の編集


1)モーダルユーザーフォームの実例

私が理解している限り(そして私が学ぼうとしている限り)、次のコードはモーダルUFに対しては問題ないはずです:

ケース1a).. UFインスタンスのローカル変数を使用、よく見られるように:

Public Sub ShowFormA
  Dim ufA As UserForm1
  Set ufA = New UserForm1
' show userform 
  ufA.Show          ' equivalent to: ufA.Show vbModal

' handle data after user okay
  If Not ufA.IsCancelled Then
      '  do something ...
  End If

' >> object reference destroyed expressly (as seen in some examples)
  unload ufA
End Sub

ケース1b)..ローカル変数はありませんが、With Newコードブロックを使用しています:

' ----------------------------------------------------------
' >> no need to destruct object reference expressly,
'    as it will be destroyed whenever exiting the with block
' ----------------------------------------------------------
  With New UserForm1
      .Show         ' equivalent to: ufA.Show vbModal

    ' handle data after user okay
      If Not .IsCancelled Then
      '  do something ...
      End If
  End With

2)問題

[〜#〜] modeless [〜#〜]UserFormインスタンスを使用すると問題が発生します。

さて、withブロックメソッド(1bを参照)は、オブジェクト参照をx-itした後に破棄するのに十分なはずです。

  With New UserForm1
      .Show vbModeless  ' << show modeless uf
  End With

しかし、私がしようとすると

  • a)キャンセルの可能性のあるユーザーに関する情報と
  • b)Unload命令の後にローカル変数(「ufA」など)を使用してバプテスマを受けた場合は、Showフォームに

フォームがMODELESSであるという正確な理由により、すべてのコード行が一度に実行されます。

  • コードはフォームを示し、次の瞬間..
  • 次の瞬間、ユーザーアクションの時間がなかったため、コードはユーザーによるキャンセルを検出しません。
  • [ユーザーフォームにローカル変数を使用している場合、コードはフォームをアンロードします]

3)質問

A)MODELESSフォームの呼び出しコードによる正しく報告されたUserFormのキャンセル、およびb)ローカル変数を使用している場合の(必要な?)アンロードをどのように処理できますか?

10
T.M.

確かに、私はモーダルフォームにかなり焦点を当ててきました-それが最も一般的に使用されているものだからです。その記事へのフィードバックをありがとう!

ただし、原則は非モーダルフォームでも同じです。リンクされた記事で大まかに概説されているModel-View-Presenterパターンと ここ を拡張するだけです。

違いは、非モーダルフォームにはパラダイムシフトが必要なことです。事前に設定された一連のイベントに応答しなくなります。むしろ、非同期イベントに応答する必要があります。いつでも発生する可能性があります。

  • モーダルフォームを処理する場合、フォームが非表示になった直後に実行される「表示前」と「非表示後」があります。イベントを使用して、「表示中に」発生するすべてのものを処理できます。
  • 非モーダルフォームを処理する場合、「表示前」、「表示中」、「表示後」の両方をイベントで処理する必要があります。

プレゼンタークラスモジュールに、モジュールレベルおよびUserFormWithEventsインスタンスを保持する責任を持たせます。

Option Explicit
Private WithEvents myModelessForm As UserForm1

プレゼンターのShowメソッドはフォームインスタンスをSetし、それを表示します。

Public Sub Show()
    'If Not myModelessForm Is Nothing Then
    '    myModelessForm.Visible = True 'just to ensure visibility & honor the .Show call
    '    Exit Sub
    'End If
    Set myModelessForm = New UserForm1
    '...
    myModelessForm.Show vbModeless
End Sub

あなたしないフォームインスタンスをここのプロシージャに対してローカルにしたいので、ローカル変数またはWithブロックは機能しません:オブジェクトはスコープ外になりますあなたがそれを意味する前に。これが、インスタンスをモジュールレベルのプライベートフィールドに保存する理由です。これで、フォームはプレゼンターインスタンスと同じように存続します。

ここで、フォームをプレゼンターに「話す」必要があります。最も簡単な方法は、UserForm1コードビハインドでイベントを公開することです。たとえば、ユーザーにキャンセルを確認してもらいたい場合は、ByRefを追加します。プレゼンターのハンドラーが情報をイベントソースに戻す(つまり、フォームコードに戻す)ことができるように、パラメーターをイベントに設定します。

Option Explicit
'...private fields, model, etc...
Public Event FormConfirmed()
Public Event FormCancelled(ByRef Cancel as Boolean)

'returns True if cancellation was cancelled by handler
Private Function OnCancel() As Boolean
    Dim cancelCancellation As Boolean
    RaiseEvent FormCancelled(cancelCancellation)
    If Not cancelCancellation Then Me.Hide
    OnCancel = cancelCancellation
End Function

Private Sub CancelButton_Click()
    OnCancel
End Sub

Private Sub OkButton_Click()
    Me.Hide
    RaiseEvent FormConfirmed
End Sub

Private Sub UserForm_QueryClose(Cancel As Integer, CloseMode As Integer)
    If CloseMode = VbQueryClose.vbFormControlMenu Then
        Cancel = Not OnCancel
    End If
End Sub

これで、プレゼンターはそのFormCancelledイベントを処理できます。

Private Sub myModelessForm_FormCancelled(ByRef Cancel As Boolean)
    'setting Cancel to True will leave the form open
    Cancel = MsgBox("Cancel this operation?", vbYesNo + vbExclamation) = vbNo
    If Not Cancel Then
        ' modeless form was cancelled and is now hidden.
        ' ...
        Set myModelessForm = Nothing
    End If
End Sub

Private Sub myModelessForm_FormConfirmed()
    'form was okayed and is now hidden.
    '...
    Set myModelessForm = Nothing
End Sub

ただし、非モーダルフォームには通常「OK」ボタンと「キャンセル」ボタンはありません。むしろ、いくつかの機能が公開されます。たとえば、他のことを行うモーダルダイアログUserForm2を表示する機能があります。ここでも、イベントを公開して、プレゼンターで処理します。

Public Event ShowGizmo()

Private Sub ShowGizmoButton_Click()
    RaiseEvent ShowGizmo
End Sub

そしてプレゼンターは行きます:

Private Sub myModelessForm_ShowGizmo()
    With New GizmoPresenter
        .Show
    End With
End Sub

モーダルUserForm2は、別のプレゼンタークラスの懸念事項であることに注意してください。

8
Mathieu Guindon

モードレスフォームの場合は、カスタムuserformプロパティと組み合わせてDoEventsを使用します。


Sub test()

    Dim frm As New UserForm1

    frm.Show vbModeless

    Do
        DoEvents
        If frm.Cancelled Then
            Unload frm
        Exit Do
    End If
    Loop Until False

    MsgBox "You closed the modeless form."

    '/ Using With
    With New UserForm1
        .Show vbModeless
        Do
            DoEvents
            If .Cancelled Then Exit Do
        Loop Until False
    End With

    MsgBox "You closed the modeless form (with)"

End Sub

'/ユーザーフォーム

Private m_bCancelled As Boolean

Public Property Get Cancelled() As Boolean
    Cancelled = m_bCancelled
End Property

Public Property Let Cancelled(ByVal bNewValue As Boolean)
    m_bCancelled = bNewValue
End Property
Private Sub UserForm_QueryClose(Cancel As Integer, CloseMode As Integer)
    Me.Cancelled = True
    Cancel = 1
    Me.Hide
End Sub
3
cyboashu

私は通常、ThisWorkbookの背後にある行に沿ってコードを配置することにより、モードレスユーザーフォームインスタンスの存続期間をワークブックの存続期間に結び付けます。

Option Explicit

Private m_MyForm As UserForm1

Private Sub Workbook_BeforeClose(Cancel As Boolean)
    If Not m_MyForm Is Nothing Then
        Unload m_MyForm
        Set m_MyForm = Nothing
    End If
End Sub

Friend Property Get MyForm() As UserForm1
    If m_MyForm Is Nothing Then
        Set m_MyForm = New UserForm1
    End If

    Set MyForm = m_MyForm
End Property

次に、コード全体でモードレスコードを参照できます。

ThisWorkbook.MyForm.Show vbModeless

等.

3
Excelosaurus