ValidationRule To Enforce Unique Name - wpf

I'm trying to write a custom WPF ValidationRule to enforce that a certain property is unique within the context of a given collection. For example: I am editing a collection of custom objects bound to a ListView and I need to ensure that the Name property of each object in the collection is unique. Does anyone know how to do this?

First, I'd create a simple DependencyObject class to hold your collection:
class YourCollectionType : DependencyObject {
[PROPERTY DEPENDENCY OF ObservableCollection<YourType> NAMED: BoundList]
}
Then, on your ValidationRule-derived class, create a property:
YourCollectionType ListToCheck { get; set; }
Then, in the XAML, do this:
<Binding.ValidationRules>
<YourValidationRule>
<YourValidationRule.ListToCheck>
<YourCollectionType BoundList="{Binding Path=TheCollectionYouWantToCheck}" />
</YourValidationRule.ListToCheck>
</YourValidationRule>
</Binding.ValidationRules>
Then in your validation, look at ListToCheck's BoundList property's collection for the item that you're validating against. If it's in there, obviously return a false validation result. If it's not, return true.

I would only create a custom dependency object if there were other properties I wanted to bind to the rule. Since in this case all we're doing is attaching a single collection of values to check against, I made my <UniqueValueValidationRule.OtherValues> property a <CollectionContainer>.
From there, to get past the problem of the DataContext not being inherited, <TextBox.Resources> needed to have a <CollectionViewSource> to hold the actual binding and give it a {StaticResource} key, which OtherValues could then use as binding source.
The validation rule itself then need only loop through OtherValues.Collection and perform equality checks.
Observe:
<TextBox>
<TextBox.Resources>
<CollectionViewSource x:Key="otherNames" Source="{Binding OtherNames}"/>
</TextBox.Resources>
<TextBox.Text>
<Binding Path="Name">
<Binding.ValidationRules>
<t:UniqueValueValidationRule>
<t:UniqueValueValidationRule.OtherValues>
<CollectionContainer Collection="{Binding Source={StaticResource otherNames}}"/>
</t:UniqueValueValidationRule.OtherValues>
</t:UniqueValueValidationRule>
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>

Related

Can you use a Binding ValidationRule within 1 line in xaml?

I don't know the correct wording to describe what I'm trying to do here... so I'll just show it.
This xaml I know works:
<TextBox>
<TextBox.Text>
<Binding Path="Location" UpdateSourceTrigger="PropertyChanged">
<Binding.ValidationRules>
<domain:NotEmptyValidationRule ValidatesOnTargetUpdated="True"/>
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
But this is pretty verbose. I would like to do it in a way similar to this...
<TextBox Text={Binding Path=Location, UpdateSourceTrigger=PropertyChanged,
ValidationRules={domain:NotEmptyValidationRuleMarkup ValidateOnTargetUpdated=True}}"/>
I made a class called NotEmptyValidationRuleMarkup that returns an instance of NotEmptyValidationRule, and it sort-of works. Project builds just fine, it runs just fine, everything works exactly as I expect it to. However, I can no longer view my window in the designer. It gives me an Invalid Markup error because The property "ValidationRules" does not have an accessible setter.. And it's true, ValidationRules does not have a setter. If I try to set ValidationRules through code in C# I get a compile error. But for some reason when I assign it in XAML it actually does build and run just fine. I'm confused. Is there a way I can make this work without jacking up the design view of my window?
Even though the xaml interpreter happens to turn the markup extension into something working, this is not really supported.
See MSDN - Binding Markup Extension
The following are properties of Binding that cannot be set using the Binding markup extension/{Binding} expression form.
...
ValidationRules: the property takes a generic collection of ValidationRule objects. This could be expressed as a property element in a Binding object element, but has no readily available attribute-parsing technique for usage in a Binding expression. See reference topic for ValidationRules.
However, let me suggest a different approach: instead of nesting the custom markup extension in the binding, nest the binding in a custom markup extension:
[ContentProperty("Binding")]
[MarkupExtensionReturnType(typeof(object))]
public class BindingEnhancementMarkup : MarkupExtension
{
public BindingEnhancementMarkup()
{
}
public BindingEnhancementMarkup(Binding binding)
{
Binding = binding;
}
[ConstructorArgument("binding")]
public Binding Binding { get; set; }
public override object ProvideValue(IServiceProvider serviceProvider)
{
Binding.ValidationRules.Add(new NotEmptyValidationRule());
return Binding.ProvideValue(serviceProvider);
}
}
And use as follows:
<TextBox Text="{local:BindingEnhancementMarkup {Binding Path=Location, UpdateSourceTrigger=PropertyChanged}}"/>
Ofcourse, for production you may want to add a few more checks in the markup extension instead of just assuming everything is in place.

Binding on a Non-UIElement

I am having problems with Binding. Since RelativeSource needs the visual tree to travel up and find the desired Ancestor, you are only allowed to use it on an UIElement but I am trying to do a RelativeSource binding on an Non-UIElement, such as is a ValidationRule, which as you all know isnt inside the VisualTree nor its UIElement. As you can expect the binding breaks. RelativeSource couldn't be found because like i said there is no VisualTree or LogicalTree available. I need to make it work though.
Here is an example of XAML:
<StackPanel DataContext{Binding}>
<Grid>
<ContentControl Content{Binding MVPart1>
<TextBox>
<TextBox.Text>
<Binding Path="VMPart1Property1">
<Binding.ValidationRules>
<my:MyValidationRule>
<my:ValidationRule.DOC>
<my:DepObjClass DepProp={Binding Path=DataContext, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type StackPanel}}}/>
</my:ValidationRule.DOC>
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
</ContentControl>
</Grid>
</StackPanel>
So basically MyValidationRule is derivering from ValidationRule class, but thats not UIElement nor DependencyObject and therefore I had to create a class which derivates from DependencyObject called DepObjClass to be able to write down the xaml binding expression.
Here is code:
public class MyValidationRule : ValidationRule
{
public DepObjClass DOC
{
get;
set;
}
public override ValidationResult Validate(object value, System.Globalization.CultureInfo cultureInfo)
{
string text = value as string;
if (!string.IsNullOrEmpty(text))
{
return new ValidationResult(true, string.Empty);
}
return new ValidationResult(false, "Not working blahhh");
}
}
public class DepObjClass : DependencyObject
{
public object DepProp
{
get
{
return (object)GetValue(DepPropProperty);
}
set
{
SetValue(DepPropProperty, value);
}
}
public static DependencyProperty DepPropProperty
= DependencyProperty.Register(typeof(object), typeof(DepObjClass)......);
}
Now to sum up. MyValidatonRule is not UIElement its not DependencyObject but it has a property of a type that is, hence why the xaml binding expression compiles.
When I run the application the binding itself isnt working because StackPanel couldnt be found because ValidationRule doesnt have VisualTree nor my validation rule participates in Logical or Visual Tree.
The question is how do I make such case work, how to find StackPanel from an Non-UIElement such as my ValidationRule?
I appologize for my code not comipiling but I hope you can understand what I am trying to do.
I am giving 50 points to you guys for the right answer.
You can do the following:
Create a helper component which derives from Freezable and defines a DependencyProperty for what you want to bind.
Create a ValidationRule with a property which takes an object of the helper component, similar to what you have done already.
Declare an instance of the helper component in the Resources of an object which can bind to whatever you want to bind. Freezable and its derived classes inherit the binding context (the location in the logical tree) of any control in whose Resources they are declared, so there you can create your binding.
When declaring the ValidationRule, use {StaticResource} to assign the helper component to the property in the ValidationRule. StaticResource works without a binding context, as long as the resource is declared before it is used.
The XAML would look like this:
<StackPanel>
<StackPanel.Resources>
<my:Helper x:Key="helper" ValProperty="{Binding}"/>
</StackPanel.Resources>
<Grid>
<TextBox DataContext="{Binding MVPart1}">
<TextBox.Text>
<Binding Path="VMPart1Property1">
<Binding.ValidationRules>
<my:MyValidationRule Helper="{StaticResource helper}"/>
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
</Grid>
</StackPanel>

wpf validation rules problem with textbox

I created a class IntegersValidationRule which inherits from ValidationRule. Now I don't know what code should I write in XAML. That's what I have:
<TextBox Name="defaultTxt"
Height="23" Width="200">
<TextBox.Text>
<Binding UpdateSourceTrigger="PropertyChanged">
<Binding.ValidationRules>
<what:IntegersValidationRule/>
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
I know that either I'm so stupid that I can't understand in many tutorials what Path in Binding property means, why should we use Binding here when there's no binding required and what should I use instead of 'what' word inside Binding.ValidationRule.
what is an xmlns (see MSDN) which needs to point to the namespace in which your validation rule class is declared, e.g.
xmlns:what="clr-namespace:MyApp.MyValidationRules"
If you add no Path (- how about reading this if you do not understand it? -) the binding will bind to the current DataContext, whatever that may be in your case.
Question 1: validators work on bindings. That is why you specify the rule on a binding. As soon as then value will be updated to the source (object that the control binds to) the rule is checked.
Question 2: See H.B. 's answer

Set of a Property is not being fired when ValidationRule returns false

I have a ValidationRule on a Text Box. When the ValidationResult returns true, this fires the set on the property that the text box is bound to.
When the ValidationResult returns false, the set is not fired.
Any pointers as to why its not firing, and how to solve it, greatly appreciated.
Thanks
Joe
Here is the XAML of the Text Box :
<Binding Path="CorrectEntry" UpdateSourceTrigger="PropertyChanged" NotifyOnValidationError="True" >
<Binding.ValidationRules>
<localValidation:CorrectEntryValidationRule x:Name="validator" ValidatesOnTargetUpdated="True"> <localValidation:CorrectEntryValidationRule.RangeContainer>
<localValidation:CorrectEntryRangeContainer
DataContext="{Binding
Source={StaticResource DataContextBridge},
Path=DataContext}"
Min="{Binding Lower}"
Max="{Binding Upper}"
/>
</localValidation:CorrectEntryValidationRule.RangeContainer>
</localValidation:CorrectEntryValidationRule>
</Binding.ValidationRules>
</Binding>
See the documentation for Validation Rule. it is by design that the property don't get set when Validation Rule returns false.
You should not use Validation Rule, if you want your Propery to be set. You should inherit your class from IDataErrorInfo. And implement the 2 methods from that interface.
This is the normal and usually wanted behavior, if your data is invalid you don't want it to be saved in your model.
(If you want to do validation via exceptions thrown in the setter you can use a property on the binding)

WPF/XAML: ExceptionValidationRule different when applying in code vs markup?

I've run across the need to apply the ExceptionValidationRule to many textboxes on a form in WPF. I can do this with markup and I get the desired result (the textbox gets a red outline when an invalid value is entered) but only when I supply the rule in markup:
<TextBox x:Name="Name" Width="150" >
<TextBox.Text>
<Binding Path="Name" Mode="TwoWay" UpdateSourceTrigger="PropertyChanged" NotifyOnValidationError="True">
<Binding.ValidationRules>
<ExceptionValidationRule />
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
But when I apply the value using code:
Name.GetBindingExpression(TextBox.TextProperty).ParentBinding.ValidationRules.Add(new ExceptionValidationRule());
I don't get the desired results. This code is applied in a userControl's constructor after the InitalizeComponent() call. The user control has the textbox "Name" as a child control.
I've gone through and I can see, when using both, that two validation rules are put in the ValidationRules collection but when I am using just the code version I don't get the desired result of a red outline around the textbox when an invalid value is entered.
Am I just misunderstanding a fundamental rule to WPF?
Or, is there a way I can apply this validation rule using a Style? I'd prefer that, to tell you the truth.
Thanks,
M
You can't change a Binding after it has been used, and apperently that goes for the ValidationRules as well. You can create a new Binding in code but that's probably not what you're after.
Binding binding = new Binding("Name");
binding.Mode = BindingMode.TwoWay;
binding.UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged;
binding.NotifyOnValidationError = true;
binding.ValidationRules.Add(new ExceptionValidationRule());
nameTextBox.SetBinding(TextBox.TextProperty, binding);
A Style won't work either since a Binding or ValidationRule doesn't derive from FrameworkElement. What I would do in your situation is a subclassed Binding where you add all the things you need. Something like this
<TextBox x:Name="Name" Width="150" >
<TextBox.Text>
<local:ExBinding Path="Name"
Mode="TwoWay"
UpdateSourceTrigger="PropertyChanged"/>
</TextBox.Text>
</TextBox>
The ExBinding, adding ValidationRule etc.
public class ExBinding : Binding
{
public ExBinding()
: base()
{
NotifyOnValidationError = true;
ValidationRules.Add(new ExceptionValidationRule());
}
}

Resources