是否可以在模式对话框的CloseReason事件中重置FormClosingEventArgs提供的FormClosing


病征

如果关闭事件先前已被取消,则设置模式对话框的DialogResult可能会导致“错误” CloseReason

细节

(以下代码仅是示例代码,以突出显示不便之处)

想象一下,我有一个带有两个按钮(确定和取消)的窗体,它们显示为模式对话框。

Me.btnOk = New Button With {.DialogResult = Windows.Forms.DialogResult.OK}
Me.btnCancel = New Button With {.DialogResult = Windows.Forms.DialogResult.Cancel}

Me.AcceptButton = Me.btnOk
Me.CancelButton = Me.btnCancel


任何关闭表单的尝试都会被取消。

如果我按以下顺序单击每个按钮(包括[X]-关闭表单按钮),则关闭原因如下:

情况1


btnOk ::::::::::::无
btnCancel :::无
X ::::::::::::::::::: UserClosing


现在,如果我重复这些步骤,您会发现UserClosing原因将继续存在:


btnOk :::::::::::: UserClosing
btnCancel ::: UserClosing
X ::::::::::::::::::: UserClosing


情况二


X ::::::::::::::::::: UserClosing
btnCancel ::: UserClosing
btnOk :::::::::::: UserClosing


同样在这里。单击X按钮后,关闭原因将始终返回UserClosing

样品申请

Public Class Form1

    Public Sub New()
        Me.InitializeComponent()
        Me.Text = "Test"
        Me.FormBorderStyle = Windows.Forms.FormBorderStyle.FixedDialog
        Me.MinimizeBox = False
        Me.MaximizeBox = False
        Me.ClientSize = New Size(75, 25)
        Me.StartPosition = FormStartPosition.CenterScreen
        Me.btnOpenDialog = New Button() With {.TabIndex = 0, .Dock = DockStyle.Fill, .Text = "Open dialog"}
        Me.Controls.Add(Me.btnOpenDialog)
    End Sub

    Private Sub HandleOpenDialog(sender As Object, e As EventArgs) Handles btnOpenDialog.Click
        Using instance As New CustomDialog()
            instance.ShowDialog()
        End Using
    End Sub

    Private WithEvents btnOpenDialog As Button

    Private Class CustomDialog
        Inherits Form

        Public Sub New()
            Me.Text = "Custom dialog"
            Me.ClientSize = New Size(400, 200)
            Me.StartPosition = FormStartPosition.CenterParent
            Me.FormBorderStyle = Windows.Forms.FormBorderStyle.FixedDialog
            Me.MinimizeBox = False
            Me.MaximizeBox = False
            Me.tbOutput = New RichTextBox() With {.TabIndex = 0, .Bounds = New Rectangle(0, 0, 400, 155), .ReadOnly = True, .ScrollBars = RichTextBoxScrollBars.ForcedBoth, .WordWrap = True}
            Me.btnExit = New Button With {.TabIndex = 3, .Text = "Exit", .Bounds = New Rectangle(10, 165, 75, 25), .Anchor = (AnchorStyles.Bottom Or AnchorStyles.Left)}
            Me.btnOk = New Button With {.TabIndex = 1, .Text = "OK", .Bounds = New Rectangle(237, 165, 75, 25), .Anchor = (AnchorStyles.Bottom Or AnchorStyles.Right), .DialogResult = Windows.Forms.DialogResult.OK}
            Me.btnCancel = New Button With {.TabIndex = 2, .Text = "Cancel", .Bounds = New Rectangle(315, 165, 75, 25), .Anchor = (AnchorStyles.Bottom Or AnchorStyles.Right), .DialogResult = Windows.Forms.DialogResult.Cancel}
            Me.Controls.AddRange({Me.tbOutput, Me.btnExit, Me.btnOk, Me.btnCancel})
            Me.AcceptButton = Me.btnOk
            Me.CancelButton = Me.btnCancel
        End Sub

        Private Sub HandleExitDialog(sender As Object, e As EventArgs) Handles btnExit.Click
            Me.exitPending = True
            Me.Close()
        End Sub

        Protected Overrides Sub OnFormClosing(e As FormClosingEventArgs)
            If (Not Me.exitPending) Then
                e.Cancel = True
                Me.tbOutput.Text += (String.Format("DialogResult={0}, CloseReason={1}{2}", Me.DialogResult.ToString(), e.CloseReason.ToString(), Environment.NewLine))
                Me.DialogResult = Windows.Forms.DialogResult.None
            End If
            MyBase.OnFormClosing(e)
        End Sub

        Private exitPending As Boolean

        Private WithEvents btnExit As Button
        Private WithEvents btnCancel As Button
        Private WithEvents btnOk As Button
        Private WithEvents tbOutput As RichTextBox

    End Class

End Class


更新资料

我的印象是,如果单击Form.AcceptButtonForm.CancelButtonIButtonControl),则关闭原因将设置为UserClosing,但事实并非如此。在下面的代码中,您将看到它所做的全部就是将拥有表单的DialogResult设置为其自己的DialogResult的表单。

Protected Overrides Sub OnClick(ByVal e As EventArgs)
    Dim form As Form = MyBase.FindFormInternal
    If (Not form Is Nothing) Then
        form.DialogResult = Me.DialogResult
    End If
    MyBase.AccessibilityNotifyClients(AccessibleEvents.StateChange, -1)
    MyBase.AccessibilityNotifyClients(AccessibleEvents.NameChange, -1)
    MyBase.OnClick(e)
End Sub


Control类确实具有一个名为CloseReason的属性,但它被定义为Friend,因此无法访问。

我还认为设置表单DialogResult会导致发送WM消息,但是它所做的只是设置一个私有字段。

因此,我深入研究了反射器,并跟踪了堆栈。下图是高度简化的插图。



CheckCloseDialog方法如下所示:

Friend Function CheckCloseDialog(ByVal closingOnly As Boolean) As Boolean
    If ((Me.dialogResult = DialogResult.None) AndAlso MyBase.Visible) Then
        Return False
    End If
    Try
        Dim e As New FormClosingEventArgs(Me.closeReason, False)
        If Not Me.CalledClosing Then
            Me.OnClosing(e)
            Me.OnFormClosing(e)
            If e.Cancel Then
                Me.dialogResult = DialogResult.None
            Else
                Me.CalledClosing = True
            End If
        End If
        If (Not closingOnly AndAlso (Me.dialogResult <> DialogResult.None)) Then
            Dim args2 As New FormClosedEventArgs(Me.closeReason)
            Me.OnClosed(args2)
            Me.OnFormClosed(args2)
            Me.CalledClosing = False
        End If
    Catch exception As Exception
        Me.dialogResult = DialogResult.None
        If NativeWindow.WndProcShouldBeDebuggable Then
            Throw
        End If
        Application.OnThreadException(exception)
    End Try
    If (Me.dialogResult = DialogResult.None) Then
        Return Not MyBase.Visible
    End If
    Return True
End Function


如您所见,模态消息循环在每个循环中检查DialogResult,如果条件成立,它将在创建CloseReason时使用存储的FormClosingEventArgs(根据观察)。

摘要

是的,我知道IButtonControl接口具有一个PerformClick方法,您可以通过编程方式调用该方法,但是,IMO仍然闻起来像个bug。如果单击按钮不是用户操作的结果,那是什么?

最佳答案

我可能会称其为错误。

如您所述,CloseReason属性被标记为内部(或VB.Net中的Friend),因此解决此问题的一种方法是使用Reflection自己重置该值:

Protected Overrides Sub OnFormClosing(e As FormClosingEventArgs)
  If Not exitPending Then
    e.Cancel = True
    tbOutput.AppendText(String.Format("DialogResult={0}, CloseReason={1}{2}", _
                        Me.DialogResult.ToString(), e.CloseReason.ToString(), _
                        Environment.NewLine))
    Dim pi As PropertyInfo
    pi = Me.GetType.GetProperty("CloseReason", _
                                BindingFlags.Instance Or BindingFlags.NonPublic)
    pi.SetValue(Me, CloseReason.None, Nothing)
  End If
  MyBase.OnFormClosing(e)
End Sub


无法保证此代码将在WinForms的未来版本中使用,但是我想这是一个安全的选择。 :-)

关于vb.net - 取消关闭时如何重置关闭原因,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/23872921/

10-09 05:09