vb.net - 取消关闭时如何重置关闭原因

标签 vb.net winforms .net-4.0

问题

Is it possible to reset the CloseReason provided by the FormClosingEventArgs in the FormClosing event of a modal dialog?



症状

设置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:::::::::::用户关闭
  • btnCancel::: 用户关闭
  • X:::::::::::::::::::UserClosing

  • 案例2
  • X:::::::::::::::::::UserClosing
  • btnCancel::: 用户关闭
  • btnOk:::::::::::用户关闭

  • 同样在这里。一旦你点击 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.CancelButton ( IButtonControl ) 被点击,关闭原因将被设置为 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正在发送消息,但它所做的只是设置一个私有(private)字段。

    所以我深入研究了反射器并跟随堆栈。下图是高度简化 插图。

    Stack

    这就是 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接口(interface)有 PerformClick您可以以编程方式调用的方法,但是,IMO 这闻起来像一个错误。如果单击按钮是 不是 用户操作的结果是什么?

    最佳答案

    理解为什么会这样是非常重要的,当你过度依赖 CloseReason 时,你很容易陷入困境。这不是错误,而是由于 Windows 的设计方式造成的限制。一个核心问题是WM_CLOSE message 的方式。制定,它是让火车运动的人,首先触发 FormClosing 事件。

    发送此消息的原因有很多,您熟悉常见的原因。但这不是它结束的地方,其他程序也可以发送该消息。您可以从我链接到的 MSDN 库文章中看出“缺陷”,该消息缺少对消息意图进行编码的 WPARAM 值。因此,程序没有任何方法可以向您提供合理的 CloseReason。 Winforms 被迫猜测一个原因。这当然是一个完全不完美的猜测。

    这不是它结束的地方,DialogResult 属性也是一个问题。当任何代码分配该属性时,它将强制关闭对话框。但是同样的问题,这样的代码没有任何方法可以表明分配的意图。所以它没有,它在内部 Form.CloseReason 属性中保留它之前的任何值,默认情况下为 None。

    这是在 .NET 1.0 中“正确”实现的,只有 Closing 事件,根本没有给出原因。但这也没有那么好,使用它的应用程序长期阻止 Windows 关闭。他们只是不知道显示消息框是不合适的。添加了 .NET 2.0 FormClosing 事件作为解决方法。但它需要与不完美的猜测一起工作。

    对 CloseReason 值进行评分很重要,有些非常准确,有些只是猜测:

  • CloseReason.WindowsShutdown - 可靠
  • CloseReason.ApplicationExitCall - 可靠
  • CloseReason.MdiFormClosing - 可靠,不是很有用
  • CloseReason.FormOwnerClosing - 可靠,不是很有用
  • CloseReason.TaskManagerClosing - 完全猜测,将在任何程序发送 WM_CLOSE 消息时返回,而不仅仅是任务管理器
  • CloseReason.UserClosing - 完整的猜测,也会在您的程序调用 Close() 方法时返回,例如
  • CloseReason.None - 它只是不知道。

  • 是的,当您的 FormClosing 事件处理程序取消时,Winforms 没有将 CloseReason 设置回 None 可以说是一个错误。但这并不是真正重要的错误。因为无论如何您都不能以不同的方式对待 UserClosing 和 None 。

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

    相关文章:

    c# - 更改 InputLanguage 有什么作用?

    entity-framework - 如何将现有的 EDMX 1.0 模型升级到新的 EDMX 2.0?

    generics - .NET 4.0 中协方差和逆变的一个很好的实际应用?

    c# - Parallel.ForEach 和 ListView 控件

    vb.net - 为什么不推荐使用一个 Public OleDbConnection?解决错误: too many connections opened的替代方案

    sql - 在有 in 子句的地方将 SQL 转换为 Linq

    c# - VB 到 C# 的转换与 lambda 不一致

    mysql - VB.NET存储过程中的dbnull OUT参数

    winforms - 在创建窗口句柄之前,无法在控件上调用 Invoke 或 BeginInvoke

    C# Windows 窗体 - 在控件外部单击后隐藏控件