我正在开发一个 WPF 应用程序。在我的一个页面中,我有一个包含 3 个组合框的 DockPanel。这 3 个 ComboBox 相互依赖。因此,我在 DockPanel 上有一个绑定组和该绑定组的验证规则。
当 DockPanel 失去焦点时,我想验证这 3 个组合框;但是,当用户单击其中一个子 ComboBox 时,会触发 DockPanel 的 LostFocus 事件!
我原以为如果子控件有焦点,父控件也会有焦点……但似乎并非如此。
我正在寻找一种解决方案,以在 DockPanel 失去焦点时对绑定组执行验证......其中焦点丢失到不是其子项之一的控件。
编辑:
因此,我创建了一个尽可能简单的应用程序来演示这个问题。
我有一个“FertilizerCombination”类,其中包含构成肥料的 3 个整数。这些在应用程序中必须是唯一的。目前唯一不可用的组合是 10-10-10。这是类中的硬编码值。
Public Class FertilizerCombination
Private _nitrogen As Integer
Private _phosphorous As Integer
Private _potassium As Integer
<System.ComponentModel.DataAnnotations.Range(1, 20)> _
Public Property Nitrogen As Integer
Get
Return _nitrogen
End Get
Set(value As Integer)
System.ComponentModel.DataAnnotations.Validator.ValidateProperty(value, NitrogenValidationContext)
_nitrogen = value
End Set
End Property
<System.ComponentModel.DataAnnotations.Range(1, 20)> _
Public Property Phosphorous As Integer
Get
Return _phosphorous
End Get
Set(value As Integer)
System.ComponentModel.DataAnnotations.Validator.ValidateProperty(value, PhosphorousValidationContext)
_phosphorous = value
End Set
End Property
<System.ComponentModel.DataAnnotations.Range(1, 20)> _
Public Property Potassium As Integer
Get
Return _potassium
End Get
Set(value As Integer)
System.ComponentModel.DataAnnotations.Validator.ValidateProperty(value, PotassiumValidationContext)
_potassium = value
End Set
End Property
Public Sub New()
End Sub
Public Sub New(ByVal nitrogen As Integer, ByVal phosphorous As Integer, ByVal potassium As Integer)
Me.Nitrogen = nitrogen
Me.Phosphorous = phosphorous
Me.Potassium = potassium
End Sub
Public Shared Function FertilizerCombinationAvailable(ByVal nitrogen As Integer, ByVal phosphorous As Integer, ByVal potassium As Integer) As Boolean
'Checking against combinations already used'
If nitrogen = "10" And phosphorous = "10" And potassium = "10" Then
'Combination has already been used'
Return False
End If
'Combination was not used yet'
Return True
End Function
Public Sub SetFertilizerCombination(ByVal nitrogen As Integer, ByVal phosphorous As Integer, ByVal potassium As Integer)
System.ComponentModel.DataAnnotations.Validator.ValidateProperty(nitrogen, NitrogenValidationContext)
System.ComponentModel.DataAnnotations.Validator.ValidateProperty(phosphorous, PhosphorousValidationContext)
System.ComponentModel.DataAnnotations.Validator.ValidateProperty(potassium, PotassiumValidationContext)
If FertilizerCombination.FertilizerCombinationAvailable(nitrogen, phosphorous, potassium) = False Then
Throw New ArgumentException("This fertilizer combination has already been used")
End If
Me.Nitrogen = nitrogen
Me.Phosphorous = phosphorous
Me.Potassium = potassium
End Sub
Private NitrogenValidationContext = New System.ComponentModel.DataAnnotations.ValidationContext(Me, Nothing, Nothing) With {.MemberName = "Nitrogen"}
Private PhosphorousValidationContext = New System.ComponentModel.DataAnnotations.ValidationContext(Me, Nothing, Nothing) With {.MemberName = "Phosphorous"}
Private PotassiumValidationContext = New System.ComponentModel.DataAnnotations.ValidationContext(Me, Nothing, Nothing) With {.MemberName = "Potassium"}
End Class
我为肥料组合类创建了一个验证规则:
Public Class FertilizerCombinationValidationRule
Inherits ValidationRule
Public Overrides Function Validate(ByVal value As Object, ByVal cultureInfo As System.Globalization.CultureInfo) As System.Windows.Controls.ValidationResult
Dim bg As BindingGroup = TryCast(value, BindingGroup)
If bg IsNot Nothing AndAlso bg.Items.Count > 0 Then
'The BindingGroup Items property contains the original object(s)'
Dim fertilizerCombo As FertilizerCombination = TryCast(bg.Items(0), FertilizerCombination)
'Using the BindingGroups GetValue method to retrieve the user provided values'
Dim proposedNitrogen As Integer
Dim proposedPhosphorous As Integer
Dim proposedPotassium As Integer
Try
proposedNitrogen = bg.GetValue(fertilizerCombo, "Nitrogen")
proposedPhosphorous = bg.GetValue(fertilizerCombo, "Phosphorous")
proposedPotassium = bg.GetValue(fertilizerCombo, "Potassium")
If FertilizerCombination.FertilizerCombinationAvailable(proposedNitrogen, proposedPhosphorous, proposedPotassium) = False Then
Return New ValidationResult(False, "This fertializer combination has already been used")
End If
Catch noValue As System.Windows.Data.ValueUnavailableException
'a binding was not properly bound yet'
End Try
End If
Return New ValidationResult(True, Nothing)
End Function
End Class
我有一个具有 FertilizerCombination 属性的“PlantSolution”类。我还有一个用于该类的 VM,以便在 XAML 中轻松绑定:
Public Class PlantSolutionVM
Public Property PlantSolution As PlantSolution
Public Sub New()
PlantSolution = New PlantSolution("Produce Blooms", "Using this fertilizer will help your plant produce flowers!", 10, 10, 20)
End Sub
End Class
Public Class PlantSolution
Private _name As String
Private _description As String
Private _fertilizer As FertilizerCombination
<System.ComponentModel.DataAnnotations.Required()>
Public Property Name As String
Get
Return _name
End Get
Set(value As String)
System.ComponentModel.DataAnnotations.Validator.ValidateProperty(value, NameValidationContext)
_name = value
End Set
End Property
<System.ComponentModel.DataAnnotations.Required()>
Public Property Description As String
Get
Return _description
End Get
Set(value As String)
System.ComponentModel.DataAnnotations.Validator.ValidateProperty(value, DescriptionValidationContext)
_description = value
End Set
End Property
Public ReadOnly Property Fertilizer As FertilizerCombination
Get
Return _fertilizer
End Get
End Property
Public Sub New(name As String, description As String, nitrogen As Integer, phosphorous As Integer, potassium As Integer)
_fertilizer = New FertilizerCombination(nitrogen, phosphorous, potassium)
Me.Name = name
Me.Description = description
End Sub
Private NameValidationContext = New System.ComponentModel.DataAnnotations.ValidationContext(Me, Nothing, Nothing) With {.MemberName = "Name"}
Private DescriptionValidationContext = New System.ComponentModel.DataAnnotations.ValidationContext(Me, Nothing, Nothing) With {.MemberName = "Description"}
End Class
现在这是我的 XAML,用于一个名为“FunWithFocus”的窗口:
<Grid>
<Grid.Resources>
<x:Array x:Key="CombinationOptions" Type="sys:Int32" xmlns:sys="clr-namespace:System;assembly=mscorlib">
<sys:Int32>0</sys:Int32>
<sys:Int32>1</sys:Int32>
<sys:Int32>2</sys:Int32>
<sys:Int32>3</sys:Int32>
<sys:Int32>4</sys:Int32>
<sys:Int32>5</sys:Int32>
<sys:Int32>6</sys:Int32>
<sys:Int32>7</sys:Int32>
<sys:Int32>8</sys:Int32>
<sys:Int32>9</sys:Int32>
<sys:Int32>10</sys:Int32>
<sys:Int32>11</sys:Int32>
<sys:Int32>12</sys:Int32>
<sys:Int32>13</sys:Int32>
<sys:Int32>14</sys:Int32>
<sys:Int32>15</sys:Int32>
<sys:Int32>16</sys:Int32>
<sys:Int32>17</sys:Int32>
<sys:Int32>18</sys:Int32>
<sys:Int32>19</sys:Int32>
<sys:Int32>20</sys:Int32>
</x:Array>
<local:PlantSolutionVM x:Key="PlantSolutionVM" />
<Style TargetType="DockPanel">
<Style.Triggers>
<Trigger Property="Validation.HasError" Value="true">
<Setter Property="ToolTip" Value="{Binding RelativeSource={x:Static RelativeSource.Self}, Path=(Validation.Errors)[0].ErrorContent}"/>
</Trigger>
</Style.Triggers>
</Style>
<Style TargetType="TextBox">
<Style.Triggers>
<Trigger Property="Validation.HasError" Value="true">
<Setter Property="ToolTip" Value="{Binding RelativeSource={x:Static RelativeSource.Self}, Path=(Validation.Errors)[0].ErrorContent}"/>
</Trigger>
</Style.Triggers>
</Style>
</Grid.Resources>
<Grid DataContext="{Binding Source={StaticResource PlantSolutionVM}, Path=PlantSolution}" Margin="50">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*"/>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBlock Text="Solution Name:" Grid.Row="0" Grid.Column="0" Margin="5"/>
<TextBox x:Name="ItemName" Text="{Binding Name, ValidatesOnDataErrors=True, ValidatesOnExceptions=True}" Grid.Row="0" Grid.Column="1" VerticalAlignment="Top" Margin="5"/>
<TextBlock Text="Description of Problem:" Grid.Row="1" Grid.Column="0" Margin="5"/>
<TextBox x:Name="ItemDescription" Text="{Binding Description, ValidatesOnDataErrors=True, ValidatesOnExceptions=True}" Grid.Row="1" Grid.Column="1" Margin="5"/>
<TextBlock Text="Recommended Fertilizer:" Grid.Row="2" Grid.Column="0" Margin="5"/>
<DockPanel x:Name="FertilizerCombinationContainer" DataContext="{Binding Fertilizer}" Grid.Row="2" Grid.Column="1" Margin="5" HorizontalAlignment="Left" VerticalAlignment="Top">
<DockPanel.BindingGroup>
<BindingGroup NotifyOnValidationError="True">
<BindingGroup.ValidationRules>
<local:FertilizerCombinationValidationRule />
</BindingGroup.ValidationRules>
</BindingGroup>
</DockPanel.BindingGroup>
<ComboBox x:Name="NitrogenValue" HorizontalAlignment="Left" VerticalAlignment="Top"
ItemsSource="{StaticResource CombinationOptions}"
SelectedItem="{Binding Nitrogen}"/>
<ComboBox x:Name="PhosphorousValue" HorizontalAlignment="Left" VerticalAlignment="Top"
ItemsSource="{StaticResource CombinationOptions}"
SelectedItem="{Binding Phosphorous}"/>
<ComboBox x:Name="PotatssiumValue" HorizontalAlignment="Left" VerticalAlignment="Top"
ItemsSource="{StaticResource CombinationOptions}"
SelectedItem="{Binding Potassium}"/>
</DockPanel>
<Button x:Name="SaveIt" Content="Save" Grid.Row="3" Grid.Column="1"/>
</Grid>
</Grid>
这是页面背后的代码:
Class FunWithFocus
Private Sub FertilizerCombinationContainer_Loaded(sender As Object, e As System.Windows.RoutedEventArgs) Handles FertilizerCombinationContainer.Loaded
'FertilizerCombinationContainer.BindingGroup.CancelEdit()'
'FertilizerCombinationContainer.BindingGroup.BeginEdit()'
End Sub
Private Sub FertilizerCombinationContainer_LostFocus(sender As Object, e As System.Windows.RoutedEventArgs) Handles FertilizerCombinationContainer.LostFocus
'This will get fired if the user drops down one of the comboboxes'
End Sub
'This is how I am currently handling the lost focus...but it doesn't fire if the user clicks somewhere that doesn't take keyboard focus'
'Private Sub FertilizerCombinationContainer_IsKeyboardFocusWithinChanged(sender As Object, e As System.Windows.DependencyPropertyChangedEventArgs) Handles FertilizerCombinationContainer.IsKeyboardFocusWithinChanged'
' If FertilizerCombinationContainer.IsKeyboardFocusWithin = False Then'
' FertilizerCombinationContainer.BindingGroup.ValidateWithoutUpdate()'
' If Validation.GetErrors(FertilizerCombinationContainer).Count = 0 Then'
' FertilizerCombinationContainer.BindingGroup.CommitEdit()'
' Dim bg As BindingGroup = FertilizerCombinationContainer.BindingGroup'
' Dim fertilizerCombo As FertilizerCombination = bg.Items(0)'
' Dim proposedNitrogen As Integer'
' Dim proposedPhosphorous As Integer'
' Dim proposedPotassium As Integer'
' Try'
' proposedNitrogen = bg.GetValue(fertilizerCombo, "Nitrogen")'
' proposedPhosphorous = bg.GetValue(fertilizerCombo, "Phosphorous")'
' proposedPotassium = bg.GetValue(fertilizerCombo, "Potassium")'
' ''there was a change: set it'
' fertilizerCombo.SetFertilizerCombination(proposedNitrogen, proposedPhosphorous, proposedPotassium)'
' Catch noValue As System.Windows.Data.ValueUnavailableException'
' ''a binding was not properly bound yet'
' End Try'
' End If'
' End If'
'End Sub'
End Class
如果您在处理 FertilizerCombinationContainer 的 LostFocus 事件的方法上放置一个断点,您会注意到当您选择其中一个 ComboBoxes 时会触发它,即使它是 FertilizerContainer 元素的子元素。
谢谢,
-弗林尼