WPF: Passing values to Validation Rules from bound data - wpf

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

Related

Add a warning icon to the TextBox when validation

I want to validate a textbox if something is wrong. The idea is if something is wrong than the next TextBox should have a warning image.
<TextBox Text="{Binding Age, UpdateSourceTrigger=PropertyChanged}">
<Validation.ErrorTemplate>
<ControlTemplate>
<StackPanel>
<!-- Placeholder for the TextBox itself -->
<AdornedElementPlaceholder x:Name="textBox"/>
<image source="some-Image.png" width="20" Height="20" />
</StackPanel>
</ControlTemplate>
</Validation.ErrorTemplate>
</TextBox>
But the thing is the image is not showing, it only shows the border of the icon.
Am I using AdornedElementPlaceholder correctly?
Tested solution that works and displays the image when error occurs:
<TextBox BorderThickness="0.8">
<Validation.ErrorTemplate>
<ControlTemplate>
<StackPanel>
<AdornedElementPlaceholder/>
<Image Source="Image.jpg" Width="20" Height="20"/>
</StackPanel>
</ControlTemplate>
</Validation.ErrorTemplate>
<TextBox.Text>
<Binding Path="ValidationTest" UpdateSourceTrigger="PropertyChanged" Mode="TwoWay" ValidatesOnDataErrors="True">
<Binding.ValidationRules>
<validation:IntegerValidationRule ValidationStep="CommittedValue" Min="1" Max="99999999"/>
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
And here is the validation rule that I have used for this example:
public class IntegerValidationRule : ValidationRule
{
private int _min = int.MinValue;
private int _max = int.MaxValue;
private string _fieldName = "Field";
private string _customMessage = String.Empty;
public int Min
{
get { return _min; }
set { _min = value; }
}
public int Max
{
get { return _max; }
set { _max = value; }
}
public string FieldName
{
get { return _fieldName; }
set { _fieldName = value; }
}
public string CustomMessage
{
get { return _customMessage; }
set { _customMessage = value; }
}
public override ValidationResult Validate(object value, System.Globalization.CultureInfo cultureInfo)
{
int num = 0;
var val = (value as BindingExpression).DataItem;
if (!int.TryParse(value.ToString(), out num))
return new ValidationResult(false, $"{FieldName} must contain an integer value.");
if (num < Min || num > Max)
{
if (!String.IsNullOrEmpty(CustomMessage))
return new ValidationResult(false, CustomMessage);
return new ValidationResult(false, $"{FieldName} must be between {Min} and {Max}.");
}
return new ValidationResult(true, null);
}
}
Which is just modified example from MSDN Docs
Some notes:
You didn't provide the validation rule so I am assuming it works as expected and produces valid validation result.
The Binding of your TextBox.Text property doesn't include the Validation rule.

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).

How To Validation This Class?(WPF)

How To Validation This Class?(WPF)
I can not understand is the Property Value for each.
For this method : public override ValidationResult Validate(object value.
name maximum char must be 10;
age maximum value must be 150;
public class Person : ValidationRule
{
string _Name;
public string Name
{
get
{
return _Name;
}
set
{
_Name = value;
}
}
int _age = 20;
public int Age
{
get { return _age; }
set { _age = value; }
}
string _Phone = "000-0000";
public string Phone
{
get { return _Phone; }
set { _Phone = value; }
}
public override ValidationResult Validate(object value, System.Globalization.CultureInfo cultureInfo)
{
**//is value Which Property?**
//I can not understand is the Property Value for each
return new ValidationResult(true, null);
}
}
You can't make your class derive from ValidationRule: it's a person, not a rule.
First, I don't recommend that WPF developers use validation rules at all. Use MVVM, and have your view model implement IDataErrorInfo as described (for instance) here.
If you want to create a single ValidationRule class to validate your Person class, you can, but you'll need to create a PropertyName property on the class and set it in your XAML, e.g.:
<TextBox>
<TextBox.Text>
<Binding Path="Age"
Mode="TwoWay">
<Binding.ValidationRules>
<local:PersonValidationRule PropertyName="Age"/>
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
Then the Validate method in this class can look at the PropertyName and branch accordingly. Of course, now you've implemented a new point of failure - what happens if you put down the wrong property name in your XAML? If you use data-error validation, that can't happen.

Can we set property of source object, on validation?

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.

Attached or dependecy Property for ValidationRule WPF

I want to bind the attached property or dependency property in xaml for the ValidationRule in xaml and then based on the value of the attached property or dependency property I want to make sum decision in the Validation rule. I can't find any solution
how can I Pass bindable value to the Validation Rule.
I supply you a sample code to help you. I have defined a ValidationRule to validate a texbox user input. The type of validation is performed according value of one enum parameter. Type of validation available are: user input cannot be empty, user input must be numeric, user input must be an IP address. A second parameter allows to specificy warning message displayed. As you know a variable for binding purposes should be a DependendyProperty, so here you find class with paramaters declaration.
public class ValidationParams : DependencyObject
{
// Dependency Properties
public static readonly DependencyProperty MessageProperty = DependencyProperty.Register("Message",
typeof(string),
typeof(ValidationParams),
new FrameworkPropertyMetadata(string.Empty));
public static readonly DependencyProperty ValidationTypeProperty = DependencyProperty.Register("ValidationType",
typeof(FieldValidationRule.EnmValidationType),
typeof(ValidationParams),
new FrameworkPropertyMetadata(FieldValidationRule.EnmValidationType.FieldNotEmpty));
// Properties
[Category("Message")]
public string Message
{
get { return (string)GetValue(MessageProperty); }
set { SetValue(MessageProperty, value); }
}
[Category("ValidationType")]
public FieldValidationRule.EnmValidationType ValidationType
{
get { return (FieldValidationRule.EnmValidationType)GetValue(ValidationTypeProperty); }
set { SetValue(ValidationTypeProperty, value); }
}
Then here is the validationrule class:
public class FieldValidationRule : ValidationRule
{
public enum EnmValidationType
{
FieldNotEmpty,
FieldNumeric,
FieldIPAddress
}
// Local variables and objects
private ValidationParams mParams = new ValidationParams();
public ValidationParams Params
{
get { return mParams; }
set { mParams = value; }
}
// Override
public override ValidationResult Validate(object value, System.Globalization.CultureInfo cultureInfo)
{
ValidationResult objResult = null;
string sValue = value as string;
objResult = new ValidationResult(true, null);
switch (Params.ValidationType)
{
case EnmValidationType.FieldNotEmpty:
if(string.IsNullOrEmpty(sValue) == true)
objResult = new ValidationResult(false, Params.Message);
break;
case EnmValidationType.FieldNumeric:
int iValue = 0;
if(int.TryParse(sValue, out iValue) == false)
objResult = new ValidationResult(false, Params.Message);
break;
case EnmValidationType.FieldIPAddress:
IPAddress objValue = IPMatrix.CreateHostAddr();
if(IPAddress.TryParse(sValue, out objValue) == false)
objResult = new ValidationResult(false, Params.Message);
break;
}
return objResult;
}
}
And finally here is the XAML code:
<TextBox Style="{DynamicResource FieldValue}" Grid.Column="1" IsReadOnly="False">
<TextBox.Text>
<Binding Source="{StaticResource XmlItemChannel}" XPath="#Name" Mode="TwoWay" UpdateSourceTrigger="LostFocus">
<Binding.ValidationRules>
<data:FieldValidationRule>
<data:FieldValidationRule.Params>
<data:ValidationParams Message="{DynamicResource ERR002}" ValidationType="FieldNotEmpty" />
</data:FieldValidationRule.Params>
</data:FieldValidationRule>
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
You can see that parameter Message is binded to a resource, but you can classically bind it too.

Resources