0

我正在开发一个 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 元素的子元素。

谢谢,

-弗林尼

4

0 回答 0