VB.NET WinForms中C# Binding.DoNothing等价实现及绑定冲突解决
问题场景
在WinForms应用中,有两个单选按钮optVal1(默认选中)和optVal2,通过自定义方法绑定到inputs类的Selection属性。原本功能正常,但添加CheckedChanged事件处理程序后,点击optVal2时optVal1会自动恢复选中状态,调试输出显示inputs.Selection未正确更新,且出现状态来回切换的情况。
绑定代码
AddDataBindingForRadioButton(optVal1, "Selection", "Val1") AddDataBindingForRadioButton(optVal2, "Selection", "Val2")
绑定辅助函数
Private Sub AddDataBindingForRadioButton(c As Control, name As String, value As String) Dim b As Binding b = New Binding("Checked", inputs, name, True, DataSourceUpdateMode.OnPropertyChanged) ' Format: model to UI AddHandler b.Format, Sub(s, e) e.Value = (CStr(e.Value) = value) End Sub ' Parse: UI to model AddHandler b.Parse, Sub(s, e) If CBool(e.Value) Then e.Value = value Else e.Value = DBNull.Value End If End Sub c.DataBindings.Add(b) End Sub
CheckedChanged事件代码
Private Sub valueChanged(sender As Object, e As EventArgs) Handles optVal1.CheckedChanged, optVal2.CheckedChanged If inputs Is Nothing Then Exit Sub End If Debug.WriteLine("optVal1.Checked=" & optVal1.Checked & " optVal2.Checked=" & optVal2.Checked & " inputs.Selection=" & If(inputs?.StructureType, "null")) Dim opt As RadioButton = DirectCast(sender, RadioButton) If Not opt.Checked Then Exit Sub End If If opt Is optVal1 Then Debug.Print("Val1") grpOtherProp0.Text = "Square" inputs.OtherProp1 = 10 inputs.OtherProp2 = "Square" ElseIf opt Is optVal2 Then Debug.Print("Val2") grpOtherProp0.Text = "Non-Square" inputs.OtherProp1 = 18 inputs.OtherProp2 = "Round" End If End Sub
调试输出
optVal1.Checked=False optVal2.Checked=True inputs.Selection=Val1 optVal1.Checked=False optVal2.Checked=True inputs.Selection=Val1 Val2 optVal1.Checked=True optVal2.Checked=False inputs.Selection=Val1 optVal1.Checked=True optVal2.Checked=False inputs.Selection=Val1 Val1
问题原因
点击optVal2时,其Checked变为True触发CheckedChanged事件,同时绑定的Parse事件触发;而此时optVal1的Checked变为False,它的Parse事件也会执行,将inputs.Selection设为DBNull.Value,导致绑定的Format事件重新执行,把optVal1的Checked又设回True,形成循环。VB.NET WinForms的Binding没有DoNothing选项直接取消Parse时的更新操作,因此需要通过其他方式规避。
解决方案
方案1:通过抛出异常终止无效的绑定更新
修改AddDataBindingForRadioButton中的Parse事件逻辑,仅在单选按钮被选中时更新数据源,取消选中时抛出OperationCanceledException(WinForms会自动捕获此异常并终止本次绑定更新):
Private Sub AddDataBindingForRadioButton(c As Control, name As String, value As String) Dim b As Binding b = New Binding("Checked", inputs, name, True, DataSourceUpdateMode.OnPropertyChanged) ' Format: model to UI AddHandler b.Format, Sub(s, e) e.Value = (CStr(e.Value) = value) End Sub ' Parse: UI to model - 仅选中时更新,取消时终止绑定 AddHandler b.Parse, Sub(s, e) Dim radioBtn As RadioButton = DirectCast(c, RadioButton) If radioBtn.Checked Then e.Value = value Else Throw New System.ComponentModel.OperationCanceledException() End If End Sub c.DataBindings.Add(b) End Sub
此方案的核心是利用WinForms对OperationCanceledException的特殊处理,取消非选中状态下的数据源更新,避免optVal1的Parse事件覆盖正确的Selection值。
方案2:手动管理绑定更新状态
在CheckedChanged事件中,先暂停所有单选按钮的绑定更新,手动设置数据源和UI属性,完成后恢复绑定:
Private Sub valueChanged(sender As Object, e As EventArgs) Handles optVal1.CheckedChanged, optVal2.CheckedChanged If inputs Is Nothing Then Exit Sub End If Dim opt As RadioButton = DirectCast(sender, RadioButton) If Not opt.Checked Then Exit Sub End If ' 临时禁用绑定的数据源更新 Dim originalMode = DataSourceUpdateMode.OnPropertyChanged For Each binding As Binding In optVal1.DataBindings originalMode = binding.DataSourceUpdateMode binding.DataSourceUpdateMode = DataSourceUpdateMode.Never Next For Each binding As Binding In optVal2.DataBindings binding.DataSourceUpdateMode = DataSourceUpdateMode.Never Next Try ' 手动设置数据源和UI属性 If opt Is optVal1 Then Debug.Print("Val1") grpOtherProp0.Text = "Square" inputs.OtherProp1 = 10 inputs.OtherProp2 = "Square" inputs.Selection = "Val1" ElseIf opt Is optVal2 Then Debug.Print("Val2") grpOtherProp0.Text = "Non-Square" inputs.OtherProp1 = 18 inputs.OtherProp2 = "Round" inputs.Selection = "Val2" End If Finally ' 恢复绑定的更新模式 For Each binding As Binding In optVal1.DataBindings binding.DataSourceUpdateMode = originalMode Next For Each binding As Binding In optVal2.DataBindings binding.DataSourceUpdateMode = originalMode Next End Try End Sub
同时可简化绑定的Parse事件(无需处理,因为手动管理数据源):
AddHandler b.Parse, Sub(s, e) ' 无需处理,由事件手动更新数据源 End Sub
效果验证
使用方案1修改后,点击optVal2时,仅它的Parse事件会将inputs.Selection设为Val2,optVal1的Parse事件抛出异常终止更新,绑定的Format事件会正确同步UI状态:optVal1保持False,optVal2保持True,不会出现状态来回切换的问题。
内容的提问来源于stack exchange,提问作者VeryColdAir

