WPF validation error style similar to Silverlight

By default, Validation.ErrorTemplate in WPF is a small red frame without ToolTip .

In Silverlight 4, a validation error is correctly framed out of the box.

Below is a comparison of validation errors in Silverlight 4 and WPF

Silverlight 4
enter image description here
WPF
enter image description here

Pay attention to the really flat, boring look of the WPF version compared to, in my opinion, the wonderful look in Silverlight.

Are there any similar styles / validation patterns in the WPF Framework, or has anyone created beautifully designed validation patterns such as the Silverlight version above? Or will I have to create them from scratch?

If someone wants to try, the verification error above can be reproduced using the following code, works for both Silverlight and WPF

MainWindow / MainPage.xaml

 <StackPanel Orientation="Horizontal" Margin="10" VerticalAlignment="Top"> <TextBox Text="{Binding Path=TextProperty, Mode=TwoWay, ValidatesOnExceptions=True}"/> <Button Content="Tab To Me..." Margin="20,0,0,0"/> </StackPanel> 

MainWindow / MainPage.xaml.cs

 public MainWindow/MainPage() { InitializeComponent(); this.DataContext = this; } private string _textProperty; public string TextProperty { get { return _textProperty; } set { if (value.Length > 5) { throw new Exception("Too many characters"); } _textProperty = value; } } 
+56
c # styles wpf silverlight
Sep 15 '11 at 16:27
source share
4 answers

I examined the Silverlight version of the validation error template and created a version of WPF that looks like this:

enter image description here
Added an animated GIF at the bottom of the post, but after I finished it, I noticed that it could be annoying because of the moving mouse in it. Let me know if I delete it .. :)

I used MultiBinding with a BooleanOrConverter to show a “hint error” when the TextBox has keyboard focus or the mouse is above the upper right corner. To animate the attenuation, I used DoubleAnimation for Opacity and a ThicknessAnimation with BackEase / EaseOut EasingFunction for Margin

Used as

 <TextBox Validation.ErrorTemplate="{StaticResource errorTemplateSilverlightStyle}" /> 

errorTemplateSilverlightStyle

 <ControlTemplate x:Key="errorTemplateSilverlightStyle"> <StackPanel Orientation="Horizontal"> <Border BorderThickness="1" BorderBrush="#FFdc000c" CornerRadius="0.7" VerticalAlignment="Top"> <Grid> <Polygon x:Name="toolTipCorner" Grid.ZIndex="2" Margin="-1" Points="6,6 6,0 0,0" Fill="#FFdc000c" HorizontalAlignment="Right" VerticalAlignment="Top" IsHitTestVisible="True"/> <Polyline Grid.ZIndex="3" Points="7,7 0,0" Margin="-1" HorizontalAlignment="Right" StrokeThickness="1.5" StrokeEndLineCap="Round" StrokeStartLineCap="Round" Stroke="White" VerticalAlignment="Top" IsHitTestVisible="True"/> <AdornedElementPlaceholder x:Name="adorner"/> </Grid> </Border> <Border x:Name="errorBorder" Background="#FFdc000c" Margin="1,0,0,0" Opacity="0" CornerRadius="1.5" IsHitTestVisible="False" MinHeight="24" MaxWidth="267"> <Border.Effect> <DropShadowEffect ShadowDepth="2.25" Color="Black" Opacity="0.4" Direction="315" BlurRadius="4"/> </Border.Effect> <TextBlock Text="{Binding ElementName=adorner, Path=AdornedElement.(Validation.Errors)[0].ErrorContent}" Foreground="White" Margin="8,3,8,3" TextWrapping="Wrap"/> </Border> </StackPanel> <ControlTemplate.Triggers> <DataTrigger Value="True"> <DataTrigger.Binding> <MultiBinding Converter="{StaticResource BooleanOrConverter}"> <Binding ElementName="adorner" Path="AdornedElement.IsKeyboardFocused" /> <Binding ElementName="toolTipCorner" Path="IsMouseOver"/> </MultiBinding> </DataTrigger.Binding> <DataTrigger.EnterActions> <BeginStoryboard x:Name="fadeInStoryboard"> <Storyboard> <DoubleAnimation Duration="00:00:00.15" Storyboard.TargetName="errorBorder" Storyboard.TargetProperty="Opacity" To="1"/> <ThicknessAnimation Duration="00:00:00.15" Storyboard.TargetName="errorBorder" Storyboard.TargetProperty="Margin" FillBehavior="HoldEnd" From="1,0,0,0" To="5,0,0,0"> <ThicknessAnimation.EasingFunction> <BackEase EasingMode="EaseOut" Amplitude="2"/> </ThicknessAnimation.EasingFunction> </ThicknessAnimation> </Storyboard> </BeginStoryboard> </DataTrigger.EnterActions> <DataTrigger.ExitActions> <StopStoryboard BeginStoryboardName="fadeInStoryboard"/> <BeginStoryboard x:Name="fadeOutStoryBoard"> <Storyboard> <DoubleAnimation Duration="00:00:00" Storyboard.TargetName="errorBorder" Storyboard.TargetProperty="Opacity" To="0"/> </Storyboard> </BeginStoryboard> </DataTrigger.ExitActions> </DataTrigger> </ControlTemplate.Triggers> </ControlTemplate> 

BooleanOrConverter

 public class BooleanOrConverter : IMultiValueConverter { public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture) { foreach (object value in values) { if ((bool)value == true) { return true; } } return false; } public object[] ConvertBack(object value, Type[] targetTypes, object parameter, System.Globalization.CultureInfo culture) { throw new NotSupportedException(); } } 

enter image description here

+98
Sep 15 2018-11-11T00:
source share

This answer simply extends Fredrik Hedblad's excellent answer. Being new to WPF and XAML, Fredrik's answer served as a foothold to determine how I wanted validation errors to appear in my application. Although the XAML below works for me, it's work in progress. I have not fully tested it, and I readily admit that I cannot fully explain each tag. With these caveats, I hope this will prove useful to others.

While animated TextBlock is a great approach, it has two drawbacks that I wanted to address.

  • Firstly, since the comment is Brent , the text is limited to the borders of the use window, so if the invalid control is on the edge of the window, the text is disabled. Fredrick proposed a solution so that it appears "outside the window." That makes sense to me.
  • Secondly, displaying a TextBlock to the right of an invalid control is not always optimal. For example, let's say a TextBlock is used to indicate a specific file to open and that the Browse button to its right. If the user enters into a nonexistent file, the TextBlock error will cover the Browse button and could potentially prevent the user from clicking on it, correct the error. For me, it makes sense to have an error message displayed diagonally up and to the right of an invalid control. This does two things. First, it avoids hiding any auxiliary controls to the right of an invalid control. It also has a visual effect that toolTipCorner indicates an error message.

Here is the dialogue around which I made my development.

Basic dialog

As you can see, there are two TextBox elements that need to be checked. Both are relatively close to the right edge of the window, so long error messages are likely to be cropped. And note that the second TextBox has a browse button, which I don’t want to hide in case of an error.

So what does the validation error look like using my implementation.

enter image description here

Functionally, it is very similar to the Fredrik implementation. If the focus is TextBox , the error will be visible. As soon as he loses focus, the error disappears. If the user hovers over the toolTipCorner, the error will be displayed regardless of whether the TextBox has focus or not. There are also a few cosmetic changes, such as toolTipCorner, 50% more (9 pixels vs 6 pixels).

The obvious difference, of course, is that my implementation uses Popup to display the error. This eliminates the first drawback, because Popup displays its contents in its own window, so it is not limited by the boundaries of the dialog. However, using Popup did present a couple of challenges to overcome.

  • From testing and online discussions, it is clear that Popup is considered the topmost window. That way, even when my application was hidden by another application, Popup was still visible. This behavior was less desirable.
  • Another way was that if the user moved or resized the dialog box when Popup was shown, Popup did not move to maintain its position relative to the invalid control.

Fortunately, both of these problems have been resolved.

Here is the code. Comments and clarifications are welcome!




  • File: ErrorTemplateSilverlightStyle.xaml
  • Namespace: MyApp.Application.UI.Templates
  • Assembly: MyApp.Application.UI.dll

 <ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity" xmlns:behaviors="clr-namespace:MyApp.Application.UI.Behaviors"> <ControlTemplate x:Key="ErrorTemplateSilverlightStyle"> <StackPanel Orientation="Horizontal"> <!-- Defines TextBox outline border and the ToolTipCorner --> <Border x:Name="border" BorderThickness="1.25" BorderBrush="#FFDC000C"> <Grid> <Polygon x:Name="toolTipCorner" Grid.ZIndex="2" Margin="-1" Points="9,9 9,0 0,0" Fill="#FFDC000C" HorizontalAlignment="Right" VerticalAlignment="Top" IsHitTestVisible="True"/> <Polyline Grid.ZIndex="3" Points="10,10 0,0" Margin="-1" HorizontalAlignment="Right" StrokeThickness="1.5" StrokeEndLineCap="Round" StrokeStartLineCap="Round" Stroke="White" VerticalAlignment="Top" IsHitTestVisible="True"/> <AdornedElementPlaceholder x:Name="adorner"/> </Grid> </Border> <!-- Defines the Popup --> <Popup x:Name="placard" AllowsTransparency="True" PopupAnimation="Fade" Placement="Top" PlacementTarget="{Binding ElementName=toolTipCorner}" PlacementRectangle="10,-1,0,0"> <!-- Used to reposition Popup when dialog moves or resizes --> <i:Interaction.Behaviors> <behaviors:RepositionPopupBehavior/> </i:Interaction.Behaviors> <Popup.Style> <Style TargetType="{x:Type Popup}"> <Style.Triggers> <!-- Shows Popup when TextBox has focus --> <DataTrigger Binding="{Binding ElementName=adorner, Path=AdornedElement.IsFocused}" Value="True"> <Setter Property="IsOpen" Value="True"/> </DataTrigger> <!-- Shows Popup when mouse hovers over ToolTipCorner --> <DataTrigger Binding="{Binding ElementName=toolTipCorner, Path=IsMouseOver}" Value="True"> <Setter Property="IsOpen" Value="True"/> </DataTrigger> <!-- Hides Popup when window is no longer active --> <DataTrigger Binding="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}, Path=IsActive}" Value="False"> <Setter Property="IsOpen" Value="False"/> </DataTrigger> </Style.Triggers> </Style> </Popup.Style> <Border x:Name="errorBorder" Background="#FFDC000C" Margin="0,0,8,8" Opacity="1" CornerRadius="4" IsHitTestVisible="False" MinHeight="24" MaxWidth="267"> <Border.Effect> <DropShadowEffect ShadowDepth="4" Color="Black" Opacity="0.6" Direction="315" BlurRadius="4"/> </Border.Effect> <TextBlock Text="{Binding ElementName=adorner, Path=AdornedElement.(Validation.Errors).CurrentItem.ErrorContent}" Foreground="White" Margin="8,3,8,3" TextWrapping="Wrap"/> </Border> </Popup> </StackPanel> </ControlTemplate> </ResourceDictionary> 




  • File: RepositionPopupBehavior.cs
  • Namespace: MyApp.Application.UI.Behaviors
  • Assembly: MyApp.Application.UI.dll

( NOTE: THIS REQUIRES EXPRESSION EXPRESSION 4 System.Windows.Interactivity ASSEMBLY)

 using System; using System.Windows; using System.Windows.Controls.Primitives; using System.Windows.Interactivity; namespace MyApp.Application.UI.Behaviors { /// <summary> /// Defines the reposition behavior of a <see cref="Popup"/> control when the window to which it is attached is moved or resized. /// </summary> /// <remarks> /// This solution was influenced by the answers provided by <see href="https://stackoverflow.com/users/262204/nathanaw">NathanAW</see> and /// <see href="https://stackoverflow.com/users/718325/jason">Jason</see> to /// <see href="https://stackoverflow.com/questions/1600218/how-can-i-move-a-wpf-popup-when-its-anchor-element-moves">this</see> question. /// </remarks> public class RepositionPopupBehavior : Behavior<Popup> { #region Protected Methods /// <summary> /// Called after the behavior is attached to an <see cref="Behavior.AssociatedObject"/>. /// </summary> protected override void OnAttached() { base.OnAttached(); var window = Window.GetWindow(AssociatedObject.PlacementTarget); if (window == null) { return; } window.LocationChanged += OnLocationChanged; window.SizeChanged += OnSizeChanged; AssociatedObject.Loaded += AssociatedObject_Loaded; } void AssociatedObject_Loaded(object sender, RoutedEventArgs e) { //AssociatedObject.HorizontalOffset = 7; //AssociatedObject.VerticalOffset = -AssociatedObject.Height; } /// <summary> /// Called when the behavior is being detached from its <see cref="Behavior.AssociatedObject"/>, but before it has actually occurred. /// </summary> protected override void OnDetaching() { base.OnDetaching(); var window = Window.GetWindow(AssociatedObject.PlacementTarget); if (window == null) { return; } window.LocationChanged -= OnLocationChanged; window.SizeChanged -= OnSizeChanged; AssociatedObject.Loaded -= AssociatedObject_Loaded; } #endregion Protected Methods #region Private Methods /// <summary> /// Handles the <see cref="Window.LocationChanged"/> routed event which occurs when the window location changes. /// </summary> /// <param name="sender"> /// The source of the event. /// </param> /// <param name="e"> /// An object that contains the event data. /// </param> private void OnLocationChanged(object sender, EventArgs e) { var offset = AssociatedObject.HorizontalOffset; AssociatedObject.HorizontalOffset = offset + 1; AssociatedObject.HorizontalOffset = offset; } /// <summary> /// Handles the <see cref="Window.SizeChanged"/> routed event which occurs when either then <see cref="Window.ActualHeight"/> or the /// <see cref="Window.ActualWidth"/> properties change value. /// </summary> /// <param name="sender"> /// The source of the event. /// </param> /// <param name="e"> /// An object that contains the event data. /// </param> private void OnSizeChanged(object sender, SizeChangedEventArgs e) { var offset = AssociatedObject.HorizontalOffset; AssociatedObject.HorizontalOffset = offset + 1; AssociatedObject.HorizontalOffset = offset; } #endregion Private Methods } } 




  • File: ResourceLibrary.xaml
  • Namespace: MyApp.Application.UI
  • Assembly: MyApp.Application.UI.dll

 <ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"> <ResourceDictionary.MergedDictionaries> <!-- Styles --> ... <!-- Templates --> <ResourceDictionary Source="Templates/ErrorTemplateSilverlightStyle.xaml"/> </ResourceDictionary.MergedDictionaries> <!-- Converters --> ... </ResourceDictionary> 




  • File: App.xaml
  • Namespace: MyApp.Application
  • Build: MyApp.exe

 <Application x:Class="MyApp.Application.App" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" StartupUri="Views\MainWindowView.xaml"> <Application.Resources> <ResourceDictionary> <ResourceDictionary.MergedDictionaries> <ResourceDictionary Source="/MyApp.Application.UI;component/ResourceLibrary.xaml"/> </ResourceDictionary.MergedDictionaries> </ResourceDictionary> </Application.Resources> </Application> 




  • File: NewProjectView.xaml
  • Namespace: MyApp.Application.Views
  • Build: MyApp.exe

 <Window x:Class="MyApp.Application.Views.NewProjectView" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:views="clr-namespace:MyApp.Application.Views" xmlns:viewModels="clr-namespace:MyApp.Application.ViewModels" Title="New Project" Width="740" Height="480" WindowStartupLocation="CenterOwner"> <!-- DATA CONTEXT --> <Window.DataContext> <viewModels:NewProjectViewModel/> </Window.DataContext> <!-- WINDOW GRID --> ... <Label x:Name="ProjectNameLabel" Grid.Column="0" Content="_Name:" Target="{Binding ElementName=ProjectNameTextBox}"/> <TextBox x:Name="ProjectNameTextBox" Grid.Column="2" Text="{Binding ProjectName, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True}" Validation.ErrorTemplate="{StaticResource ErrorTemplateSilverlightStyle}"/> ... </Window> 
+30
Dec 05 '13 at 7:56
source share

I created my error interlocutor in one of the projects to show the error advertiser just below my error message text box. You just need to set the "Validation.ErrorTemplate" property to your default style for the text field, which you can store in your application resources so that it applies to all text fields in your application.

Note. I used several brushes here, replaced them with my own set of brushes that you want to use for your musical advertiser. Maybe this can help:

 <Setter Property="Validation.ErrorTemplate"> <Setter.Value> <ControlTemplate> <StackPanel> <!--TextBox Error template--> <Canvas Panel.ZIndex="1099"> <DockPanel> <Border BorderBrush="{DynamicResource HighlightRedBackgroundBrush}" BorderThickness="2" Padding="1" CornerRadius="3"> <AdornedElementPlaceholder x:Name="ErrorAdorner" /> </Border> </DockPanel> <Popup IsOpen="True" AllowsTransparency="True" Placement="Bottom" PlacementTarget="{Binding ElementName=ErrorAdorner}" StaysOpen="False"> <Border Canvas.Bottom="4" Canvas.Left="{Binding Path=AdornedElement.ActualWidth, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type Adorner}}}" BorderBrush="{DynamicResource HighlightRedBackgroundBrush}" BorderThickness="1" Padding="4" CornerRadius="5" Background="{DynamicResource ErrorBackgroundBrush}"> <StackPanel Orientation="Horizontal"> <ContentPresenter Width="24" Height="24" Content="{DynamicResource ExclamationIcon}" /> <TextBlock TextWrapping="Wrap" Margin="4" MaxWidth="250" Text="{Binding Path=AdornedElement.(Validation.Errors)[0].ErrorContent, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type Adorner}}}" /> </StackPanel> </Border> </Popup> </Canvas> </StackPanel> </ControlTemplate> </Setter.Value> </Setter> 
+3
Sep 15 '11 at 18:36
source share

I ran into a problem while trying to apply it to the wpf project I'm working on. If you have the following problem while trying to start a project:

"An exception like" System.Windows.Markup.XamlParseException "occurred in PresentationFramework.dll but was not handled in user code"

You need to create an instance of the booleanOrConverter class in your resources (in the app.xaml application):

 <validators:BooleanOrConverter x:Key="myConverter" /> 

Also do not forget to add the namespace at the beginning of the file (in the application tag):

xmlns: validators = "clr-namespace: ParcelRatesViewModel.Validators; assembly = ParcelRatesViewModel"

0
May 30 '17 at 21:52
source share



All Articles