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.
Related
In my WPF MVVM app I have a TextBox which has bound a validation rule.
In validation rule class I have below property:
public bool CanBeValidated { get; set; } = false;
Then in the view my TextBox has below validation rule bound (I only put the relevant part):
<TextBox.Text>
<Binding Path="myPath"
UpdateSourceTrigger="PropertyChanged"
ValidatesOnDataErrors="True">
<Binding.ValidationRules>
<vRules:RootPathValidationRule
ValidatesOnTargetUpdated="True"
CanBeValidated="{Binding Path=ValidationEnabled}"/>
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
In my view model the property is defined as below:
public bool ValidationEnabled
{
get { return _isValidationEnabled; }
set { this._isValidationEnabled = value; OnPropertyChanged(); }
}
So I receive below compilation error:
A 'Binding' cannot be set on the 'CanBeValidated' property of type
'MyPathValidatorRule'. A 'Binding' can only be set on a
DependencyProperty of a DependencyObject.
For first time when TextBox is loaded I want to avoid validation rule to fire until user edits it and avoid throwing a validation error since TextBox is empty.
Once user edits the TextBox, I would like to enable validation rule by performing a simple this.ValidationEnabled = true from view model.
How can I achieve this without using dependency properties? Is it possible?
You could create a wrapper class that derives from DependencyObject and exposes a dependency property. Then you add a CLR property to the ValidationRule class that returns an instance of this wrapper type:
public class RootPathValidationRule : ValidationRule
{
public Wrapper Wrapper { get; set; }
public override ValidationResult Validate(object value, CultureInfo cultureInfo)
{
bool canBeValidated = Wrapper?.CanBeValidated == true;
...
}
}
public class Wrapper : DependencyObject
{
public static readonly DependencyProperty CanBeValidatedProperty =
DependencyProperty.Register(nameof(CanBeValidated), typeof(bool),
typeof(Wrapper));
public bool CanBeValidated
{
get { return (bool)GetValue(CanBeValidatedProperty); }
set { SetValue(CanBeValidatedProperty, value); }
}
}
Finally, you'll also need a binding proxy object that captures the DataContext where the source property is defined:
public class BindingProxy : System.Windows.Freezable
{
protected override Freezable CreateInstanceCore()
{
return new BindingProxy();
}
public object Data
{
get { return (object)GetValue(DataProperty); }
set { SetValue(DataProperty, value); }
}
public static readonly DependencyProperty DataProperty =
DependencyProperty.Register("Data", typeof(object), typeof(BindingProxy), new PropertyMetadata(null));
}
XAML:
<TextBox>
<TextBox.Resources>
<vRules:BindingProxy x:Key="proxy" Data="{Binding}"/>
</TextBox.Resources>
<TextBox.Text>
<Binding Path="myPath"
UpdateSourceTrigger="PropertyChanged"
ValidatesOnDataErrors="True">
<Binding.ValidationRules>
<vRules:RootPathValidationRule ValidatesOnTargetUpdated="True">
<vRules:RootPathValidationRule.Wrapper>
<vRules:Wrapper CanBeValidated="{Binding Data.ValidationEnabled,
Source={StaticResource proxy}}"/>
</vRules:RootPathValidationRule.Wrapper>
</vRules:RootPathValidationRule>
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
Please refer to this article for details.
This is my GridViewColumn:
<GridViewColumn Width="180" Header="Status">
<GridViewColumn.CellTemplate>
<DataTemplate>
<TextBlock x:Name="Txt" Text="{Binding Status}" Foreground="Yellow" />
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
The Status field is a property of my binding object and all i want to do is change this GridViewColumn color but this time base on condition:
I have another propgerty called StatusMessage which is simple enum:
public enum StatusMessage
{
InProcess,
Done,
Wait
}
So this enum property is changing all the time and for every value of this enum i want to define different color.
Is it possible ?
Edit
My View model class inherit from BaseObservableObject:
public class BaseObservableObject : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
var handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
protected virtual void OnPropertyChanged<T>(Expression<Func<T>> raiser)
{
var propName = ((MemberExpression)raiser.Body).Member.Name;
OnPropertyChanged(propName);
}
protected bool Set<T>(ref T field, T value, [CallerMemberName] string name = null)
{
if (!EqualityComparer<T>.Default.Equals(field, value))
{
field = value;
OnPropertyChanged(name);
return true;
}
return false;
}
}
My properties:
public string Status
{
get { return _status; }
set
{
_status = value;
OnPropertyChanged();
}
}
public StatusMsg StatusMessage
{
get { return _statusMsg; }
set {
_statusMsg = value;
OnPropertyChanged();
}
}
XAML:
<GridViewColumn Width="180" Header="Status" DisplayMemberBinding="{Binding Status}">
<GridViewColumn.CellTemplate>
<DataTemplate>
<TextBlock Foreground="{Binding StatusMsg,Converter={c:StatusMessageToColorConverter}}" />
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
StatusMessageToColorConverter is the same as #grek40 suggested and still my TextBlock Foreground not changing.
I first focus on the value conversion, then I say something about the other requirement ("So this enum property is changing all the time and for every value of this enum i want to define different color.")
Since you have an enum value but you want to have a color specification, you need to convert the value. This can be done with the help of an implementation of the IConverter interface. There are different ways to reference a converter in XAML, I additionally inherit from MarkupExtension so I am able to access the converter directly.
public class StatusMessageToColorConverter : MarkupExtension, IValueConverter
{
// one way converter from enum StatusMessage to color
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (value is StatusMessage && targetType == typeof(Brush))
{
switch ((StatusMessage)value)
{
case StatusMessage.InProcess:
return Brushes.Yellow;
case StatusMessage.Done:
return Brushes.Green;
case StatusMessage.Wait:
return Brushes.Red;
}
}
return null;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
public StatusMessageToColorConverter()
{
}
// MarkupExtension implementation
public override object ProvideValue(IServiceProvider serviceProvider)
{
return this;
}
}
As you can see, I convert if the input value is of type StatusMessage and the target type is typeof(Brush), which is the Type of the Foreground property. I just chose some colors, you may use different colors or even more complex brushes if you like.
In XAML, the namespace of the converter needs to be referenced. In my case its just WpfApplication2 associated to the XAML namespace name c
<Window
[... other properties]
xmlns:c="clr-namespace:WpfApplication2">
Now, when binding to the foreground property, utilize the converter
<TextBlock Text="{Binding Status}" Foreground="{Binding StatusMessage,Converter={c:StatusMessageToColorConverter}}" />
This should do the trick.
Now to the other part about dynamically changing the value. Your viewmodel class needs to implement INotifyPropertyChanged and raise the PropertyChanged event whenever a value is changed. As an example, see the following class that only contains the two properties of your example and the necessary notification logic.
public class ItemModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
void NotifyPropertyChanged([CallerMemberName]string prop = null)
{
var tmp = PropertyChanged;
if (tmp != null) tmp(this, new PropertyChangedEventArgs(prop));
}
private string _Status;
public string Status
{
get { return _Status; }
set { _Status = value; NotifyPropertyChanged(); }
}
private StatusMessage _StatusMessage;
public StatusMessage StatusMessage
{
get { return _StatusMessage; }
set { _StatusMessage = value; NotifyPropertyChanged(); }
}
}
More complex viewmodels can follow the same approach. For less update overhead, compare the current value and the new value in the setter and only notify if the value actually changes.
In your view model you could add a property called GetColour that looks at the current enum value and returns a colour. Then just bind the GetColour property in your xaml.
i wrote custom control base on TextBox which has also Minimum and Maximum inputs as follows:
public class NumericTextBox : TextBox
{
static NumericTextBox()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(NumericTextBox), new FrameworkPropertyMetadata(typeof(NumericTextBox)));
}
public static readonly DependencyProperty MinimumProperty =
DependencyProperty.Register("Minimum", typeof(int), typeof(NumericTextBox), new PropertyMetadata(default(int)));
public int Minimum
{
get { return (int)GetValue(MinimumProperty); }
set { SetValue(MinimumProperty, value); }
}
public static readonly DependencyProperty MaximumProperty =
DependencyProperty.Register("Maximum", typeof(int), typeof(NumericTextBox), new PropertyMetadata(100));
public int Maximum
{
get { return (int)GetValue(MaximumProperty); }
set { SetValue(MaximumProperty, value); }
}
public new static readonly DependencyProperty TextProperty =
DependencyProperty.Register("Text", typeof(int), typeof(NumericTextBox),
new FrameworkPropertyMetadata(
default(int),
FrameworkPropertyMetadataOptions.BindsTwoWayByDefault,
null,
CoerceCurrentValue),
IsValid);
public new int Text
{
get { return (int)GetValue(TextProperty); }
set { SetValue(TextProperty, value); }
}
private static object CoerceCurrentValue(DependencyObject d, object baseValue)
{
var numericTextBox = (NumericTextBox)d;
var intValue = (int)baseValue;
if (intValue < numericTextBox.Minimum) intValue = numericTextBox.Minimum;
if (intValue > numericTextBox.Maximum) intValue = numericTextBox.Maximum;
if ((int)baseValue != intValue)
numericTextBox.Text = intValue;
return intValue;
}
private static bool IsValid(object value)
{
if (value == null)
return false;
int intValue;
var result = Int32.TryParse(value.ToString(), out intValue);
return result;
}
}
and in my xaml i call it:
<controls:NumericTextBox
Grid.Row="0"
Grid.Column="1"
Margin="5"
VerticalAlignment="Center"
Text="{Binding Test, UpdateSourceTrigger=PropertyChanged}"
Minimum="0"
Maximum="100"
/>
it is bind to Test property in my view model (as int).
everything works good until i type a character and i get binding error:
System.Windows.Data Error: 7 : ConvertBack cannot convert value '1a'
(type 'String'). BindingExpression:Path=Text;
DataItem='NumericTextBox' (Name=''); target element is 'TextBox'
(Name=''); target property is 'Text' (type 'String')
FormatException:'System.FormatException: Input string was not in a
correct format. at System.Number.StringToNumber(String str,
NumberStyles options, NumberBuffer& number, NumberFormatInfo info,
Boolean parseDecimal) at System.Number.ParseInt32(String s,
NumberStyles style, NumberFormatInfo info) at
System.String.System.IConvertible.ToInt32(IFormatProvider provider)
at System.Convert.ChangeType(Object value, Type conversionType,
IFormatProvider provider) at
MS.Internal.Data.SystemConvertConverter.ConvertBack(Object o, Type
type, Object parameter, CultureInfo culture) at
System.Windows.Data.BindingExpression.ConvertBackHelper(IValueConverter
converter, Object value, Type sourceType, Object parameter,
CultureInfo culture)'
it may be because the original Text property in TextBox is string... but i'm not sure.
please assist on that.
You are going about this in the wrong way. You are not restricting the input of the TextBox to only numerical numbers. The Framework is trying to convert a string into an int to fit into your new int Text property, but as the error says: Input string was not in a correct format., eg. not an int. Try adding this into the constructor instead:
PreviewTextInput += new TextCompositionEventHandler((s, e) => e.Handled =
!e.Text.All(c => Char.IsNumber(c) && c != ' '));
PreviewKeyDown += new KeyEventHandler((s, e) => e.Handled = e.Key == Key.Space);
They simply work by setting e.Handled to true if non numerical values are input and this has the effect of ignoring the input on those occasions.
Also, you don't need to implement your own Text property... that is just confusing matters. Just use the original one and parse the value into an int wherever you need to, resting assured that it will be a number. I think that these two handlers should ensure that. Let me know if you have any problems.
Another idea is to simply create an AttachedProperty using these handlers and then you can apply it to any TextBox control. Of course, you'd then need to implement your Minimum and Maximum properties as AttachedProperties too, but then you could do something like this:
<TextBox Text={Binding Test} Attached:TextBoxProperties.IsNumeric="True"
Attached:TextBoxProperties.Minimum="0" Attached:TextBoxProperties.Maximum="100" />
You can find out more from the Attached Properties Overview page on MSDN.
UPDATE >>>
If you don't want the user to be able to delete the last character from the TextBox, then we can just change one of the handlers to disallow it:
PreviewKeyDown += PreviewKeyDown;
...
private void PreviewKeyDown(object sender, KeyEventArgs e)
{
TextBox textBox = sender as TextBox;
e.Handled = e.Key == Key.Space ||
(textBox.Text.Length == 1 && (e.Key == Key.Delete || e.Key == Key.Back));
}
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.
I have some XAML textboxes that need to allow nothing but double and some that need to allow nothing but int.
I could use Binding.ValidationRules with all its code behind, triggers, styles as described here, but isn't there a way for these simple validations to use an attribute something like this, just so nothing but that type can be typed in:
PSEUDO-CODE:
<TextBox Text="{Binding NumberOfContracts}" SimpleValidation="{x:Type sys:Integer}"/>
<TextBox Text="{Binding ContractPrice}" SimpleValidation="{x:Type sys:Double}"/>
I have always thought about something as useful as this but now that I've read your question, I got around to writing one.
using System;
using System.Globalization;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
namespace SimpleValidation
{
public class SimpleValidator : ValidationRule
{
#region Validation Attached Property
public static Type GetValidationType(DependencyObject obj)
{
return (Type)obj.GetValue(ValidationTypeProperty);
}
public static void SetValidationType(DependencyObject obj, Type value)
{
obj.SetValue(ValidationTypeProperty, value);
}
// Using a DependencyProperty as the backing store for ValidationType. This enables animation, styling, binding, etc...
public static readonly DependencyProperty ValidationTypeProperty =
DependencyProperty.RegisterAttached("ValidationType", typeof(Type), typeof(ValidationRule), new UIPropertyMetadata(null, OnValidationTypeChanged));
private static void OnValidationTypeChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
{
var element = obj as FrameworkElement;
if (element == null) return;
// When the element has loaded.
element.Loaded += (s, e) =>
{
// Create a new validator
var validation = new SimpleValidator(args.NewValue as Type);
// Get the binding expression for the textbox property
var binding = BindingOperations.GetBinding(obj, TextBox.TextProperty);
// If not null and doesn't already contain the validator, then add it.
if (binding != null)
if (!binding.ValidationRules.Contains(validation))
binding.ValidationRules.Add(validation);
};
}
#endregion
#region Validation
public SimpleValidator() { }
public SimpleValidator(Type validationType)
{
ValidationType = validationType;
}
public Type ValidationType { get; set; }
public override ValidationResult Validate(object value, CultureInfo cultureInfo)
{
try
{
// Try to convert to the type specified
Convert.ChangeType(value, ValidationType);
// Accept value
return new ValidationResult(true, "Valid value");
}
catch (Exception)
{
// return invalid type error.
return new ValidationResult(false, "Value is not of type " + ValidationType.FullName);
}
}
#endregion
}
}
Usage example:
<TextBox Text="{Binding Text, UpdateSourceTrigger=PropertyChanged}" validator:SimpleValidator.ValidationType="{x:Type system:Double}" />
Make sure UpdateSourceTrigger reflects the type of update you require, usually PropertyChanged is best. I hope you enjoy it as much as I writing it. Full source code here.