Estilo de error de validación en WPF, similar a Silverlight

Por defecto, Validation.ErrorTemplate en WPF es solo un pequeño borde rojo sin ToolTip .

En Silverlight 4 , el error de validación está muy bien diseñado desde el primer momento.

Aquí hay una comparación de un error de validación que ocurre en Silverlight 4 y WPF

Silverlight 4
enter image description here
WPF
enter image description here

Observe el aspecto realmente plano y aburrido de la versión de WPF en comparación con, en mi opinión, una gran apariencia en Silverlight.

¿Existen estilos / plantillas de validación similares en el Marco de WPF o alguien ha creado plantillas de validación de estilo agradable como la versión de Silverlight anterior? ¿O tendré que crearlos desde cero?

Si alguien quiere probarlo, el error de validación anterior puede reproducirse con el siguiente código, funciona tanto para Silverlight como para WPF.

MainWindow / MainPage.xaml

   

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; } } 

Estudié la versión de Silverlight de la Plantilla de error de validación y creé una versión de WPF que se parece a esta

enter image description here
Agregué un GIF animado en la parte inferior de la publicación, pero después de que lo terminé noté que podría ser molesto debido al movimiento del mouse en él. Avísame si debería eliminarlo … 🙂

MultiBinding un MultiBinding con BooleanOrConverter para mostrar el “tooltip-error” cuando el TextBox tiene el foco del teclado o el mouse está sobre la esquina superior derecha. Para la animación de fundido de entrada utilicé una DoubleAnimation para la Opacity y una BackEase ThicknessAnimation con una función BackEase / EaseOut EasingFunction para el Margin

Utilizable de esta manera

  

errorTemplateSilverlightStyle

                                                

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

Esta respuesta simplemente se expande en la excelente respuesta de Fredrik Hedblad . Siendo nuevo en WPF y XAML, la respuesta de Fredrik sirvió como trampolín para definir cómo quería que los errores de validación se mostraran en mi aplicación. Mientras que el XAML a continuación funciona para mí, es un trabajo en progreso. No lo he probado completamente, y admitiré fácilmente que no puedo explicar completamente cada etiqueta. Con esas advertencias, espero que esto sea útil para otros.

Si bien el TextBlock animado es un buen enfoque, tiene dos deficiencias que quería abordar.

  1. En primer lugar, como observó el comentario de Brent , el texto está limitado por los bordes de la ventana propietaria, de modo que si el control no válido está en el borde de la ventana, el texto se corta. La solución sugerida por Fredrik fue mostrarla “fuera de la ventana”. Eso tiene sentido para mí.
  2. En segundo lugar, mostrar el TextBlock a la derecha del control no válido no siempre es óptimo. Por ejemplo, supongamos que TextBlock se usa para especificar un archivo en particular para abrir y que hay un botón Examinar a su derecha. Si el usuario escribe un archivo inexistente, el error TextBlock cubrirá el botón Examinar y posiblemente evitará que el usuario haga clic en él para corregir el error. Lo que tiene sentido para mí es que el mensaje de error se muestre en diagonal hacia arriba y hacia la derecha del control no válido. Esto logra dos cosas. En primer lugar, evita ocultar los controles complementarios a la derecha del control no válido. También tiene el efecto visual de que toolTipCorner apunta hacia el mensaje de error.

Aquí está el diálogo alrededor del cual hice mi desarrollo.

Diálogo básico

Como puede ver, hay dos controles TextBox que deben validarse. Ambos están relativamente cerca del borde derecho de la ventana, por lo que los mensajes de error largos probablemente se recortarán. Y observe que el segundo TextBox tiene un botón Examinar que no quiero ocultar en caso de error.

Así que aquí está el error de validación usando mi implementación.

enter image description here

Funcionalmente, es muy similar a la implementación de Fredrik. Si TextBox tiene foco, el error estará visible. Una vez que pierde el foco, el error desaparece. Si el usuario coloca el mouse sobre la toolTipCorner , el error aparecerá independientemente de si TextBox tiene foco o no. También hay algunos cambios cosméticos, como toolTipCorner que es un 50% más grande (9 píxeles frente a 6 píxeles).

La diferencia obvia, por supuesto, es que mi implementación usa una ventana emergente para mostrar el error. Esto resuelve el primer inconveniente porque Popup muestra sus contenidos en su propia ventana, por lo que no está limitado por los bordes del diálogo. Sin embargo, usar un Popup presentó algunos desafíos para superar.

  1. Según las pruebas y las discusiones en línea, parece que Popup se considera una ventana superior. Entonces, incluso cuando mi aplicación estaba oculta por otra aplicación, la ventana emergente aún era visible. Este fue un comportamiento menos que deseable.
  2. El otro problema era que si el usuario movía o cambiaba el tamaño del cuadro de diálogo mientras el Popup estaba visible, el Popup no se reposicionaba para mantener su posición relativa al control no válido.

Afortunadamente, ambos desafíos se han abordado.

Aquí está el código. Comentarios y refinamientos son bienvenidos!


  • Archivo: ErrorTemplateSilverlightStyle.xaml
  • Espacio de nombres: MyApp.Application.UI.Templates
  • Asamblea: MyApp.Application.UI.dll

                               


  • Archivo: RepositionPopupBehavior.cs
  • Espacio de nombres: MyApp.Application.UI.Behaviors
  • Asamblea: MyApp.Application.UI.dll

( NOTA: ESTO REQUIERE LA EXPRESSION BLEND 4 System.Windows.Interactivity ASSEMBLY)

 using System; using System.Windows; using System.Windows.Controls.Primitives; using System.Windows.Interactivity; namespace MyApp.Application.UI.Behaviors { ///  /// Defines the reposition behavior of a  control when the window to which it is attached is moved or resized. ///  ///  /// This solution was influenced by the answers provided by NathanAW and /// Jason to /// this question. ///  public class RepositionPopupBehavior : Behavior { #region Protected Methods ///  /// Called after the behavior is attached to an . ///  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; } ///  /// Called when the behavior is being detached from its , but before it has actually occurred. ///  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 ///  /// Handles the  routed event which occurs when the window's location changes. ///  ///  /// The source of the event. ///  ///  /// An object that contains the event data. ///  private void OnLocationChanged(object sender, EventArgs e) { var offset = AssociatedObject.HorizontalOffset; AssociatedObject.HorizontalOffset = offset + 1; AssociatedObject.HorizontalOffset = offset; } ///  /// Handles the  routed event which occurs when either then  or the ///  properties change value. ///  ///  /// The source of the event. ///  ///  /// An object that contains the event data. ///  private void OnSizeChanged(object sender, SizeChangedEventArgs e) { var offset = AssociatedObject.HorizontalOffset; AssociatedObject.HorizontalOffset = offset + 1; AssociatedObject.HorizontalOffset = offset; } #endregion Private Methods } } 


  • Archivo: ResourceLibrary.xaml
  • Espacio de nombres: MyApp.Application.UI
  • Asamblea: MyApp.Application.UI.dll

    ...     ...  


  • Archivo: App.xaml
  • Espacio de nombres: MyApp.Application
  • Asamblea: MyApp.exe

          


  • Archivo: NewProjectView.xaml
  • Espacio de nombres: MyApp.Application.Views
  • Asamblea: MyApp.exe

       ...   ...  

Creé mi decoder de errores personalizado en uno de los proyectos para mostrar el adorno de error justo debajo de mi cuadro de texto con un mensaje de error. Solo necesita establecer la propiedad “Validation.ErrorTemplate” en su estilo predeterminado de cuadro de texto que puede guardar en los recursos de su aplicación para que se aplique a todos los cuadros de texto en su aplicación.

Nota: He usado algunos pinceles aquí, reemplázalo con tu propio conjunto de pinceles que quieras para tu decoder messgae. Puede ser esto puede ser de alguna ayuda:

                         

Me encontré con un problema al tratar de aplicarlo a un proyecto de wpf en el que estoy trabajando. Si tiene el siguiente problema cuando intenta ejecutar el proyecto:

“Se produjo una excepción de tipo ‘System.Windows.Markup.XamlParseException’ en PresentationFramework.dll pero no se manejó en el código de usuario”

Necesita crear una instancia de la clase booleanOrConverter en sus recursos (dentro de app.xaml):

  

Además, no olvide agregar el espacio de nombres al principio del archivo (en la etiqueta de la aplicación):

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