WPF父元素失去焦点

WPF父元素失去焦点,wpf,focus,Wpf,Focus,我正在开发一个WPF应用程序。在我的一个页面中,我有一个DockPanel,里面有3个组合框。这3个组合框相互依赖。因此,我在DockPanel上有一个绑定组,并为该绑定组提供了一个验证规则 我想在DockPanel失去焦点时验证这3个组合框;但是,DockPanel的LostFocus事件是在用户单击其中一个子组合框时触发的 我原以为,如果子控件有焦点,父控件也会有焦点……但事实似乎并非如此 我正在寻找一种解决方案,以便在DockPanel失去焦点时对绑定组执行验证…其中焦点将丢失给不是其子控

我正在开发一个WPF应用程序。在我的一个页面中,我有一个DockPanel,里面有3个组合框。这3个组合框相互依赖。因此,我在DockPanel上有一个绑定组,并为该绑定组提供了一个验证规则

我想在DockPanel失去焦点时验证这3个组合框;但是,DockPanel的LostFocus事件是在用户单击其中一个子组合框时触发的

我原以为,如果子控件有焦点,父控件也会有焦点……但事实似乎并非如此

我正在寻找一种解决方案,以便在DockPanel失去焦点时对绑定组执行验证…其中焦点将丢失给不是其子控件之一的控件

编辑:

因此,我创建了一个尽可能简单的应用程序来演示这个问题

我有一个“肥料组合”类,它包含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
我有一个“PlantSolution”类,它具有FertifizerCombination属性。我还为该类提供了一个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
如果在处理FertizerCombinationContainer的LostFocus事件的方法上放置断点,您将注意到,当您选择其中一个组合框时,即使它是FertizerContainer元素的子元素,也会触发它

谢谢,


-Frinny

提供您的xaml源代码会很有帮助。请查看酒店。谢谢HighCore,这就是我目前正在使用的,但是我觉得它不是100%正确。我发布了一个示例项目的代码,你可以用它来自己尝试。我对IsKeyboardFocusWithin属性不100%满意的原因是,如果我单击某个不带键盘焦点的地方(例如,窗口本身)那么验证就不会被执行了……不过我还是希望这样
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
    <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