How to implement binding validation for a textbox?
<TextBox Name="textBox1" Width="50" FontSize="15"
Validation.ErrorTemplate="{StaticResource validationTemplate}"
Style="{StaticResource textBoxInError}"
Grid.Row="1" Grid.Column="1" Margin="2">
<TextBox.Text>
<Binding Path="Age" Source="{StaticResource ods}"
UpdateSourceTrigger="PropertyChanged" >
<Binding.ValidationRules>
// *** What should I write here? ***
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
What should I write in the validation Rule?
The following example shows the implementation of AgeRangeRule, which inherits from ValidationRule and overrides the Validate method. The Int32.Parse() method is called on the value to make sure that it does not contain any invalid characters. The Validate method returns a ValidationResult that indicates if the value is valid based on whether an exception is caught during the parsing and whether the age value is outside of the lower and upper bounds.
public class AgeRangeRule : ValidationRule
{
private int _min;
private int _max;
public AgeRangeRule()
{
}
public int Min
{
get { return _min; }
set { _min = value; }
}
public int Max
{
get { return _max; }
set { _max = value; }
}
public override ValidationResult Validate(object value, CultureInfo cultureInfo)
{
int age = 0;
try
{
if (((string)value).Length > 0)
age = Int32.Parse((String)value);
}
catch (Exception e)
{
return new ValidationResult(false, "Illegal characters or " + e.Message);
}
if ((age < Min) || (age > Max))
{
return new ValidationResult(false,
"Please enter an age in the range: " + Min + " - " + Max + ".");
}
else
{
return new ValidationResult(true, null);
}
}
}
To expand on Cornel's answer, here is the related XAML that you would use with his example code:
<TextBox Name="textBox1" Width="50" FontSize="15"
Validation.ErrorTemplate="{StaticResource validationTemplate}"
Style="{StaticResource textBoxInError}"
Grid.Row="1" Grid.Column="1" Margin="2"> <TextBox.Text>
<Binding Path="Age" Source="{StaticResource ods}"
UpdateSourceTrigger="PropertyChanged" >
<Binding.ValidationRules>
<c:AgeRangeRule Min="21" Max="130"/>
</Binding.ValidationRules>
</Binding></TextBox.Text></TextBox>
So in summary, you create your custom Validation class, reference it in your XAML code, and then implement it with the necessary properties initialized.
MSDN example
Related
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.
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));
}
}
}
so I have written the following DP and ValidationRule:
public class ComparisonValue : DependencyObject
{
public Object ComparisonObject
{
get { return (Object)GetValue(ComparisonObjectProp); }
set {
SetValue(ComparisonObjectProp, value);
}
}
public static readonly DependencyProperty ComparisonObjectProp =
DependencyProperty.Register("ComparisonObject", typeof(object), typeof(ComparisonValue), new UIPropertyMetadata(null));
}
public class ObjectComparisonValidator : ValidationRule
{
private ComparisonValue _ObjectToCompare;
public ComparisonValue ObjectToCompare
{
get
{
return _ObjectToCompare;
}
set
{
_ObjectToCompare = value;
}
}
public override ValidationResult Validate(object value, CultureInfo cultureInfo)
{
if (value != null)
{
if (!value.Equals(ObjectToCompare.ComparisonObject))
{
return new ValidationResult(false, "Values are not equal");
}
else
{
return new ValidationResult(true, null);
}
}
else
{
if (value != ObjectToCompare.ComparisonObject)
{
return new ValidationResult(false, "Values are not equal");
}
else
{
return new ValidationResult(true, null);
}
}
}
}
Then in my XAML I have the following markup:
<UserControl.Resources>
<l:EnumToStringConverter x:Key="CustomEnumConverter"/>
<l:BooleanToBrushConverter x:Key="BooleanToBrushConverter"/>
<l:ObjectComparisonValidator x:Key="ObjectComparisonValidator"/>
<l:ComparisonValue x:Key="ComparisonValue"/>
</UserControl.Resources>
....
<TextBox Grid.Row="2" Grid.Column="1" Grid.ColumnSpan="2" Height="25" Text="{Binding Path=NetworkKey.Value, UpdateSourceTrigger=PropertyChanged}">
<TextBox.Background>
<Binding Path="NetworkKey.Changed" Converter="{StaticResource BooleanToBrushConverter}">
<Binding.ConverterParameter>
<x:Array Type="Brush">
<SolidColorBrush Color="Yellow"/>
<SolidColorBrush Color="White"/>
</x:Array>
</Binding.ConverterParameter>
</Binding>
</TextBox.Background>
</TextBox>
....
<TextBox Grid.Row="3" Grid.Column="1" Grid.ColumnSpan="2" Height="25">
<TextBox.Text>
<Binding Path="DuplicateNetworkKey.Value" UpdateSourceTrigger="PropertyChanged">
<Binding.ValidationRules>
<l:ObjectComparisonValidator>
<l:ObjectComparisonValidator.ObjectToCompare>
<l:ComparisonValue ComparisonObject="{Binding Path=NetworkKey.Value}"/>
</l:ObjectComparisonValidator.ObjectToCompare>
</l:ObjectComparisonValidator>
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
<TextBox.Background>
<Binding Path="DuplicateNetworkKey.Changed" Converter="{StaticResource BooleanToBrushConverter}">
<Binding.ConverterParameter>
<x:Array Type="Brush">
<SolidColorBrush Color="Yellow"/>
<SolidColorBrush Color="White"/>
</x:Array>
</Binding.ConverterParameter>
</Binding>
</TextBox.Background>
</TextBox>
Now the problem I am having is that the Validate method fo the Validation rule gets invoked, but when the binding for NetworkKey gets triggered, the Setter in the ComparisonValue for the object never gets invoked, so any time the validation rule runs, the ComparisonObject property of ObjectComparisonValidator.ObjectToCompare is null, and thus validation fails. Whats wrong with the binding I have for ComparisonObject?
just for a bit of clarification, the type of NetworkKey and DuplicateKey (props in the VM) are INPC classes. Heres the code for them as well:
public class ValueField<T> : AChangeReportingViewModel, INotifyPropertyChanged
{
private T _OriginalVal;
public T OriginalVal
{
get
{
return _OriginalVal;
}
set
{
_OriginalVal = value;
Value = value;
Changed = false;
PropertyChanged(this, new PropertyChangedEventArgs("OriginalVal"));
}
}
private T _Value;
public T Value
{
get
{
return _Value;
}
set
{
_Value = value;
if (_Value == null)
{
if (_OriginalVal != null) Changed = true;
}
else
{
Changed = !_Value.Equals(_OriginalVal);
}
PropertyChanged(this, new PropertyChangedEventArgs("Value"));
}
}
private Boolean _Changed;
public Boolean Changed
{
get
{
return _Changed;
}
set
{
if (_Changed != value)
{
if (value) ChangeMade();
else ChangeReversed();
}
_Changed = value;
PropertyChanged(this, new PropertyChangedEventArgs("Changed"));
}
}
public event PropertyChangedEventHandler PropertyChanged = delegate { };
}
<Binding Path="DuplicateNetworkKey.Value" UpdateSourceTrigger="PropertyChanged">
<Binding.ValidationRules>
<l:ObjectComparisonValidator>
<l:ObjectComparisonValidator.ObjectToCompare>
<l:ComparisonValue ComparisonObject="{Binding Path=NetworkKey.Value}"/>
</l:ObjectComparisonValidator.ObjectToCompare>
</l:ObjectComparisonValidator>
</Binding.ValidationRules>
</Binding>
The inner binding object is not part of the visual tree, and so does not inherit the data context of the parent. To bind outside of the visual tree, use an x:Reference binding:
<l:ComparisonValue ComparisonObject="{Binding Source={x:Reference Root}
Path=DataContext.NetworkKey.Value}"/>
It's similar to an ElementName binding, but you can't do those outside of the visual tree. Note that "Root" in this example is the name of the root UI element.
DataGrid ItemsSource="{Binding Legs}" Grid.Row="1"
DataContext="{Binding}"
Tag="{Binding}"
CanUserAddRows="False"
CanUserDeleteRows="False"
HorizontalAlignment="Stretch"
IsSynchronizedWithCurrentItem="True"
x:Name="statusGrid"
AutoGenerateColumns="False" HorizontalScrollBarVisibility="Hidden"
RowStyle="{StaticResource GridRowStyle}"
>
And then
<DataGrid.Columns>
<DataGridTemplateColumn Width="Auto" Header="Status">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Image Height="16" MaxHeight="14" Width="14" MaxWidth="14" HorizontalAlignment="Center">
<Image.Source>
<MultiBinding>
<Binding Path="SwapswireBuyerStatusText"/>
<Binding Path="SwapswireSellerStatusText"/>
<MultiBinding.Converter>
<Control:SwapswireStatusImagePathConvertor AllGoodPath="/Resources/Done.png"
InProgressPath="/Resources/Go.png" WarningPath="/Resources/Warning.png">
**<Control:SwapswireStatusImagePathConvertor.DealProp>
<Control:DealObject Deal="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type DataGrid}}, Path=Tag}"/>
</Control:SwapswireStatusImagePathConvertor.DealProp>**
</Control:SwapswireStatusImagePathConvertor>
</MultiBinding.Converter>
</MultiBinding>
</Image.Source>
</Image>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
Following is my Depenency property on Convertor
public class DealObject : DependencyObject
{
public static DependencyProperty TradedDecimalsProperty =
DependencyProperty.Register("TradedDecimals", typeof(Int32), typeof(DealObject), new UIPropertyMetadata(0));
public static DependencyProperty DealProperty =
DependencyProperty.Register("Deal", typeof(CMBSTrade), typeof(DealObject), new UIPropertyMetadata(new CMBSTrade()));
public CMBSTrade Deal
{
get { return (CMBSTrade)GetValue(DealProperty); }
set { SetValue(DealProperty, value); }
}
public Int32 TradedDecimals
{
get { return (Int32)GetValue(TradedDecimalsProperty); }
set { SetValue(TradedDecimalsProperty, value); }
}
}
[ValueConversion(typeof(object), typeof(BitmapImage))]
public class SwapswireStatusImagePathConvertor : IMultiValueConverter
{
public String AllGoodPath { get; set; }
public String InProgressPath { get; set; }
public String WarningPath { get; set; }
private DealObject _DealProp = null;
public DealObject DealProp
{
get { return _DealProp; }
set { _DealProp = value; }
}
public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
String path = WarningPath;
try
{
if (null != DealProp && null != DealProp.Deal && null != values && values.Length == 2)
{
String str1 = System.Convert.ToString(values[0]);
String str2 = System.Convert.ToString(values[1]);
if (DealProp.Deal.Swapswire)
{
switch (MBSConfirmationHelper.GetSwapswireStatus(str1, str2, MBSConfirmationHelper.GetParticipantType(DealProp.Deal)))
{
case DealExecutionStatus.InProgress:
path = InProgressPath; break;
case DealExecutionStatus.ActionRequired:
case DealExecutionStatus.Error:
path = WarningPath;
break;
case DealExecutionStatus.Executed:
path = AllGoodPath;
break;
case DealExecutionStatus.NotApplicable:
path = String.Empty;
break;
}
}
else path = String.Empty;
}
}
catch (Exception)
{
}
return String.IsNullOrEmpty(path)? null : new BitmapImage(new Uri(path, UriKind.Relative));
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException("Not Implemented");
}
}
in the above XAML code... I am trying to access datagrid's Tag property, the binding is working for the object is always comming null. How can I achieve this?
The problem is evident from your code...
<Control:SwapswireStatusImagePathConvertor.DealProp>
<Control:DealObject
Deal="{Binding RelativeSource={RelativeSource FindAncestor,
AncestorType={x:Type DataGrid}},
Path=Tag}"/>
</Control:SwapswireStatusImagePathConvertor.DealProp>**
Your converter or any property within the converter can never be part of the visual tree and hence the binding wont work on it even if your deal object or the converter itself is a DependencyObject!
Please revise your binding ...
Why does your converter need to have a property that needs binding? You already have MultiBinding, so involve this binding as part of that ....
<MultiBinding>
<Binding Path="SwapswireBuyerStatusText"/>
<Binding Path="SwapswireSellerStatusText"/>
**<Binding RelativeSource="{RelativeSource FindAncestor,
AncestorType={x:Type DataGrid}}"
Path="Tag"/>**
<MultiBinding.Converter>
<Control:SwapswireStatusImagePathConvertor
AllGoodPath="/Resources/Done.png"
InProgressPath="/Resources/Go.png"
WarningPath="/Resources/Warning.png" />
</MultiBinding.Converter>
</MultiBinding>
Now your converter receives all the 3 values that it needs ...
String str1 = System.Convert.ToString(values[0]);
String str2 = System.Convert.ToString(values[1]);
** this.DealProp = new DealObject();
this.DealProp.Deal = values[2] as CMBSTrade; **
if (DealProp.Deal.Swapswire)
{
....
}
Let me know if this helps...
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.