Can we set property of source object, on validation? - wpf

I have a wpf-mvvm application.
In the below code, "PartBPremiumBuydown" is an instance of a class. which has two properties => 1. Value. and 2. HasValidationError.
Property "Value" is used for binding to textbox. If there is any validation error...Can I set HasValidationError=true ?
<TextBox ToolTip="{Binding RelativeSource={RelativeSource Self},
Path=(Validation.Errors).CurrentItem.ErrorContent}">
<TextBox.Text>
<Binding Path="PartBPremiumBuydown.Value"
ValidatesOnDataErrors="True"
UpdateSourceTrigger="PropertyChanged"
Converter="{x:Static localns:Converters.DecimalToCurrency}">
<Binding.ValidationRules>
<localns:CurrencyRule />
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>

You should have PartBPremiumBuydown implement the IDataErrorInfo interface, similar to the below code:
public string Error { get; private set; }
public string this[string propertyName]
{
get
{
string mError = string.Empty;
if (propertyName == "Value"
&& !<insert your rule>)
{
mError = "Validation error text."
}
Error = mError;
return (string.IsNullOrWhiteSpace(mError))// if NOTHING
? null // then return null
: mError; // else return error
}
}
Now, when you bind your TextBox to Value, if the user enters in text which breaks your rule, the validation error will show on the TextBox.

Related

Material Design WPF TextBox Validation error message change position when scrolling page

I'm new in Material Design. Could someone, please, explain me how can I fix a bug with validation message in textbox
Validation error shows uncorrectly - changes position when I'm scrolling view.
Here is code from .xaml
<TextBox Grid.Row="11"
Grid.Column="1"
Width="20"
VerticalAlignment="Center"
materialDesign:ValidationAssist.UsePopup="True"
materialDesign:ValidationAssist.OnlyShowOnFocus="True"
HorizontalAlignment="Left"
ToolTip="Use a popup which can escape the bounds of the control where space is limited">
<TextBox.Text>
<Binding Path="Name"
UpdateSourceTrigger="PropertyChanged">
<Binding.ValidationRules>
<helpers:NotEmptyValidationRule ValidatesOnTargetUpdated="True" />
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
This Validation TextBox uses data binding behind the scenes to work. The code is correct but you might be missing the data binding on the Name.
<Binding Path="Name" UpdateSourceTrigger="PropertyChanged">
For this, to work you need to data bind the path to the Name property.
This can be done in two steps.
Create/reuse a property Name in the desired class.
Assign the data context of the XAML page to the desired class.
Sample (desired class is shown as MainWindow here)
Step 1, file: MainWindow.xaml.cs
private string _name;
public string Name
{
get { return _name; }
set { _name = value; }
}
Step 2, file: MainWindow.xaml.cs
public MainWindow()
{
InitializeComponent();
this.DataContext = this;
...
}
And then the problem should be solved.

WPF MVVMLight Setter not fired on binding TextBox when validation rule is invalid

I have a TextBox with binding on Mo property :
<TextBox>
<TextBox.Style>
<Style TargetType="TextBox">
<Style.Setters>
<Setter Property="Text">
<Setter.Value>
<Binding Path="Mo" UpdateSourceTrigger="PropertyChanged">
<Binding.ValidationRules>
<validator:FloatPositiveValidationRule ValidatesOnTargetUpdated="True" />
</Binding.ValidationRules>
</Binding>
</Setter.Value>
</Setter>
</Style.Setters>
</Style>
</TextBox.Style>
</TextBox>
This one contains a validation rule which validate the control only if the value is not empty and greater than 0 :
public class FloatPositiveValidationRule : ValidationRule
{
public override ValidationResult Validate(object value, CultureInfo cultureInfo)
{
string str = (string)value;
if (str.Length > 0)
{
double mo = Double.Parse(str.Replace(".", ","));
if (mo > 0)
return new ValidationResult(true, null);
else
return new ValidationResult(false, "Must be greater than 0");
}
else
return new ValidationResult(false, "Empty");
}
}
In my view model, when the validation is false, the setter is not fired :
private double? _mo;
public string Mo
{
get { return _mo.ToString(); }
set
{
if (value != "")
mo = double.Parse(value.Replace(".", ","));
Set(ref _mo, mo);
}
}
Is it possible to enter in the setter even if the validation is invalid ?
You could try to set ValidationStep property to UpdatedValue:
<validator:FloatPositiveValidationRule ValidatesOnTargetUpdated="True" ValidationStep="UpdatedValue" />
This should make the validation rule run after the source property has been set.
But what you really should do is to remove the ValidationRule and implement the validation logic in your view model, for example by implementing the INotifyDataErrorInfo interface.

Localize ExceptionValidationRule

Binding to double may produce following validation error
Value ... could not be converted.
When using ExceptionValidationRule errors are more talkative:
Input string was not in a correct format.
Value was either too big or too small for a ...
Perhaps there are more. Add to them those what I can throw myself in bound property setter.
Now, I want to localize those messages (preferably second version). Not a big surprise, but sources doesn't reveal anything useful (am I looking at wrong place?).
I can make my own validation rules, but perhaps there is an easier way?
My question: can I localize ExceptionValidationRule?
Below is mcve:
xaml:
<TextBox>
<TextBox.Text>
<Binding Path="TestDouble" UpdateSourceTrigger="PropertyChanged">
<Binding.ValidationRules>
<!--<local:MyValidationRule />-->
<!--<ExceptionValidationRule />-->
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
<Validation.ErrorTemplate>
<ControlTemplate>
<TextBlock Margin="0,20,0,0" Foreground="Red" Text="{Binding ErrorContent}" />
</ControlTemplate>
</Validation.ErrorTemplate>
</TextBox>
cs:
public partial class MainWindow : Window
{
public double TestDouble { get; set; }
public MainWindow()
{
InitializeComponent();
DataContext = this;
}
}
public class MyValidationRule : ValidationRule
{
public override ValidationResult Validate(object value, CultureInfo cultureInfo) => ValidationResult.ValidResult; // not used
public override ValidationResult Validate(object value, CultureInfo cultureInfo, BindingExpressionBase owner)
{
var bindingExpression = owner as BindingExpression;
if (bindingExpression != null)
{
var type = bindingExpression.ResolvedSource.GetType().GetProperty(bindingExpression.ResolvedSourcePropertyName).PropertyType;
if (type == typeof(double))
{
double result;
if (!double.TryParse((string)value, out result))
return new ValidationResult(false, "The value format is not recognized"); // custom message
}
... // and so on, for all types ?????
}
return base.Validate(value, cultureInfo, owner);
}
}
To localize such error messages, which appears during updating source of binding, its enough to setup UpdateSourceExceptionFilter callback handler of binding, no custom validation rule is needed (ExceptionValidationRule is required).
xaml:
<Binding UpdateSourceExceptionFilter="ExeptionFilter" ...>
cs:
object ExeptionFilter(object bindingExpression, Exception exception)
{
return "Localized error message";
}
Obviously it can be switch/case (in C# 7, earlier - if/else if) to provide exception-specific messages (in my question those are FormatException and OverflowException correspondingly).

Cancel TextBox input on validation errors

The standard behavior in TextBoxes when a input string is not valid is to show a red square (for example user introduces a letter in numeric TextBox). This happens when TextBox loses focus.
I want implement this behavior:
The textBox loses focus.
TextBox do internal validation (date, numeric, etc).
If input user string is not valid, old value is restored and TextBox don't show any error.
you have an exemple of validation in textbox here in this link: http://www.codeproject.com/Tips/690130/Simple-Validation-in-WPF
<ControlTemplate x:Key="validationErrorTemplate">
<DockPanel>
<TextBlock Foreground="Red"
DockPanel.Dock="Top">!</TextBlock>
<AdornedElementPlaceholder
x:Name="ErrorAdorner"
></AdornedElementPlaceholder>
</DockPanel>
</ControlTemplate>
public class NameValidator : ValidationRule
{
public override ValidationResult Validate
(object value, System.Globalization.CultureInfo cultureInfo)
{
if (value == null)
return new ValidationResult(false, "value cannot be empty.");
else
{
if (value.ToString().Length > 3)
return new ValidationResult
(false, "Name cannot be more than 3 characters long.");
}
return ValidationResult.ValidResult;
}
}
<TextBox Height="23" HorizontalAlignment="Left"
Grid.Column="1" Grid.Row="0" Name="textBox1"
VerticalAlignment="Top" Width="120"
Validation.ErrorTemplate="{StaticResource validationErrorTemplate}"
>
<TextBox.Text>
<Binding Path="Name" Mode="TwoWay"
UpdateSourceTrigger="LostFocus">
<Binding.ValidationRules>
<local:NameValidator></local:NameValidator>
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
Reverting to the old value is quite easy if you use MVVM. In your ViewModel's prop setter you can simply not set the new value to the model if it's invalid and invoke PropertyChanged instead. This will tell the bound view element to call your property getter, which will return the old value and thus revert the view element's content to the old value.
Example (validating that user input is an int value):
public string Number
{
get { return _model.Number.ToString(); }
set
{
if (_model.Number.ToString() != value)
{
int number;
if (int.TryParse(value, out number))
{
_model.Number = number;
}
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Number));
}
}
}

WPF: Passing values to Validation Rules from bound data

Quick question.
I have a validator configured in WPF that checks to ensure that a value is within a specific range. This works great. See code below:
<TextBox Name="valueTxt" Style="{StaticResource SquareBox}" GotKeyboardFocus="amountTxt_GotKeyboardFocus" GotMouseCapture="amountTxt_GotMouseCapture" LostFocus="boxLostFocus" Height="25" Width="50">
<TextBox.Text>
<Binding Path="UnitCost" NotifyOnValidationError="True">
<Binding.ValidationRules>
<local:ValidDecimal MaxAmount="1000"></local:ValidDecimal>
</Binding.ValidationRules>
<Binding.Converter>
<local:CurrencyConverter addPound="False" />
</Binding.Converter>
</Binding>
</TextBox.Text>
</TextBox>
However, I would like to pass the validator another piece of data from the data that is being bound. I assumed that I could just add this to the delcaration of the validator like so:
<local:ValidDecimal MaxAmount="1000" SKU="{Binding Path=tblProducts.ProductSKU}"></local:ValidDecimal>
However, is appears that I can't access the SKU value in this way.
Any suggestions?
Thanks,
EDIT
May well be worth pointing out that SKU is simply a string declared in my validator, like so:
public class ValidDecimal : ValidationRule
{
public int MaxAmount { get; set; }
public string SKU { get; set; }
public override ValidationResult Validate(object value, System.Globalization.CultureInfo cultureInfo)
{
//validate the value as a decimal in to two decimal places
string cost = (string)value;
try
{
decimal decCost = decimal.Parse(cost);
if (SKU != "85555")
{
if (decCost <= 0 || decCost > MaxAmount)
{
return new ValidationResult(false, "Amount is not within valid range");
}
}
return new ValidationResult(true, null);
}
catch (Exception ex)
{
return new ValidationResult(false, "Not a valid decimal value");
}
}
}

Resources