题
是否可以在模式对话框的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
:::::::::::: UserClosingbtnCancel
::: UserClosingX
::::::::::::::::::: UserClosing情况二
X
::::::::::::::::::: UserClosingbtnCancel
::: UserClosingbtnOk
:::::::::::: 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.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
消息,但是它所做的只是设置一个私有字段。因此,我深入研究了反射器,并跟踪了堆栈。下图是高度简化的插图。
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/