WPF Validação não disparando na primeira LostFocus do TextBox
-
01-07-2019 - |
Pergunta
Eu estou tentando validar o formulário WPF contra um objeto. Os incêndios de validação quando eu digitar algo no foco perder caixa de texto voltar para a caixa de texto e, em seguida, apagar o que eu escrevi. Mas se eu simplesmente carregar o aplicativo WPF e guia para fora da caixa de texto sem escrever e apagar qualquer coisa de caixa de texto, então não é demitido.
Aqui é a classe Customer.cs:
public class Customer : IDataErrorInfo
{
public string FirstName { get; set; }
public string LastName { get; set; }
public string Error
{
get { throw new NotImplementedException(); }
}
public string this[string columnName]
{
get
{
string result = null;
if (columnName.Equals("FirstName"))
{
if (String.IsNullOrEmpty(FirstName))
{
result = "FirstName cannot be null or empty";
}
}
else if (columnName.Equals("LastName"))
{
if (String.IsNullOrEmpty(LastName))
{
result = "LastName cannot be null or empty";
}
}
return result;
}
}
}
E aqui está o código WPF:
<TextBlock Grid.Row="1" Margin="10" Grid.Column="0">LastName</TextBlock>
<TextBox Style="{StaticResource textBoxStyle}" Name="txtLastName" Margin="10"
VerticalAlignment="Top" Grid.Row="1" Grid.Column="1">
<Binding Source="{StaticResource CustomerKey}" Path="LastName"
ValidatesOnExceptions="True" ValidatesOnDataErrors="True"
UpdateSourceTrigger="LostFocus"/>
</TextBox>
Solução
Se você não é adverso a colocar um pouco de lógica em seu atrás de código, você pode lidar com o real LostFocus evento com algo como isto:
XAML
<TextBox LostFocus="TextBox_LostFocus" ....
.xaml.cs
private void TextBox_LostFocus(object sender, RoutedEventArgs e)
{
((Control)sender).GetBindingExpression(TextBox.TextProperty).UpdateSource();
}
Outras dicas
Infelizmente isso é por design. validação de WPF só é acionado se o valor no controle mudou.
Inacreditável, mas verdadeiro. Até agora, a validação WPF é a dor proverbial grande - é terrível.
Uma das coisas que você pode fazer, porém, é obter a expressão de vinculação de propriedade do controle e manualmente invocar as validações. É chato, mas funciona.
Dê uma olhada na ValidatesOnTargetUpdated propriedade de ValidationRule. Ele irá validar os dados quando é carregada pela primeira vez. Isso é bom se você está tentando recuperar os campos vazios ou nulos.
Você iria atualizar o seu elemento de ligação como esta:
<Binding
Source="{StaticResource CustomerKey}"
Path="LastName"
ValidatesOnExceptions="True"
ValidatesOnDataErrors="True"
UpdateSourceTrigger="LostFocus">
<Binding.ValidationRules>
<DataErrorValidationRule
ValidatesOnTargetUpdated="True" />
</Binding.ValidationRules>
</Binding>
Eu descobri a melhor maneira para eu lidar com isso era sobre o evento LostFocus da caixa de texto eu fazer algo assim
private void dbaseNameTextBox_LostFocus(object sender, RoutedEventArgs e)
{
if (string.IsNullOrWhiteSpace(dbaseNameTextBox.Text))
{
dbaseNameTextBox.Text = string.Empty;
}
}
Em seguida, ele vê um erro
Eu já passei pelo mesmo problema e encontrou uma maneira ultra simples de resolver isso: no evento Loaded da sua janela, basta colocar txtLastName.Text = String.Empty. É isso aí!! Desde a propriedade de seu objeto mudou (sido definida como uma cadeia vazia), disparo da validação!
O código a seguir faz um loop sobre todos os controles e valida-los. Não necessariamente a maneira preferida, mas parece funcionar. Ele só faz TextBlocks e TextBoxes mas você pode facilmente mudá-lo.
public static class PreValidation
{
public static IEnumerable<T> FindVisualChildren<T>(DependencyObject depObj) where T : DependencyObject
{
if (depObj != null)
{
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(depObj); i++)
{
DependencyObject child = VisualTreeHelper.GetChild(depObj, i);
if (child != null && child is T)
{
yield return (T)child;
}
foreach (T childOfChild in FindVisualChildren<T>(child))
{
yield return childOfChild;
}
}
}
}
public static void Validate(DependencyObject depObj)
{
foreach(var c in FindVisualChildren<FrameworkElement>(depObj))
{
DependencyProperty p = null;
if (c is TextBlock)
p = TextBlock.TextProperty;
else if (c is TextBox)
p = TextBox.TextProperty;
if (p != null && c.GetBindingExpression(p) != null) c.GetBindingExpression(p).UpdateSource();
}
}
}
Basta ligar Validar em sua janela ou o controle e deve pré-validá-los para você.