WPF父元素丢失焦点

时间:2013-04-17 15:13:38

标签: wpf focus

我正在开发一个WPF应用程序。在我的一个页面中,我有一个DockPanel,里面有3个ComboBox。这3个组合框彼此依赖。所以,我在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事件的方法上设置一个断点,你会注意到当你选择其中一个ComboBox时它会被触发,即使它是FertilizerContainer元素的子代。

谢谢,

-Frinny

0 个答案:

没有答案