Set custom error message in ValidationRules in WPF - c#

I have set everything perfectly. If I set some string in ErrorMessage then it shows without error.
What I want is, I want to set ErrorMessage dynamically/programmatically. something
MyValidation.ErrorMessage = "some new message";
username.Update() //something
XAML Code
<TextBox Margin="5" Name="userName">
<TextBox.Text>
<Binding RelativeSource="{RelativeSource Self}" Path="Tag" Mode="OneWayToSource" UpdateSourceTrigger="PropertyChanged">
<Binding.ValidationRules>
<local:MyValidation ErrorMessage="Static String" />
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
C# Class Code
public class MyValidation : ValidationRule {
public string ErrorMessage { get; set; }
public override ValidationResult Validate(object value, CultureInfo cultureInfo) {
if (ErrorMessage.Length > 0) {
return new ValidationResult(false, ErrorMessage);
}
return ValidationResult.ValidResult;
}
}

If you give the ValidationRule a name in the XAML markup:
<Binding.ValidationRules>
<local:MyValidation x:Name="val" ErrorMessage="Static String" />
</Binding.ValidationRules>
...you could set its ErrorMessage property directly and then just explicitly update the binding:
val.ErrorMessage = "some new message";
userName.GetBindingExpression(TextBox.TextProperty)?.UpdateSource();

You can implement INotifyDataErrorInfo in your viewmodel. Implement GetErrors(string) so it returns different error messages based on your condition. You can even return multiple messages at once and they will be displayed below each other.
Here's a nice tutorial, but feel free to implement it on your own. Keep in mind that there's not just one correct approach and the interface gives you a lot of freedom.

Related

Bind custom class with Validation in WPF

I noticed that it is possible to bind variables of the type DateTime to a textbox in WPF. If I enter a wrong value it will not validate and show a red border.
How can I implement my own class, that I can bind to a textbox without having to bind to a property of the class? The Textbox should show a string and the class will validate the input.
Is this possible?
My current solution is this:
In the Model:
public string DefaultLanguageValue
{
get
{
return _defaultLanguageValue;
}
set
{
if (value != this._defaultLanguageValue)
{
ValidateLanguage(value);
this._defaultLanguageValue = value;
NotifyPropertyChanged();
}
}
}
private void ValidateLanguage(string value)
{
string rx = "([a-zA-Z]{2}|[iI]-[a-zA-Z]+|[xX]-[a-zA-Z]{1,8})(-[a-zA-Z]{1,8})*";
if (!Regex.IsMatch(value, rx))
{
throw new ArgumentException();
}
}
In the XAML:
<TextBox Text="{Binding TreeViewModel.Model.DefaultLanguageValue, UpdateSourceTrigger=PropertyChanged, ValidatesOnExceptions=True}" BorderThickness="0" MinWidth="100"/>
It would be nice to have a Class that I can just bind like a String, Int or DateTime for examlpe. Any Ideas?
You could bind to the Tag property of the TextBox itself and validate using a ValidationRule:
public class DateValidationRule : ValidationRule
{
public override ValidationResult Validate(object value, CultureInfo cultureInfo)
{
if (!DateTime.TryParse(value as string, out DateTime _))
return new ValidationResult(false, "Invalid date...");
return ValidationResult.ValidResult;
}
}
XAML:
<TextBox>
<TextBox.Text>
<Binding Path="Tag" RelativeSource="{RelativeSource Self}"
UpdateSourceTrigger="PropertyChanged">
<Binding.ValidationRules>
<local:DateValidationRule />
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
This doesn't require you to bind to a view model.
I finally tried the solution suggested by mm8.
The only issue I have now is that if one enters an invalid value into the textbox, it will not update the textbox when I programmatically change the value of the source after clicking a button.
I tried Validation after update, but this allows the user to save invalid values.
<TreeViewItem>
<TreeViewItem.Header>
<StackPanel Orientation="Horizontal">
<CheckBox x:Name="chkDefaultLanguage" IsChecked="{Binding TreeViewModel.TreeModel.DefaultLanguage, UpdateSourceTrigger=PropertyChanged}"/>
<TextBlock Text="DefaultLanguage: " />
<TextBox BorderThickness="0" MinWidth="100">
<TextBox.Text>
<Binding Path="TreeViewModel.TreeModel.DefaultLanguageValue" UpdateSourceTrigger="PropertyChanged">
<Binding.ValidationRules>
<validationrules:LanguageCodeValidationRule/>
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
</StackPanel>
</TreeViewItem.Header>
</TreeViewItem>
class LanguageCodeValidationRule : ValidationRule
{
public override ValidationResult Validate(object value, CultureInfo cultureInfo)
{
string rx = "([a-zA-Z]{2}|[iI]-[a-zA-Z]+|[xX]-[a-zA-Z]{1,8})(-[a-zA-Z]{1,8})*";
if (!Regex.IsMatch(value.ToString(), rx))
{
return new ValidationResult(false, "Invalid Language Codee.");
}
return ValidationResult.ValidResult;
}
}

WPF/MVVM Validation

Here's the XAML code representing a TextBox used as input for the IdCard
<TextBox.Text>
<Binding Mode="TwoWay"
Path="IdCardNumber"
UpdateSourceTrigger="PropertyChanged">
<Binding.ValidationRules>
<v:AlphaNumValidationRule ValidationStep="UpdatedValue" />
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
The validation :
public class AlphaNumValidationRule : ValidationRule
{
public override ValidationResult Validate(object value, CultureInfo cultureInfo)
{
if (string.IsNullOrWhiteSpace((value ?? "").ToString()))
return new ValidationResult(false, Resources.Strings.MessagesResource.RequiredField);
else if (value.ToString().MatchRegex(RegexResource.ALPHANUMERIC))
return new ValidationResult(true, null);
else
return new ValidationResult(false, Resources.Strings.MessagesResource.InvalidFormat);
}
}
The ViewModel
public override bool IsValid
{
get { return !string.IsNullOrWhiteSpace(IdCardNumber); }
}
private string idCardNumber;
public string IdCardNumber
{
get { return idCardNumber; }
set { Set(() => IdCardNumber, ref idCardNumber, value);
RaisePropertyChanged("IsValid");
}
}
What I want to have is to update IsValid everytime the IdCard input is updated , I tried different ValidationStep but none do as I wish.
At first when loading the input for the first time IsValid is false , when typing a correct value it becomes true after deleting input and adding wrong non-supported values IsValid stays the same since it keeps the last correct value.
Any way to solve this ?
There is an attached event Validation.Error that is fired when binding error occurs.
So basically you could attach to this event and set value of Validation.HasErrors property to your viewmodel's IsValid property.
I see a conflict however. You defined your validation logic in the View, but you want to access it in your ViewModel, that's why you are having troubles.
I recommend you to move entire validation logic to your viewmodel by implementing INotifyDataErrorInfo. then you will have all validation rules and validation errors at your disposal in viewmodel.
You can try to change the UpdateSourceTrigger property with LostFocus:
<Binding Mode="TwoWay"
Path="IdCardNumber"
UpdateSourceTrigger="LostFocus">
<Binding.ValidationRules>
<v:AlphaNumValidationRule ValidationStep="UpdatedValue" />
</Binding.ValidationRules>
</Binding>
Edit :
To bind the validation result you can use HasError property :
<TextBox Name="TextBox">
<TextBox.Text>
<Binding Mode="TwoWay"
Path="Text"
UpdateSourceTrigger="PropertyChanged">
<Binding.ValidationRules>
<local:AlphaNumValidationRule/>
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
<TextBlock Text="{Binding (Validation.HasError), ElementName=TextBox}"/>

Validation using ValidationRules - Ok button should trigger validation and not continue execution

I have a simple example application, 2 text boxes a validation rule and a button.
I want my button to trigger validation and if it's not valid, not to continue with its execution.
<TextBox>
<TextBox.Text>
<Binding Path="FirstName">
<Binding.ValidationRules>
<local:MyValidationRule ErrorMessage="Enter first name" />
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
<TextBox>
<TextBox.Text>
<Binding Path="LastName">
<Binding.ValidationRules>
<local:MyValidationRule ErrorMessage="Enter last name" />
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
<Button Command="{Binding OkCommand}" Content="Ok" />
The validation rule:
public class MyValidationRule : ValidationRule
{
public string ErrorMessage { get; set; }
public override ValidationResult Validate(object value, CultureInfo cultureInfo)
{
string valueToCheck = value as string;
if (string.IsNullOrWhiteSpace(valueToCheck))
{
return new ValidationResult(false, ErrorMessage);
}
else
{
return new ValidationResult(true, null);
}
}
}
My OkCommand is empty method:
OkCommand= new RelayCommand(OkRequested);
private void OkRequested()
{
// Do stuff
}
However, no matter if the text boxes are empty OkCommand gets executed properly. What am I doing wrong here? I want text boxes to be styled if they're not valid (I've excluded styles from example), but that does not happen.
You should add into OkCommand something like this. And same for other textBoxes.
if ( Validation.GetHasError( textBox1 ) )
{
MessageBox.Show( Validation.GetErrors( textBox1 )[0].ErrorContent.ToString() );
return;
}

ComboBox Validation without Binding

This question is based on this solution, but I somehow cannot get this to work using a ComboBox. I would like to validate that upon clicking a submit button, the combobox has a selected item and is not null. Please note that I am not binding to anything on purpose, and not because I don't know how to. But if the answer is that there is no way I can use the validation rules without binding (to ComboBoxes specifically, I've done it to textboxes via the linked solution), please let me know.
Here is what I have so far:
XAML:
<ComboBox DataContext="{StaticResource ChargeAssigneeViewSource}" Name="ChargeAssigneeBox" ItemsSource="{Binding}" Width="85">
<ComboBox.SelectedItem>
<Binding RelativeSource="{RelativeSource Self}" Path="GetType" Mode="TwoWay">
<Binding.ValidationRules>
<my:ComboBoxValidationRule ErrorMessage="Please select an Assignee" />
</Binding.ValidationRules>
</Binding>
</ComboBox.SelectedItem>
</ComboBox>
Validation Rule:
class ComboBoxValidationRule : ValidationRule
{
private string errorMessage;
public string ErrorMessage
{
get { return errorMessage; }
set { errorMessage = value; }
}
public override ValidationResult Validate(object value, CultureInfo cultureInfo)
{
if (value == null)
return new ValidationResult(false, ErrorMessage);
return new ValidationResult(true, null);
}
}
Button Click:
private void AddAnHours()
{
Employee currEmployee;
if (!ValidateElement.HasError(ChargeAssigneeBox))
{
if (!ValidateElement.HasError(analystTimeTxtBox))
{
currEmployee = ChargeAssigneeBox.SelectedItem as Employee;
item.AddTime(currEmployee, DateTime.Now, double.Parse(analystTimeTxtBox.Text));
analystTimeTxtBox.Clear();
ChargeAssigneeBox.SelectedItem = null;
}
}
UpdateTotals();
}
The error that I get is in this line:
currEmployee = ChargeAssigneeBox.SelectedItem as Employee;
but my ItemsSource is binding properly so even though I have selected an item, the selecteditem is not converting it to an employee object. I suspect it has something to do with what I am binding it to:
<Binding RelativeSoruce="{RelativeSource Self}" Path="GetType"....>
Help would be greatly appreciated, thanks.
You can't bind a property to the selectedItem on the combobox and then check if thats property is null?
Public string SelectedComboboxItem { get; set; }
<ComboBox DataContext="{StaticResource ChargeAssigneeViewSource}" SelectedItem="{Binding SelectedComboboxItem}" Name="ChargeAssigneeBox" ItemsSource="{Binding}" Width="85">

How can I handle a Validation.Error in my ViewModel instead of my View's code behind?

I'm trying to get WPF validation to work within the MVVM pattern.
In my View, I can validate a TextBox like this which gets handled by the code-behind method "HandleError", which works fine:
<TextBox Width="200"
Validation.Error="HandleError">
<TextBox.Text>
<Binding Path="FirstName"
NotifyOnValidationError="True"
Mode="TwoWay">
<Binding.ValidationRules>
<validators:DataTypeLineIsValid/>
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
However, I would like to handle the validation in my ViewModel via a DelegateCommand but when I try it with the following code, I get the explicit error "'{Binding HandleErrorCommand}' is not a valid event handler method name. Only instance methods on the generated or code-behind class are valid."
Are there any workaround for this so that we can handle validations within a MVVM pattern?
View:
<TextBox Width="200"
Validation.Error="{Binding HandleErrorCommand}">
<TextBox.Text>
<Binding Path="FirstName"
NotifyOnValidationError="True"
Mode="TwoWay">
<Binding.ValidationRules>
<validators:DataTypeLineIsValid/>
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
ViewModel:
#region DelegateCommand: HandleError
private DelegateCommand handleErrorCommand;
public ICommand HandleErrorCommand
{
get
{
if (handleErrorCommand == null)
{
handleErrorCommand = new DelegateCommand(HandleError, CanHandleError);
}
return handleErrorCommand;
}
}
private void HandleError()
{
MessageBox.Show("in view model");
}
private bool CanHandleError()
{
return true;
}
#endregion
I don't know if this will help you, but I'll offer it all the same.
Also, I'm using Silverlight, not WPF.
I don't specify any validation in my Views, neither in the code behind nor the xaml. My View has only data bindings to properties on the ViewModel.
All my error checking/validation is handled by the ViewModel. When I encounter an error, I set a ErrorMessage property, which is bound to the view as well. The ErrorMessage textblock (in the view) has a value converter which hides it if the error is null or empty.
Doing things this way makes it easy to unit test input validation.
Here's a way to do this using Expression Blend 3 behaviors. I wrote a ValidationErrorEventTrigger because the built-in EventTrigger doesn't work with attached events.
View:
<TextBox>
<i:Interaction.Triggers>
<MVVMBehaviors:ValidationErrorEventTrigger>
<MVVMBehaviors:ExecuteCommandAction TargetCommand="HandleErrorCommand" />
</MVVMBehaviors:ValidationErrorEventTrigger>
</i:Interaction.Triggers>
<TextBox.Text>
<Binding Path="FirstName"
Mode="TwoWay"
NotifyOnValidationError="True">
<Binding.ValidationRules>
<ExceptionValidationRule />
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
ViewModel: (could be unchanged, but here's a look at how I dug into the validation arguments to find the error message when using the exception validation rule)
public ICommand HandleErrorCommand
{
get
{
if (_handleErrorCommand == null)
_handleErrorCommand = new RelayCommand<object>(param => OnDisplayError(param));
return _handleErrorCommand;
}
}
private void OnDisplayError(object param)
{
string message = "Error!";
var errorArgs = param as ValidationErrorEventArgs;
if (errorArgs != null)
{
var exception = errorArgs.Error.Exception;
while (exception != null)
{
message = exception.Message;
exception = exception.InnerException;
}
}
Status = message;
}
ValidationErrorEventTrigger:
public class ValidationErrorEventTrigger : EventTriggerBase<DependencyObject>
{
protected override void OnAttached()
{
Behavior behavior = base.AssociatedObject as Behavior;
FrameworkElement associatedElement = base.AssociatedObject as FrameworkElement;
if (behavior != null)
{
associatedElement = ((IAttachedObject)behavior).AssociatedObject as FrameworkElement;
}
if (associatedElement == null)
{
throw new ArgumentException("Validation Error Event trigger can only be associated to framework elements");
}
associatedElement.AddHandler(Validation.ErrorEvent, new RoutedEventHandler(this.OnValidationError));
}
void OnValidationError(object sender, RoutedEventArgs args)
{
base.OnEvent(args);
}
protected override string GetEventName()
{
return Validation.ErrorEvent.Name;
}
}

Categories

Resources