Validation rule property: how to bind without using dependency properties - wpf

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.

Related

WPF ValidationTule with binding parameter problem

I need to pass a bind value to my validation rule. This is the XAML code:
<Label Grid.Column="11" Content="Default" Style="{StaticResource labelStyle2}" x:Name="txtTipo" />
<TextBox Grid.Column="12" Style="{StaticResource txtDataStyle1}" Width="100" TextChanged="Data_TextChanged">
<Binding Path="ConfigObject.Edit.Default" UpdateSourceTrigger="Default">
<Binding.ValidationRules>
<local:GenericValidationRule>
<local:GenericValidationRule.Wrapper>
<local:Wrapper TipoInterno="{Binding ElementName=txtTipo, Path=Content}"/>
</local:GenericValidationRule.Wrapper>
</local:GenericValidationRule>
</Binding.ValidationRules>
</Binding>
</TextBox>
Codebehind:
public class GenericValidationRule : ValidationRule
{
public Wrapper Wrapper { get; set; }
public override ValidationResult Validate(object value, CultureInfo cultureInfo)
{
var xx = Wrapper.TipoInternoProperty; //TEST
return new ValidationResult(is_valid, error_context);
}
}
public class Wrapper : DependencyObject
{
public static readonly DependencyProperty TipoInternoProperty = DependencyProperty.Register("TipoInterno", typeof(string), typeof(Wrapper), new PropertyMetadata(string.Empty));
public string TipoInterno
{
get { return (string)GetValue(TipoInternoProperty); }
set { SetValue(TipoInternoProperty, value); }
}
}
Basically I cannot get the required value into my TinoInterno property. If I hardcode a value such:
<local:Wrapper TipoInterno="TEST" />
the property is correctly valorized. In the first case I need to pass the property Content of control with elementName txtTipo.
What's wrong?
You should get the value of the TipoInterno of the Wrapper instance in the ValidationRule:
public override ValidationResult Validate(object value, CultureInfo cultureInfo)
{
string s = Wrapper.TipoInterno;
return ...;
}
You could then bind using an x:Reference:
<local:Wrapper TipoInterno="{Binding Path=Content, Source={x:Reference txtTipo}}"/>

Why is my dependency property binding not updating?

I have tried to create a MultiConverter that compares a CurrentUser's permission level to a subset of permissions from an enum, in order to toggle visibility of certain buttons. My converter is called at startup when the UserControl is created, but if I modify the CurrentUser's level after that, the converter is not called again.
The basic structure is:
KioskController owns UserInfo owns CurrentUser owns Level. I am binding to, and updating, CurrentUser.Level. Important snippets follow:
<Image x:Name="Redeem" Height="114" Width="178" Source="Graphics\MAINMENU_Redeem.png" Margin="128,260,718,394">
<Image.Visibility>
<MultiBinding Converter="{StaticResource theIntPermissionToVisibilityConverter}">
<Binding Path="_UserInfo.CurrentUser.Level"/>
<Binding Source="{x:Static local:UserInfo+UserLevel.Cashier}"/>
<Binding Source="{x:Static local:UserInfo+UserLevel.Manager}"/>
<Binding Source="{x:Static local:UserInfo+UserLevel.Tech}"/>
</MultiBinding>
</Image.Visibility>
</Image>
The KioskController:
public sealed class KC : DependencyObject
{
/// <summary>
/// Singleton interface to the Kiosk Controller
/// </summary>
///
#region _UserInfoDependencyProperty
public UserInfo _UserInfo
{
get { return (UserInfo)this.GetValue(_UserInfoProperty); }
set { this.SetValue(_UserInfoProperty, value); }
}
public static readonly DependencyProperty _UserInfoProperty = DependencyProperty.Register(
"_UserInfo", typeof(UserInfo), typeof(KC), new PropertyMetadata(null));
#endregion
The UserInfo class:
public class UserInfo : DependencyObject
{
#region CurrentUserProperty
public User CurrentUser
{
get { return (User)this.GetValue(CurrentUserProperty); }
set { this.SetValue(CurrentUserProperty, value); }
}
public static readonly DependencyProperty CurrentUserProperty = DependencyProperty.Register(
"CurrentUser", typeof(User), typeof(UserInfo), new PropertyMetadata(null));
#endregion
And finally the user class:
public class User : DependencyObject
{
#region UserLevelProperty
public UserInfo.UserLevel Level
{
get { return (UserInfo.UserLevel)this.GetValue(LevelProperty); }
set { this.SetValue(LevelProperty, value); }
}
public static readonly DependencyProperty LevelProperty = DependencyProperty.Register(
"UserLevel", typeof(UserInfo.UserLevel), typeof(User), new PropertyMetadata(UserInfo.UserLevel.Invalid));
#endregion
I am setting the DataContext of the usercontrol to my KioskController, and that appears to be working. I've tested a simple string binding in a textblock, and it showed up ok.
Finally, the call that updates CurrentUser, triggers the Setter, but never causes the converter to be called again:
CurrentUser.Level = theUser.Level;
I have binding errors enabled in the console window, and I don't see any problems in the output.
As I understand it, changing a DependencyProperty will cause an element bound to it to update, but it will not cause the converter to re-evalute. This appears to be generally considered as an oversight or bug. The solution is to force the reevaluation of the converter:
MultiBindingExpression be = BindingOperations.GetMultiBindingExpression(Redeem, Image.VisibilityProperty);
be.UpdateTarget();
or for a single binding converter:
BindingExpression be = BindingOperations.GetBindingExpression(Redeem, Image.VisibilityProperty);
be.UpdateTarget();
This is a little unfriendly, however, because your codebehind needs to be aware of the bindings you have in your XAML, and call this operation for each object using the converter in question. if there is a solution using iNotifyPropertyChanged, I would like to see it.
This link helped solve the problem: DependencyObject.InvalidateProperty not working

Adding a dependency property to a text box

How can I Add a dependency property to a text box and bind the dependency property to a Boolean property in silver light. my Boolean property is in my view model.
ImageSearchIsFocused is the property which allows me to set the focus on a text box.
<TextBox Text="{Binding ImgSearch, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}">
<i:Interaction.Behaviors>
<common:FocusBehavior HasInitialFocus="True" IsFocused="{Binding ImageSearchIsFocused, Mode=TwoWay}" ></common:FocusBehavior>
</i:Interaction.Behaviors>
</TextBox>
ImageIsFocused Property
bool _ImageSearchIsFocused;
public bool ImageSearchIsFocused
{
get { return _ImageSearchIsFocused; }
set
{
_ImageSearchIsFocused = value;
NotifyPropertyChanged("ImageSearchIsFocused");
}
}
If you want to add a dependency property, you're going to have the subclass the TextBox and add the dependency property to your subclass. Then you can bind that to whatever you like:
public class MyTextBox : TextBox
{
public static readonly DependencyProperty MyBooleanValueProperty = DependencyProperty.Register(
"MyBooleanValue", typeof(bool), typeof(MyTextBox),
new PropertyMetadata(new PropertyChangedCallback(MyBooleanValueChanged)));
public bool MyBooleanValue
{
get { return (bool)GetValue(MyBooleanValueProperty); }
set { SetValue(MyBooleanValueProperty, value); }
}
private static void MyBooleanValueChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var propValue = (bool)e.NewValue;
var control = d as MyTextBox;
// do something useful
}
}

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.

WPF ValidationRule with dependency property

Suppose you have a class inheriting from ValidationRule:
public class MyValidationRule : ValidationRule
{
public string ValidationType { get; set; }
public override ValidationResult Validate(object value, CultureInfo cultureInfo) {}
}
In XAML you are validating like this:
<ComboBox.SelectedItem>
<Binding Path="MyPath" Mode="TwoWay" UpdateSourceTrigger="PropertyChanged" NotifyOnValidationError="True">
<Binding.ValidationRules>
<qmvalidation:MyValidationRule ValidationType="notnull"/>
</Binding.ValidationRules>
</Binding>
</ComboBox.SelectedItem>
Which works and everything is ok.
But suppose now, you want to have ValidationType="{Binding MyBinding}" where MyBinding comes from DataContext.
For this purpose I would need to make MyValidationRule as a DependencyObject and add a Dependency Property.
I've tried to write a class that is DependencyObject, and bind it. There are 2 problems though.. the ValidationRule DOES NOT have the DataContext from the Combobox / Item.
Do you have any ideas, on how to solve that?
Since ValidationRule does not inherit from DependencyObject you cannot create a DependecyProperty in your custom validation class.
However as explained in this link you can have a normal property in your validation class which is of a type that inherits from DependecyObject and create a DependencyProperty in that class.
For example here is a custom ValidationRule class that support bindable property:
[ContentProperty("ComparisonValue")]
public class GreaterThanValidationRule : ValidationRule
{
public ComparisonValue ComparisonValue { get; set; }
public override ValidationResult Validate(object value, CultureInfo cultureInfo)
{
string s = value?.ToString();
int number;
if (!Int32.TryParse(s, out number))
{
return new ValidationResult(false, "Not a valid entry");
}
if (number <= ComparisonValue.Value)
{
return new ValidationResult(false, $"Number should be greater than {ComparisonValue}");
}
return ValidationResult.ValidResult;
}
}
ComparisonValue is a simple class that inherits from DependencyObject and has a DependencyProperty:
public class ComparisonValue : DependencyObject
{
public int Value
{
get { return (int)GetValue(ValueProperty); }
set { SetValue(ValueProperty, value); }
}
public static readonly DependencyProperty ValueProperty = DependencyProperty.Register(
nameof(Value),
typeof(int),
typeof(ComparisonValue),
new PropertyMetadata(default(int));
This solves the original problem but unfortunately brings two more problems:
The binding does not work correctly since the ValidationRules is not part of visual tree and therefore cannot get the bound property correctly. For example this naive approach will not work:
<TextBox Name="TextBoxToValidate">
<TextBox.Text>
<Binding Path="ViewModelProperty" UpdateSourceTrigger="PropertyChanged">
<Binding.ValidationRules>
<numbers:GreaterThanValidationRule>
<numbers:ComparisonValue Value="{Binding Text, ElementName=TextBoxToValidate}"/>
</numbers:GreaterThanValidationRule>
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
Instead a proxy object should be used as explained in this answer:
<TextBox Name="TextBoxToValidate">
<TextBox.Resources>
<bindingExtensions:BindingProxy x:Key="TargetProxy" Data="{Binding Path=Text, ElementName=TextBoxToValidate}"/>
</TextBox.Resources>
<TextBox.Text>
<Binding Path="ViewModelProperty" UpdateSourceTrigger="PropertyChanged">
<Binding.ValidationRules>
<numbers:GreaterThanValidationRule>
<numbers:ComparisonValue Value="{Binding Data, Source={StaticResource TargetProxy}}"/>
</numbers:GreaterThanValidationRule>
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
BindingProxy is a simple class:
public class BindingProxy : Freezable
{
protected override Freezable CreateInstanceCore()
{
return new BindingProxy();
}
public object Data
{
get { return GetValue(DataProperty); }
set { SetValue(DataProperty, value); }
}
public static readonly DependencyProperty DataProperty = DependencyProperty.Register(nameof(Data), typeof(object), typeof(BindingProxy), new UIPropertyMetadata(null));
}
If the property in custom ValidationRule is bound to another object's property, the validation logic for the original property will not fire when that other object's property changes.
To solve this problem we should update the binding when the ValidationRule's bound property is updated. First we should bind that property to our ComparisonValue class. Then, we can update the source of the binding when the Value property changes:
public class ComparisonValue : DependencyObject
{
public int Value
{
get { return (int)GetValue(ValueProperty); }
set { SetValue(ValueProperty, value); }
}
public static readonly DependencyProperty ValueProperty = DependencyProperty.Register(
nameof(Value),
typeof(int),
typeof(ComparisonValue),
new PropertyMetadata(default(int), OnValueChanged));
private static void OnValueChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
ComparisonValue comparisonValue = (ComparisonValue) d;
BindingExpressionBase bindingExpressionBase = BindingOperations.GetBindingExpressionBase(comparisonValue, BindingToTriggerProperty);
bindingExpressionBase?.UpdateSource();
}
public object BindingToTrigger
{
get { return GetValue(BindingToTriggerProperty); }
set { SetValue(BindingToTriggerProperty, value); }
}
public static readonly DependencyProperty BindingToTriggerProperty = DependencyProperty.Register(
nameof(BindingToTrigger),
typeof(object),
typeof(ComparisonValue),
new FrameworkPropertyMetadata(default(object), FrameworkPropertyMetadataOptions.BindsTwoWayByDefault));
}
The same proxy problem in the first case also exists here. Therefore we should create another proxy object:
<ItemsControl Name="SomeCollection" ItemsSource="{Binding ViewModelCollectionSource}"/>
<TextBox Name="TextBoxToValidate">
<TextBox.Resources>
<bindingExtensions:BindingProxy x:Key="TargetProxy" Data="{Binding Path=Items.Count, ElementName=SomeCollection}"/>
<bindingExtensions:BindingProxy x:Key="SourceProxy" Data="{Binding Path=Text, ElementName=TextBoxToValidate, Mode=TwoWay}"/>
</TextBox.Resources>
<TextBox.Text>
<Binding Path="ViewModelProperty" UpdateSourceTrigger="PropertyChanged">
<Binding.ValidationRules>
<numbers:GreaterThanValidationRule>
<numbers:ComparisonValue Value="{Binding Data, Source={StaticResource TargetProxy}}" BindingToTrigger="{Binding Data, Source={StaticResource SourceProxy}}"/>
</numbers:GreaterThanValidationRule>
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
In this case the Text property of TextBoxToValidate is validated against the Items.Count property of SomeCollection. When the number of items in the list changes, the validation for the Text property will be triggered.

Resources