Reputation: 1160
I am working on a .NET 4.0 project that deals with validation of the range of a particular data type. For example, a 32 bit int should only be between Int32.MinValue
and Int32.MaxValue
or any other value defined by the application. I want to be able to specify data types and ranges on a custom validator so they can be called directly from the xaml through binding: <CheckIfValueRangeValidator>
This is what I have in mind, I am not sure it will work or if it can even be done through the xaml.
class CheckIfValueInRangeValidator<T> : ValidationRule
{
public T Max { get; set; }
public T Min { get; set; }
public override ValidationResult Validate(object value, System.Globalization.CultureInfo cultureInfo)
{
// Implementation...
}
}
Upvotes: 3
Views: 1829
Reputation: 1734
I had a similar problem: I needed a ValidationRule
specific to the binding source, i.e. I wanted the source to define the rule.
I ended up using the following approach of an attached property that sets the binding incl. rule via code.
ICustomRuleProvider.cs: Interface the binding source (ViewModel) must implement.
public interface ICustomRuleProvider
{
ValidationRule CreateRule();
}
CustomRule.cs: Attached property used in Xaml to create the required binding incl. rule.
public class CustomRule
{
public static ICustomRuleProvider GetProvider(DependencyObject obj)
{
return (ICustomRuleProvider)obj.GetValue(ProviderProperty);
}
public static void SetProvider(DependencyObject obj, ICustomRuleProvider value)
{
obj.SetValue(ProviderProperty, value);
}
public static readonly DependencyProperty ProviderProperty =
DependencyProperty.RegisterAttached("Provider", typeof(ICustomRuleProvider), typeof(CustomRule), new FrameworkPropertyMetadata(null, CustomRule.OnProviderPropertyChanged));
public static void OnProviderPropertyChanged(object sender, DependencyPropertyChangedEventArgs e)
{
if (!(sender is TextBox fe)) return;
var provider = e.NewValue as ICustomRuleProvider;
if (provider != null)
{
var b = new Binding("Value");
b.ValidationRules.Add(provider.CreateRule());
fe.SetBinding(System.Windows.Controls.TextBox.TextProperty, b);
}
else
{
BindingOperations.ClearBinding(fe, TextBox.TextProperty);
}
}
}
Example: Be sure to define xmlns:customRule="clr-namespace:...;assembly=..."
<TextBox customRule:CustomRule.Provider="{Binding}"/>
This implementation is tailor-made for binding a the Text
property of a TextBox
the "Value" property of the binding source, but this can easily be extended.
Using this I was able to use a custom (generic) converter.
BTW: I do not agree that a custom generic ValidationRule
is a "bad idea", although the use cases may be limited. In many cases it may be necessary to also implement a custom IValueConverter
and assign that to the binding as well to have full control over the object value on its way from the control to the source.
Upvotes: 0
Reputation: 16652
My bad, actually you cannot use x:TypeArguments as it will raise x:TypeArguments is not allowed in object elements for XAML version lower than 2009, it's valid only on loose XAML files or the root element (Window in my case) ...
http://msdn.microsoft.com/en-us/library/ms750476.aspx
But as a workaround you could use the following pattern :
<TextBox x:Name="textBox1">
<Binding Path="MyValue"
Source="{StaticResource MyObject}"
UpdateSourceTrigger="PropertyChanged">
<Binding.ValidationRules>
<wpfApplication6:MyValidationRule ObjectType="{x:Type system:Int32}" />
</Binding.ValidationRules>
</Binding>
</TextBox>
Code behind :
public class MyObject
{
public object MyValue { get; set; }
}
public class MyValidationRule : ValidationRule
{
public Type ObjectType { get; set; }
public override ValidationResult Validate(object value, CultureInfo cultureInfo)
{
throw new NotImplementedException();
}
}
Take a look at this one too : Using a Generic IValueConverter from XAML
@Blam comment is worthwhile to consider, a range to check generally applies for an integer or a double, for other types I'd say you could just add a boolean that return the validity of that object and perform such validation inside the object itself.
For numbers you have RangeAttribute but it's not really part of the WPF validation infrastructure.
You also have an another option for validation : INotifyDataErrorInfo validation takes place inside the object in this case.
I wrote a lengthy answer here : https://softwareengineering.stackexchange.com/questions/203590/is-there-an-effective-way-for-creating-complex-forms you might find something useful on it.
From my experience I'd say that a generic validating rule is probably not wise to do.
You should edit your question to be less generic ;-) but rather more specific, you'd get more help from people here. Give one or two concrete cases of the objects you are trying to validate.
EDIT
You can also use a BindingGroup for validating an object :
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void TextBox1_OnTextChanged(object sender, TextChangedEventArgs e)
{
grid.BindingGroup.CommitEdit();
}
}
public class Indices
{
public int ColorIndex { get; set; }
public string ColorPrefix { get; set; }
public int GradientIndex { get; set; }
public string GradientPrefix { get; set; }
}
public class ValidateMe : ValidationRule
{
public override ValidationResult Validate(object value, CultureInfo cultureInfo)
{
var bindingGroup = value as BindingGroup;
var o = bindingGroup.Items[0] as Indices;
return new ValidationResult(true, null);
}
}
<Window x:Class="WpfApplication6.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:wpfApplication6="clr-namespace:WpfApplication6"
Title="MainWindow"
Width="525"
Height="350">
<Window.Resources>
<wpfApplication6:Indices x:Key="Indices"
ColorIndex="1"
ColorPrefix="MyColor"
GradientIndex="1"
GradientPrefix="MyGradient" />
</Window.Resources>
<Grid x:Name="grid" DataContext="{StaticResource Indices}">
<Grid.BindingGroup>
<BindingGroup>
<BindingGroup.ValidationRules>
<wpfApplication6:ValidateMe />
</BindingGroup.ValidationRules>
</BindingGroup>
</Grid.BindingGroup>
<TextBox TextChanged="TextBox1_OnTextChanged">
<Binding Path="ColorIndex" UpdateSourceTrigger="PropertyChanged" />
</TextBox>
</Grid>
</Window>
Using RangeAtribute :
private void test()
{
Indices indices = new Indices();
indices.ColorIndex = 20;
var validationContext = new ValidationContext(indices);
var validationResults = new List<System.ComponentModel.DataAnnotations.ValidationResult>();
var tryValidateObject = Validator.TryValidateObject(indices, validationContext, validationResults,true);
}
public class Indices
{
[Range(1, 10)]
public int ColorIndex { get; set; }
public string ColorPrefix { get; set; }
public int GradientIndex { get; set; }
public string GradientPrefix { get; set; }
}
Upvotes: 3