Estilo de erro de validação no WPF, semelhante ao Silverlight

Por padrão, o Validation.ErrorTemplate no WPF é apenas uma pequena borda vermelha sem nenhuma ToolTip .

No Silverlight 4 , o erro de validação é bem estilizado e pronto para uso.

Aqui está uma comparação de um erro de validação que ocorre no Silverlight 4 e no WPF

Silverlight 4
insira a descrição da imagem aqui
WPF
insira a descrição da imagem aqui

Observe a aparência realmente chata e chata da versão do WPF em comparação com a, na minha opinião, ótima aparência no Silverlight.

Algum modelo / modelo de validação similar existe no WPF Framework ou alguém criou modelos de validação bem estilizados como a versão do Silverlight acima? Ou vou ter que criá-los a partir do zero?

Se alguém quiser testá-lo, o erro de validação acima pode ser reproduzido com o seguinte código, funciona tanto para o Silverlight quanto para o 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; } } 

Estudei a versão do Silverlight do Modelo de Erro de Validação e criei uma versão do WPF que se parece com isso

insira a descrição da imagem aqui
Adicionado um GIF animado na parte inferior do post, mas depois que eu terminei, notei que poderia ser irritante por causa do mouse em movimento nele. Deixe-me saber se eu deveria removê-lo .. 🙂

Eu usei um MultiBinding com um BooleanOrConverter para mostrar o erro “tooltip-error” quando o TextBox tem foco no teclado ou o mouse está no canto superior direito. Para a animação fade-in usei um DoubleAnimation para a Opacity e uma ThicknessAnimation com um BackEase / EaseOut EasingFunction para a Margin

Utilizável como este

  

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

insira a descrição da imagem aqui

Esta resposta apenas expande a excelente resposta de Fredrik Hedblad . Sendo novo no WPF e XAML, a resposta de Fredrik serviu como um trampolim para definir como eu queria que erros de validação fossem exibidos em meu aplicativo. Enquanto o XAML abaixo funciona para mim, é um trabalho em andamento. Eu não testei completamente, e admitirei prontamente que não posso explicar totalmente todas as tags. Com essas ressalvas, espero que isso seja útil para os outros.

Enquanto o TextBlock animado é uma ótima abordagem, ele tem duas falhas que eu queria resolver.

  1. Primeiro, como observou o comentário de Brent , o texto é restringido pelas bordas da janela proprietária, de modo que, se o controle inválido estiver na borda da janela, o texto é cortado. A solução sugerida por Fredrik era mostrá-lo “fora da janela”. Isso faz sentido para mim.
  2. Segundo, mostrar o TextBlock à direita do controle inválido nem sempre é o ideal. Por exemplo, digamos que o TextBlock é usado para especificar um arquivo específico a ser aberto e que há um botão Procurar à sua direita. Se o usuário digitar um arquivo inexistente, o erro TextBlock cobrirá o botão Procurar e impedirá potencialmente que o usuário clique nele para corrigir o erro. O que faz sentido para mim é ter a mensagem de erro exibida diagonalmente para cima e para a direita do controle inválido. Isso realiza duas coisas. Primeiro, evita ocultar quaisquer controles complementares à direita do controle inválido. Ele também tem o efeito visual que o toolTipCorner está apontando para a mensagem de erro.

Aqui está o diálogo em torno do qual eu fiz meu desenvolvimento.

Diálogo Básico

Como você pode ver, existem dois controles TextBox que precisam ser validados. Ambos estão relativamente perto da borda direita da janela, portanto, mensagens de erro longas provavelmente seriam cortadas. E observe que o segundo TextBox tem um botão Browse que não quero escondido em caso de erro.

Então, aqui está o que um erro de validação parece usando minha implementação.

insira a descrição da imagem aqui

Funcionalmente, é muito semelhante à implementação de Fredrik. Se o TextBox tiver foco, o erro ficará visível. Uma vez que perde o foco, o erro desaparece. Se o usuário passar o mouse sobre o toolTipCorner , o erro aparecerá, independentemente de o TextBox ter ou não foco. Existem algumas mudanças cosméticas, como o toolTipCorner sendo 50% maior (9 pixels vs. 6 pixels).

A diferença óbvia, claro, é que minha implementação usa um Popup para exibir o erro. Isso resolve a primeira falha, pois o Pop – up exibe seu conteúdo em sua própria janela, portanto não é restringido pelas bordas da checkbox de diálogo. No entanto, usando um Popup apresentou alguns desafios para superar.

  1. Aparece de testes e discussões on-line que o Popup é considerado uma janela superior. Então, mesmo quando meu aplicativo estava oculto por outro aplicativo, o Popup ainda estava visível. Este foi um comportamento menos do que desejável.
  2. A outra pegadinha era que se o usuário movesse ou redimensionasse a checkbox de diálogo enquanto o Popup estivesse visível, o Popup não se reposicionaria para manter sua posição relativa ao controle inválido.

Felizmente, esses dois desafios foram resolvidos.

Aqui está o código. Comentários e refinamentos são bem vindos!


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

                               


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

( NOTA: ESTE REQUER A EXPRESSÃO 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 } } 


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

    ...     ...  


  • Arquivo: App.xaml
  • Namespace: MyApp.Application
  • Assembly: MyApp.exe

          


  • Arquivo: NewProjectView.xaml
  • Namespace: MyApp.Application.Views
  • Assembly: MyApp.exe

       ...   ...  

Eu criei meu adorno de erro personalizado em um dos projetos para mostrar o adorno de erro logo abaixo da minha checkbox de texto com mensagem de erro nele. Você só precisa definir a propriedade “Validation.ErrorTemplate” em seu estilo padrão de checkbox de texto que você pode manter nos resources do aplicativo para que ele seja aplicado a todas as checkboxs de texto em seu aplicativo.

Nota: Eu usei alguns pincéis aqui, substitua-o com o seu próprio conjunto de pincéis que você deseja para o seu adorno messgae. Pode ser que isso possa ser de alguma ajuda:

                         

Eu me deparei com um problema ao tentar aplicá-lo a um projeto wpf em que estou trabalhando. Se você está tendo o seguinte problema ao tentar executar o projeto:

“Uma exceção do tipo ‘System.Windows.Markup.XamlParseException’ ocorreu em PresentationFramework.dll, mas não foi tratada no código do usuário”

Você precisa criar uma instância da class booleanOrConverter em seus resources (dentro de app.xaml):

  

Também não esqueça de adicionar o namespace ao topo do arquivo (na tag do aplicativo):

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