WPF Binding ValidationRules on TextBox - wpf

This is my first attempt at using Binding.ValidationRules. They are working as expected with the exception of:
When I enter a TextBox value, for example: "abc" it's fine. When I backspace over letters "c", "b", then "a" the rule fires as it should and warns me the TextBox is empty. The strange behavior is that the string property, to which CompanyName is bound, still retains the "a". Therefore the string is not empty and I can't use the string.Empty comparison in my business logic.
It seems that the ValidationRule is preventing that last edit/character deletion to the string property.
I hope that makes sense, but I have included some images. The green "abc" Label in the image is bound to the same property as the TextBox.
What I want to do is control the visibility of a "Save" Button based on a valid TextBox. Pretty standard I would imagine, so I must be approaching this wrong or I a missing something simple.
<TextBox x:Name="CompanyNameTextBox" Grid.Row="1" TabIndex="1"
Style="{StaticResource TextBoxStyleWithError}"
Validation.ErrorTemplate="{StaticResource validationErrorTemplate}">
<TextBox.Text>
<Binding Path="objCompanyClass.CompanyName" UpdateSourceTrigger="PropertyChanged" >
<Binding.ValidationRules>
<HelperClass:CompanyNameValidator Max="50"/>
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
<Style x:Key="TextBoxStyleWithError" TargetType="TextBox">
<Style.Triggers>
<Trigger Property="Validation.HasError" Value="true">
<Setter Property="ToolTip" Value="{Binding RelativeSource={x:Static RelativeSource.Self}, Path=(Validation.Errors)[0].ErrorContent}"/>
<Setter Property="Background" Value="DarkRed"/>
<Setter Property="Margin" Value="3 15 3 3"/>
</Trigger>
</Style.Triggers>
</Style>
<ControlTemplate x:Key="validationErrorTemplate">
<DockPanel>
<StackPanel Orientation="Horizontal" DockPanel.Dock="Top" VerticalAlignment="Center" Margin="0 0 0 3">
<TextBlock Foreground="Red" FontSize="12" Margin="2,0,0,0"
Text="{Binding ElementName=ErrorAdorner, Path=AdornedElement.(Validation.Errors)[0].ErrorContent}">
</TextBlock>
</StackPanel>
<AdornedElementPlaceholder x:Name="ErrorAdorner" ></AdornedElementPlaceholder>
</DockPanel>
</ControlTemplate>
C#
public class CompanyNameValidator : ValidationRule
{
public static bool TextEntryValid = false;
private int _max;
public int Max
{
get { return _max; }
set { _max = value; }
}
public override ValidationResult Validate (object value, System.Globalization.CultureInfo cultureInfo)
{
if (string.IsNullOrEmpty(value.ToString()))
{
TextEntryValid = false;
return new ValidationResult(false, "Company Name cannot be empty.");
}
else
{
if (value.ToString().Length > Max)
{
TextEntryValid = false;
return new ValidationResult(false, $"Cannot be more than {Max} characters long.");
}
}
TextEntryValid = true;
return ValidationResult.ValidResult;
}
}

Related

WPF ValidationRule Binding to display the error message

In the following code, I am using ValidationRule to validate user input on a TextBox. It works fine. If user enters invalid data (any non-alphabet character) it displays a small red ! sign on the left of the textbox, and also if you mouseover the textbox it would display an invalid data message (as shown in the image below).
Question: How can we modify the following code so - instead of tooltip, the invalid data message is displayed just below the textbox?
Remarks: I tried replacing ToolTip with TexBlock in the following line <Setter Property="ToolTip" of the XAML below, and put a textblock below TexBox as follows: <TextBlock Text="{Binding (Validation.Errors)[0].ErrorContent, ElementName=txtTest}"/>. But (as expected) it did not work since TextBlock, of course, is not a property of TextBox
MyDataSource.cs:
public class MyDataSource
{
public MyDataSource()
{
}
public string FirstName { get; set; }
}
AlphabetsOnlyValidationRule:
public class AlphabetsOnlyValidationRule : ValidationRule
{
public override ValidationResult Validate(object value, CultureInfo cultureInfo)
{
string sVal = value as string;
if (string.IsNullOrEmpty(sVal))
{
return new ValidationResult(false, "Please enter some text");
}
if (!sVal.ToList().All(t => char.IsLetter(t)))
{
return new ValidationResult(false, "First Name should contains alphabets only");
}
return new ValidationResult(true, null);
}
}
MainWindow.xaml:
<Window.Resources>
<local:MyDataSource x:Key="Ods"/>
<local:AlphabetsOnlyValidationRule x:Key="AlphabetsOnlyKey"/>
<ControlTemplate x:Key="ValidationTemplate">
<DockPanel>
<TextBlock Foreground="Red" FontSize="20">!</TextBlock>
<AdornedElementPlaceholder/>
</DockPanel>
</ControlTemplate>
<Style x:Key="TextBoxInError" TargetType="{x:Type TextBox}">
<Style.Triggers>
<Trigger Property="Validation.HasError" Value="true">
<Setter Property="ToolTip" Value="{Binding RelativeSource={x:Static RelativeSource.Self}, Path=(Validation.Errors)[0].ErrorContent}"/>
</Trigger>
</Style.Triggers>
</Style>
</Window.Resources>
<Grid>
<TextBox x:Name="txtTest" Margin="48,0,572,388" Style="{StaticResource TextBoxInError}" Validation.ErrorTemplate="{StaticResource ValidationTemplate}">
<TextBox.Text>
<Binding Path="FirstName" Source="{StaticResource Ods}" UpdateSourceTrigger="PropertyChanged" >
<Binding.ValidationRules>
<local:AlphabetsOnlyValidationRule />
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
</Grid>
Screenshot of Validation Message [When user enters non-alphabet character]
Add a TextBlock that binds to the ErrorContent property to the Validation.ErrorTemplate:
<ControlTemplate x:Key="ValidationTemplate">
<DockPanel>
<TextBlock Foreground="Red" FontSize="20" DockPanel.Dock="Left">!</TextBlock>
<TextBlock Text="{Binding [0].ErrorContent}" Foreground="Red" DockPanel.Dock="Bottom"/>
<AdornedElementPlaceholder/>
</DockPanel>
</ControlTemplate>

How to invoke DataTrigger on a Toolbar Button during Validate.HasError

I have implemented the following xaml code to disable a toolbar button when Validation.HasError based on the text entered into the ComboBox based on the UserAccountValidationRule
The core xaml code is shown here:
<telerik:RadComboBox
x:Name="DataProviderComboBox" Width="120" Height="23" IsEditable="True" DataContext="{StaticResource MainWindowViewModel}">
<telerik:RadComboBox.Text>
<Binding Mode="TwoWay" Path="InputString">
<Binding.ValidationRules>
<validationRules:UserAccountValidationRule/>
</Binding.ValidationRules>
</Binding>
</telerik:RadComboBox.Text>
</telerik:RadComboBox>
<telerik:RadButton
Name="ToolbarButton" Width="74" HorizontalAlignment="Left" VerticalAlignment="Top" Content="Button">
<telerik:RadButton.Style>
<Style TargetType="{x:Type telerik:RadButton}">
<Style.Triggers>
<DataTrigger Binding="{Binding (Validation.HasError), ElementName=DataProviderComboBox}" Value="true" >
<Setter Property="IsEnabled" Value="false"/>
</DataTrigger>
</Style.Triggers>
</Style>
</telerik:RadButton.Style>
</telerik:RadButton>
</telerik:RadToolBar>
<Grid>
<telerik:RadButton
Name="OrdinaryButton" Width="74" HorizontalAlignment="Left" VerticalAlignment="Top" Content="Button" Margin="252,26,0,0">
<telerik:RadButton.Style>
<Style TargetType="{x:Type telerik:RadButton}">
<Style.Triggers>
<DataTrigger Binding="{Binding (Validation.HasError), ElementName=DataProviderComboBox}" Value="true" >
<Setter Property="IsEnabled" Value="false"/>
</DataTrigger>
</Style.Triggers>
</Style>
</telerik:RadButton.Style>
</telerik:RadButton>
</Grid>
What I would like is for the DataTrigger in the ToolBarButton to be invoked, but it is not.
I did a check on the validation by creating a second OrdinarryButton, and hooked this up to the validation, and this worked fine. It seems as if the DataTrigger does not work if the button is a toolbar item.
Can anyone explain how to get this to work?
So, as it turns out, if the button is an OrdinaryButton control, then the DataTrigger will be invoked and the button will be disabled on Validation.HasError = true via the xaml code below:
<Grid>
<telerik:RadButton
Name="OrdinaryButton" Width="74" HorizontalAlignment="Left" VerticalAlignment="Top" Content="Button" Margin="252,26,0,0">
<telerik:RadButton.Style>
<Style TargetType="{x:Type telerik:RadButton}">
<Style.Triggers>
<DataTrigger Binding="{Binding (Validation.HasError), ElementName=DataProviderComboBox}" Value="true" >
<Setter Property="IsEnabled" Value="false"/>
</DataTrigger>
</Style.Triggers>
</Style>
</telerik:RadButton.Style>
</telerik:RadButton>
</Grid>
However, if the button control is placed inside a ToolbarButton, the same xaml code will not disable the Toolbar button:
<telerik:RadComboBox
x:Name="DataProviderComboBox" Width="120" Height="23" IsEditable="True" DataContext="{StaticResource MainWindowViewModel}">
<telerik:RadComboBox.Text>
<Binding Mode="TwoWay" Path="InputString">
<Binding.ValidationRules>
<validationRulesParameterPass:UserAccountValidationRule/>
</Binding.ValidationRules>
</Binding>
</telerik:RadComboBox.Text>
</telerik:RadComboBox>
<telerik:RadButton
Name="ToolbarButton" Width="74" HorizontalAlignment="Left" VerticalAlignment="Top" Content="Button">
<telerik:RadButton.Style>
<Style TargetType="{x:Type telerik:RadButton}">
<Style.Triggers>
<DataTrigger Binding="{Binding (Validation.HasError), ElementName=DataProviderComboBox}" Value="true" >
<Setter Property="IsEnabled" Value="false"/>
</DataTrigger>
</Style.Triggers>
</Style>
</telerik:RadButton.Style>
</telerik:RadButton>
</telerik:RadToolBar>
I find this very peculiar, and it might be a previously undiscovered bug? If anyone knows whether this is expected behavior or not, and has a reason for the observed behavior, please feel free to post.
In the meantime, I spent some time developing an acceptable workaround.
In this case I modified the <validationRules:UserAccountValidationRule/> to take a parameter that passes a single boolean IsEnabled property:
<Binding.ValidationRules>
<validationRulesParameterPass:UserAccountValidationRule>
<validationRulesParameterPass:UserAccountValidationRule.AccountValidationParameter>
<validationRulesParameterPass:AccountValidationParameter IsEnabled="{Binding IsButtonEnabled, Source={StaticResource MainWindowViewModel}}" />
</validationRulesParameterPass:UserAccountValidationRule.AccountValidationParameter>
</validationRulesParameterPass:UserAccountValidationRule>
</Binding.ValidationRules>
I also binded the IsButtonEnabled dependency property property to the ToolBar Button item's IsEnabled property.
Then, inside the UserAccountValidationRule, based on whether the value is valid or not, the IsButtonEnabled property will be set to true or false. As this DependencyProperty is connected to the Toolbar Button's, if the validation sets this true, the button's IsEnabled property will also be set to true, and vice versa.
public class UserAccountValidationRule : ValidationRule
{
private AccountValidationParameter _accountValidationParameter;
public UserAccountValidationRule()
{
}
public AccountValidationParameter AccountValidationParameter
{
get { return _accountValidationParameter; }
set { _accountValidationParameter = value; }
}
public override ValidationResult Validate(object value, System.Globalization.CultureInfo cultureInfo)
{
if ( !string.IsNullOrEmpty(value as string) && value as string == "good")
{
AccountValidationParameter.IsEnabled = true;
return new ValidationResult(true, null);
}
else
{
AccountValidationParameter.IsEnabled = false;
return new ValidationResult(false, "bad string.");
}
}
}
Problem solved.

WPF, Validation.ErrorTemplate and Windows Phone

I'm develepoing a Windows Phone application, and I need to validate some user inputs in text boxs. Here the XAML of one of these textbox:
<TextBox
Name="times"
Grid.Row="1"
Height="80"
Text="{Binding UpdateSourceTrigger=Explicit,
Mode=TwoWay,
Path=orari,
ValidatesOnDataErrors=True,
ValidatesOnExceptions=True,
NotifyOnValidationError=true}"
TextChanged="TextBoxChangedHandler"
/>
Using BreakPoints I'm sure the IDataError finds the error, but the TextBox appereance doesn't change. I've read I should use Validate.ErrorTemplate in the XAML, but I don't find this option, pheraps it doesn't exist in Windows Phone? How can I do to change the style of the textbox if the input is not valid?
Thanks
Hard to tell from what you have posted, but here's an example of some of my code that does something very similar perhaps it can help you locate your error.
The style to be used by textboxes I want to be validated, gets a red box when an error has occurred.
<Style x:Key="ValidationTextBox" TargetType="TextBox" BasedOn="{StaticResource {x:Type TextBox}}">
<Setter Property="Validation.ErrorTemplate">
<Setter.Value>
<ControlTemplate>
<Border BorderBrush="Red" BorderThickness="1">
<AdornedElementPlaceholder />
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
<Trigger Property="Validation.HasError" Value="true">
<Setter Property="ToolTip"
Value="{Binding RelativeSource={RelativeSource Self},
Path=(Validation.Errors)[0].ErrorContent}"/>
</Trigger>
</Style.Triggers>
</Style>
The text box itself
<TextBox Style="{StaticResource ValidationTextBox}">
<TextBox.Text>
<Binding Path="Description" UpdateSourceTrigger="PropertyChanged">
<Binding.ValidationRules>
<rules:MandatoryInputRule ValidatesOnTargetUpdated="True" />
<rules:IllegalCharsRule ValidatesOnTargetUpdated="True" />
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
An example validation rule
class IllegalCharsRule : ValidationRule
{
public override ValidationResult Validate(object value, System.Globalization.CultureInfo cultureInfo)
{
if (value != null)
{
string input = value as string;
if (input.Contains(",") || input.Contains("/") || input.Contains(#"\") || input.Contains(".") || input.Contains("\"") || input.Contains("'"))
return new ValidationResult(false, "Validation error. Illegal characters.");
}
return new ValidationResult(true, null);
}
}

WPF error template not showing

I get no binding errors and this code works at another place. I haven't found out yet what I do now differently to the code where it works and it's not that much code.
In UserControl.Resource:
<Style TargetType="TextBox">
<Setter Property="BorderBrush" Value="DarkBlue"/>
<Setter Property="BorderThickness" Value="1"/>
<Setter Property="Margin" Value="0,1,0,1"/>
<Setter Property="Validation.ErrorTemplate">
<Setter.Value>
<ControlTemplate>
<StackPanel Orientation="Horizontal">
<AdornedElementPlaceholder/>
<Grid Margin="2,0,0,0">
<Ellipse Width="20" Height="20" Fill="Red"/>
<TextBlock Foreground="White" Text="X" FontWeight="Bold"
HorizontalAlignment="Center" VerticalAlignment="Center"/>
</Grid>
</StackPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
<Trigger Property="Validation.HasError" Value="True">
<Setter Property="ToolTip"
Value="{Binding RelativeSource={RelativeSource Self},
Path=(Validation.Errors)[0].ErrorContent}"/>
</Trigger>
</Style.Triggers>
</Style>
Below in Xaml too:
<TextBlock Height="23" HorizontalAlignment="Left" Margin="22,90,0,0"
Text="Keywords" VerticalAlignment="Top"/>
<TextBox Height="23" HorizontalAlignment="Left" Margin="22,108,0,0"
VerticalAlignment="Top" Width="244">
<Binding Path="Tags" Mode="TwoWay" UpdateSourceTrigger="PropertyChanged">
<Binding.ValidationRules>
<DataErrorValidationRule ValidatesOnTargetUpdated="False"/>
</Binding.ValidationRules>
</Binding>
</TextBox>
The button SAVE in my ViewModel is only activated when the Model.Tags property is longer 10 chars input from the user. The button activation/disable works fine when I enter 10,11 and then back 8 chars. All the property changes are fired.
Model:
namespace TBM.Model
{
public class Document : EntityBase , IDataErrorInfo
{
public int Id { get; set; }
public string DocumentName { get; set; }
public string Tags { get; set; }
public byte[] DocumentData { get; set; }
public int PeriodId { get; set; }
string IDataErrorInfo.Error { get { return null; } }
string IDataErrorInfo.this[string propertyName]
{
get { return this.GetValidationError(propertyName); }
}
public bool IsValid
{
get
{
foreach (string property in ValidatedProperties)
if (GetValidationError(property) != null)
return false;
return true;
}
}
static readonly string[] ValidatedProperties = { "Tags", };
private string GetValidationError(string propertyName)
{
if (Array.IndexOf(ValidatedProperties, propertyName) < 0)
return null;
string error = null;
switch (propertyName)
{
case "Tags": error = this.IsTagsEmpty(Tags); break;
default:
Debug.Fail("Unexpected property being validated on Document: " + propertyName);
break;
}
return error;
}
private string IsTagsEmpty(string value)
{
if (value != null && value.Trim().Length >= 10)
return null;
else
return "The keywords must have at least 10 chars!";
}
}
}
ViewModel:
public RelayCommand SaveDocumentCommand
{
get { return _saveDocumentCommand ?? (_saveDocumentCommand =
new RelayCommand(() => SaveDocument(),() => CanSaveDocument())); }
}
private bool CanSaveDocument()
{
return _document.IsValid;
}
//...
What does not work is the ErrorTemplate with the red Ellipse is not showing at all?
UPDATE: Exactly the below code works in a TEST project. But in my productive project It does not find the Resource??? Why the heck this?
<TextBlock Height="23" HorizontalAlignment="Left" Margin="22,89,0,0"
Text="Keywords" VerticalAlignment="Top"/>
<TextBox Style="{StaticResource bla}" Height="23" HorizontalAlignment="Left"
Margin="22,109,0,0" VerticalAlignment="Top" Width="244">
<Binding Path="Tags" Mode="TwoWay" UpdateSourceTrigger="PropertyChanged">
<Binding.ValidationRules>
<DataErrorValidationRule ValidatesOnTargetUpdated="False"
ValidationStep="UpdatedValue"/>
</Binding.ValidationRules>
</Binding>
</TextBox>
<UserControl.Resources>
<Style x:Name="bla" TargetType="TextBox">
<Setter Property="BorderBrush" Value="DarkBlue"/>
<Setter Property="BorderThickness" Value="1"/>
<Setter Property="Margin" Value="0,1,0,1"/>
<Setter Property="Validation.ErrorTemplate">
<Setter.Value>
<ControlTemplate>
<StackPanel Orientation="Horizontal">
<AdornedElementPlaceholder/>
<Grid Margin="2,0,0,0">
<Ellipse Width="20" Height="20" Fill="Red"/>
<TextBlock Foreground="White" Text="X" FontWeight="Bold"
HorizontalAlignment="Center" VerticalAlignment="Center"/>
</Grid>
</StackPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
<Trigger Property="Validation.HasError" Value="True">
<Setter Property="ToolTip"
Value="{Binding RelativeSource={RelativeSource Self},
Path=(Validation.Errors)[0].ErrorContent}"/>
</Trigger>
</Style.Triggers>
</Style>
</UserControl.Resources>
I had similar problem. Fight it for hours just to realize that something was wrong with adorner layer.
What I did is put my with controls inside . And that was it. For some reason this decorator layer sometimes is gone. This is certainly true for TabControl (but in my case it was some other reason).
So it should look like this
<AdornerDecorator>
<Grid>
<TextBox .../>
</Grid>
</AdornerDecorator>
Hope this helps!
I have had a similar problem to this, it turned out not to be the template but the validation was not returning what I expected.
The ViewModel have to implement IDataErrorInfo, not the Model. The ViewModel binds to your View as the DataContext, not the Model, so implement the interface in ViewModel and bind to corresponding properties in XAML.

WPF TextBox setting Text using Trigger during validation

I am having a requirement where in I have to revert the values of a TextBox to old value when the user enters a wrong input. I am using MVVM framework so I dont want to write any codebehind. The Text and Tag of TextBox is databound from ViewModel variable. So my Tag field of TextBox will always have old value. I want to use the Tag field value to revert my Text value.
<Style TargetType="{x:Type TextBox}">
<Setter Property="Validation.ErrorTemplate">
<Setter.Value>
<ControlTemplate>
<DockPanel LastChildFill="True">
<TextBlock DockPanel.Dock="Right"
Foreground="Orange"
FontSize="12pt">
</TextBlock>
<Border BorderBrush="Red" BorderThickness="1">
<AdornedElementPlaceholder />
</Border>
</DockPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
<Trigger Property="Validation.HasError" Value="true" >
<Setter Property="ToolTip"
Value="{Binding Path=Tag,RelativeSource={RelativeSource Self}}">
</Setter>
<Setter Property="Text"
Value="{Binding Path=Tag,RelativeSource={RelativeSource Self}}">
</Setter>
</Trigger>
</Style.Triggers>
</Style>
<TextBox Width="68" Tag="{Binding SampleText}" Height="23" HorizontalAlignment="Left" Margin="39,37,0,0" VerticalAlignment="Top" >
<TextBox.Text>
<Binding Path="SampleText" NotifyOnValidationError="True" ValidatesOnDataErrors="True" ValidatesOnExceptions="True">
<Binding.ValidationRules>
<val:SampleTextValidator></val:SampleTextValidator>
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
Now When an error happens, the TextBox is highlighted red.I have written a Trigger to revert the value back to original value (value stored in Tag field). Tt is not working. But Tooltip part is working. I am confused fully. Please help where am I doing wrong!!!. Correct me with a sample code if possible!!!!
My first guess is that when you made your text input invalid (eg. delete all values), you cause the tag to bind to the same value, hence, it will reflect an empty string.
What you need is a separate property for your original value to bind your tag to.
private string _oldValue;
public string OldValue
{
get {...}
set {... NotifyPropertyChanged()...}
}
private string _sampleText;
public string SampleText
{
get { return _sampleText; }
set {
OldValue = _sampleText;
_sampleText = value;
NotifyPropertyChanged(...);
}
}
<TextBox Width="68" Tag="{Binding OldValue}" ... >
Don't forget to implement INotifyPropertyChanged.

Resources