Ответ 1
Так как ValidationRule
не наследует от DependencyObject
, вы не можете создать DependecyProperty
в своем настраиваемом классе проверки.
Однако, как описано в этой ссылке, вы можете иметь нормальное свойство в своем классе проверки, который имеет тип, который наследуется от DependecyObject
и создайте DependencyProperty
в этом классе.
Например, это пользовательский класс ValidationRule
, который поддерживает свойство bindable:
[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
- это простой класс, наследующий от DependencyObject
и имеющий 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));
Это решает исходную проблему, но, к сожалению, вызывает еще две проблемы:
-
Связывание работает неправильно, поскольку
ValidationRules
не является частью визуального дерева и поэтому не может правильно получить связанное свойство. Например, этот наивный подход не будет работать:<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>
Вместо этого следует использовать прокси-объект, как описано в этом:
<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
- простой класс: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)); }
-
Если свойство в пользовательском
ValidationRule
связано с другим свойством объекта, логика проверки для исходного свойства не будет срабатывать при изменении этого другого свойства объекта.Чтобы решить эту проблему, мы должны обновить привязку при обновлении свойства привязки
ValidationRule
. Сначала мы должны привязать это свойство к нашему классуComparisonValue
. Затем мы можем обновить источник привязки при изменении свойстваValue
: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)); }
В первом случае существует такая же проблема с прокси-сервером. Поэтому мы должны создать еще один прокси-объект:
<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>
В этом случае свойство
Text
TextBoxToValidate
проверяется на свойствоItems.Count
SomeCollection
. Когда количество элементов в списке изменится, будет активирована проверка для свойстваText
.