WPF validation adorners - only show if the control has held focus before
In my WPF application, I want to only show the validation adorner after a control has been edited/entered/focused by the user. This way the user is given a chance to provide valid input into the field and only if they chose not to, then the validation will display.
We want to encourage every field to be completed so indicating mandatory fields when the form first opens may circumvent that as immediately the user will be inclined to just complete what they need to in order to get rid of the big red validation errors which may also circumvent the flow of the form.
Is there a way to know if a control has held focus yet? Would an attached property maybe work?
In case it helps provide a more concrete response: here is my current validation style that displays a red border [if the control has a border] and little exclamation mark with a tooltip for the error message (pretty standard really):
<Style TargetType="Control">
<Style.Triggers>
<Trigger Property="Validation.HasError" Value="true">
<Setter Property="ToolTip"
Value="{Binding RelativeSource={x:Static RelativeSource.Self},
Path=(Validation.Errors).CurrentItem.ErrorContent}"/>
<Setter Property="Validation.ErrorTemplate">
<Setter.Value>
<ControlTemplate>
<DockPanel LastChildFill="true">
<Image Source="../Resources/Icons/Error.ico" Margin="4" Width="15" ToolTip="{Binding ElementName=customAdorner, Path=AdornedElement.(Validation.Errors).CurrentItem.ErrorContent}" />
<AdornedElementPlaceholder Name="customAdorner" VerticalAlignment="Center" >
<Border BorderBrush="red" BorderThickness="1" Visibility="{Binding ElementName=customAdorner, Path=AdornedElement.BorderThickness, Converter={StaticResource hasBorderToVisibilityConverter}}" />
</AdornedElementPlaceholder>
开发者_Python百科 </DockPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
</Trigger>
<Trigger Property="IsVisible" Value="False">
<Setter Property="Validation.ErrorTemplate" Value="{x:Null}"/>
</Trigger>
</Style.Triggers>
</Style>
You could combine an Attached Behavior with an Attached Property to do this. The Attached Behavior, ObserveFocus
will subscribe to the GotFocus
event and in the event handler set the HasHeldFocus
Attached Property to True
It could be used to set a Property in a ViewModel like this
<Button local:HasHeldFocusBehavior.ObserveFocus="True"
local:HasHeldFocusBehavior.HasHeldFocus="{Binding HasHeldFocus,
Mode=OneWayToSource}"/>
Here is an example of how it could be used to change the Background
of a Button
once it has been Focused
<Style TargetType="Button">
<Setter Property="Background" Value="Red"/>
<Setter Property="local:HasHeldFocusBehavior.ObserveFocus" Value="True"/>
<Style.Triggers>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource Self},
Path=(local:HasHeldFocusBehavior.HasHeldFocus)}"
Value="True">
<Setter Property="Background" Value="Green"/>
</DataTrigger>
</Style.Triggers>
</Style>
HasHeldFocusBehavior
public static class HasHeldFocusBehavior
{
public static readonly DependencyProperty ObserveFocusProperty =
DependencyProperty.RegisterAttached("ObserveFocus",
typeof(bool),
typeof(HasHeldFocusBehavior),
new UIPropertyMetadata(false, OnObserveFocusChanged));
public static bool GetObserveFocus(DependencyObject obj)
{
return (bool)obj.GetValue(ObserveFocusProperty);
}
public static void SetObserveFocus(DependencyObject obj, bool value)
{
obj.SetValue(ObserveFocusProperty, value);
}
private static void OnObserveFocusChanged(DependencyObject dpo,
DependencyPropertyChangedEventArgs e)
{
UIElement element = dpo as UIElement;
element.Focus();
if ((bool)e.NewValue == true)
{
SetHasHeldFocus(element, element.IsFocused);
element.GotFocus += element_GotFocus;
}
else
{
element.GotFocus -= element_GotFocus;
}
}
static void element_GotFocus(object sender, RoutedEventArgs e)
{
UIElement element = sender as UIElement;
SetHasHeldFocus(element, true);
}
private static readonly DependencyProperty HasHeldFocusProperty =
DependencyProperty.RegisterAttached("HasHeldFocus",
typeof(bool),
typeof(HasHeldFocusBehavior),
new UIPropertyMetadata(false));
public static void SetHasHeldFocus(DependencyObject element, bool value)
{
element.SetValue(HasHeldFocusProperty, value);
}
public static bool GetHasHeldFocus(DependencyObject element)
{
return (bool)element.GetValue(HasHeldFocusProperty);
}
}
Update
In your case, you could replace the Validation.HasError
Trigger with a MultiTrigger
<Style TargetType="Control">
<Style.Triggers>
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding RelativeSource={RelativeSource Self},
Path=(Validation.HasError)}"
Value="True"/>
<Condition Binding="{Binding RelativeSource={RelativeSource Self},
Path=(local:HasHeldFocusBehavior.HasHeldFocus)}"
Value="True"/>
</MultiDataTrigger.Conditions>
<!-- Setters.. -->
</MultiDataTrigger>
<!-- ... -->
</Style.Triggers>
</Style>
精彩评论