WPF ValidationRule with dependency property - wpf

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.

Related

Validation rule property: how to bind without using dependency properties

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.

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}}"/>

wpf binding property in ValidationRule

i'm having a form with 2 text boxes:
TotalLoginsTextBox
UploadsLoginsTextBox
i want to limit UploadsLoginsTextBox so the maximum input for the text will be the value of the TotalLoginsTextBox.
i am also using a value converter so i try to bound the Maximum value:
this is the XAML:
<!-- Total Logins -->
<Label Margin="5">Total:</Label>
<TextBox Name="TotalLoginsTextBox" MinWidth="30" Text="{Binding Path=MaxLogins, Mode=TwoWay}" />
<!-- Uploads -->
<Label Margin="5">Uploads:</Label>
<TextBox Name="UploadsLoginsTextBox" MinWidth="30">
<TextBox.Text>
<Binding Path="MaxUp" Mode="TwoWay" NotifyOnValidationError="True">
<Binding.ValidationRules>
<Validators:MinMaxRangeValidatorRule Minimum="0" Maximum="{Binding Path=MaxLogins}" />
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
the problem i am getting the following error:
A 'Binding' cannot be set on the 'Maximum' property of type
'MinMaxRangeValidatorRule'. A 'Binding' can only be set on a
DependencyProperty of a DependencyObject.
what is the proper way to do the binding ?
You're seeing this error because MinMaxRangeValidatorRule.Maximum needs to be a DependencyProperty if you want to bind it to MaxLogins, while it is probably a simple CLR property.
The real problem is that MinMaxRangeValidatorRule should be able to inherit from ValidationRule AND from DependencyObject (to make Dependency Properties available). This is not possible in C#.
I solved a similar problem in this way:
give a name to your validator rule
<Validators:MinMaxRangeValidatorRule Name="MinMaxValidator" Minimum="0" />
in code behind, set the Maximum value whenever MaxLogins changes
public int MaxLogins
{
get { return (int )GetValue(MaxLoginsProperty); }
set { SetValue(MaxLoginsProperty, value); }
}
public static DependencyProperty MaxLoginsProperty = DependencyProperty.Register("MaxLogins ",
typeof(int),
typeof(mycontrol),
new PropertyMetadata(HandleMaxLoginsChanged));
private static void HandleMinValueChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
mycontrol source = (mycontrol) d;
source.MinMaxValidator.Maximum = (int) e.NewValue;
}
I'm guessing the "MinMaxRangeValidatorRule" is something custom.
The error message is quite explicit actually, you need to make the "Maximum" variable a Dependency Property, like so:
public int Maximum
{
get { return (int)GetValue(MaximumProperty); }
set { SetValue(MaximumProperty, value); }
}
// Using a DependencyProperty as the backing store for Maximum. This enables animation, styling, binding, etc...
public static readonly DependencyProperty MaximumProperty =
DependencyProperty.Register("Maximum", typeof(int), typeof(MinMaxRangeValidatorRule), new UIPropertyMetadata(0));
You can access the dependency property snippet by typing "propdp" in vs2010.

How to use Binding like proxy?

<Setter Property="IsChecked">
<Setter.Value>
<MultiBinding>
<!-- Get value for property -->
<Binding Path="IsPressed" RelativeSource="{RelativeSource Self}" Mode="OneWay"/>
<!-- Set value to ViewModel's property -->
<Binding Path="Shift" Mode="OneWayToSource"/>
</MultiBinding>
</Setter.Value>
</Setter>
I need to use 2 bindings for property: one to get value for property and one to set value to ViewModel's property.
How I can realize this scenario?
You can create a couple of attached properties. One will be the target of your binding, and second will contain binding for your proxy. Example:
Then in ProxySource OnChange implementation you will get TextBox as UIElement, there you can read value from ProxySource and write it to ProxyTarget.
This is not a very clean aproach, but it should work.
If you can't get it working, I can write a complete sample later.
Ok, I've implemented everything, here's complete source:
public class ViewModel : ViewModelBase
{
string sourceText;
public string SourceText
{
get { return sourceText; }
set
{
if (sourceText == value) return;
sourceText = value;
System.Diagnostics.Debug.WriteLine("SourceText:" + value);
RaisePropertyChanged("SourceText");
}
}
string targetText;
public string TargetText
{
get { return targetText; }
set
{
if (targetText == value) return;
targetText = value;
System.Diagnostics.Debug.WriteLine("TargetText:" + value);
RaisePropertyChanged("TargetText");
}
}
}
public static class AttachedPropertiesHost
{
public static object GetProxySource(DependencyObject obj)
{
return obj.GetValue(ProxySourceProperty);
}
public static void SetProxySource(DependencyObject obj, object value)
{
obj.SetValue(ProxySourceProperty, value);
}
public static readonly DependencyProperty ProxySourceProperty =
DependencyProperty.RegisterAttached(
"ProxySource", typeof(object), typeof(AttachedPropertiesHost),
new UIPropertyMetadata(null, ProxySourcePropertyPropertyChanged)
);
private static void ProxySourcePropertyPropertyChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs e)
{
dependencyObject.Dispatcher.BeginInvoke(
new { Dp = dependencyObject, NewValue = e.NewValue },
args => SetProxyTarget(args.Dp, args.NewValue)
);
}
public static object GetProxyTarget(DependencyObject obj)
{
return obj.GetValue(ProxyTargetProperty);
}
public static void SetProxyTarget(DependencyObject obj, object value)
{
obj.SetValue(ProxyTargetProperty, value);
}
public static readonly DependencyProperty ProxyTargetProperty =
DependencyProperty.RegisterAttached("ProxyTarget", typeof(object), typeof(AttachedPropertiesHost));
}
<TextBox Text="{Binding SourceText, UpdateSourceTrigger=PropertyChanged}"
WpfDataGridLayout:AttachedPropertiesHost.ProxySource="{Binding RelativeSource={RelativeSource Self}, Path=Text, UpdateSourceTrigger=PropertyChanged}"
WpfDataGridLayout:AttachedPropertiesHost.ProxyTarget="{Binding TargetText, Mode=OneWayToSource}"
/>
And the output from console while editing textbox:
SourceText:f
TargetText:f
SourceText:fh
TargetText:fh
SourceText:fhh
TargetText:fhh
Please dont design your solution around IsPressed, thats actually what some call a flash data which means it changes back to a default value (false) sooner. Also contextually Binding will have dedicated target, source and mode. In MultiBinding acheiving one way IsPressed (from a Source) and other way saving back to another Target is not supported. For two way update to occur, all bindings have to be TowWay.
Although a Hack to this could be using MultiConverter having a Target itself as one of the values.
<MultiBinding Converter="MyMultiBindingConverter">
<!-- Get value for property -->
<Binding Path="IsPressed"
RelativeSource="{RelativeSource Self}" Mode="OneWay"/>
<!-- Set value to ViewModel's property -->
<Binding BindsDirectlyToSource="True"/>
</MultiBinding>
MyMultiBindingConverter.Convert()
{
var myViewModel = values[1] as MyViewModel;
var isPressed = bool.Parse(values[0].ToString());
if (isPressed)
{
myViewModel.Shift = !myViewModel.Shift;
}
}
But this is strongly NOT recommended.

XAML Binding.UpdateSourceTrigger when a Button.Click is fired?

I want to set the UpdateSourceTrigger to an event of a control:
<TextBox Text="{Binding Field, UpdateSourceMode=btnOK.Click}">
<Button Name="btnOK">
<Button.Triggers>
<Trigger>
<!-- Update source -->
</Trigger>
</Button.Triggers>
</Button>
I thought about two ways:
Set UpdateSourceMode or some other stuff in the binding.
Set an EventTrigger that updates source on button click.
Possible, or I have to do it with code?
You'll have to use code. Specifically:
Set UpdateSourceTrigger=Explicit on the TextBox.
Call UpdateSource when the user clicks the Button.
However, you can put the code in the code behind or in an attached behavior.
I know it's been a while, but I came across the same issue and want to share my solution. Hope it will be helpful for somebody.
public class UpdateSourceBehavior : Behavior<System.Windows.Interactivity.TriggerBase>
{
internal const string TargetElementPropertyLabel = "TargetElement";
static UpdateSourceBehavior()
{
TargetElementProperty = DependencyProperty.Register
(
TargetElementPropertyLabel,
typeof(FrameworkElement),
typeof(UpdateSourceBehavior),
new PropertyMetadata(null)
);
}
public static readonly DependencyProperty TargetElementProperty;
[Bindable(true)]
public FrameworkElement TargetElement
{
get { return (FrameworkElement)base.GetValue(TargetElementProperty); }
set { base.SetValue(TargetElementProperty, value); }
}
public PropertyPath TargetProperty { get; set; }
protected override void OnAttached()
{
base.OnAttached();
this.InitializeMembers();
base.AssociatedObject.PreviewInvoke += this.AssociatedObject_PreviewInvoke;
}
protected override void OnDetaching()
{
base.AssociatedObject.PreviewInvoke -= this.AssociatedObject_PreviewInvoke;
base.OnDetaching();
}
private void AssociatedObject_PreviewInvoke(object sender, PreviewInvokeEventArgs e)
{
this.m_bindingExpression.UpdateSource();
}
private void InitializeMembers()
{
if (this.TargetElement != null)
{
var targetType = this.TargetElement.GetType();
var fieldInfo = targetType.GetFields(BindingFlags.Public | BindingFlags.Static | BindingFlags.FlattenHierarchy)
.FirstOrDefault(fi => fi.Name == this.TargetProperty.Path + "Property");
if (fieldInfo != null)
this.m_bindingExpression = this.TargetElement.GetBindingExpression((DependencyProperty)fieldInfo.GetValue(null));
else
throw new ArgumentException(string.Format("{0} doesn't contain a DependencyProperty named {1}.", targetType, this.TargetProperty.Path));
}
else
throw new InvalidOperationException("TargetElement must be assigned to in order to resolve the TargetProperty.");
}
private BindingExpression m_bindingExpression;
}
Here is my solution:
XAML:
<StackPanel>
<i:Interaction.Triggers>
<i:EventTrigger SourceName="submit" EventName="Click">
<behaviours:TextBoxUpdateSourceAction TargetName="searchBox"></behaviours:TextBoxUpdateSourceAction>
</i:EventTrigger>
</i:Interaction.Triggers>
<TextBox x:Name="searchBox">
<TextBox.Text>
<Binding Path="SomeProperty" UpdateSourceTrigger="Explicit" NotifyOnValidationError="True">
<Binding.ValidationRules>
<DataErrorValidationRule ValidatesOnTargetUpdated="False"/>
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
<Button x:Name="submit"></Button>
</StackPanel>
Behaviour definition (inherited from TargetedTriggerAction):
public class TextBoxUpdateSourceAction : TargetedTriggerAction<TextBox>
{
protected override void Invoke(object parameter)
{
BindingExpression be = Target.GetBindingExpression(TextBox.TextProperty);
be.UpdateSource();
}
}
Please note that it's important to attach TextBoxUpdateSourceAction to parent container (StackPanel in example code).

Resources