问题
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.AcceptButton
或 Form.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)字段。所以我深入研究了反射器并跟随堆栈。下图是高度简化 插图。
这就是
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 值进行评分很重要,有些非常准确,有些只是猜测:
是的,当您的 FormClosing 事件处理程序取消时,Winforms 没有将 CloseReason 设置回 None 可以说是一个错误。但这并不是真正重要的错误。因为无论如何您都不能以不同的方式对待 UserClosing 和 None 。
关于vb.net - 取消关闭时如何重置关闭原因,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/23872921/