Binding ValidationRules in one line? - wpf

I have several one-line binding already written and I'd like to keep it that way if possible and if it still is humanly readable.
Is there any way to rewrite this
<TextBox.Text>
<Binding Path="SomePath" NotifyOnValidationError="True" >
<Binding.ValidationRules>
<local:ValidationRule1></local:ValidationRule1>
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
in one line?, like
<TextBox Text="{Binding Path=SomePath, [ValidationRule1...]}" />

I think there is no one-liner and besides the standard version is more readable.

There is no one-liner out of the box, but it's perfectly possible to make one yourself by taking advantage of markup extensions. More specifically, create a custom method of defining bindings for typical scenarios in your project.
A useful thing to note is that Binding class is itself a markup extension and thus provides an implementation of ProvideValue. It means that a custom binding markup extensions can just create or take a binding, fill out its values as needed and provide the value by the newly modified binding.
For example, if you commonly use a single validation rule and want to keep it in a one-liner, you may want to create an extension like this:
using System;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Markup;
namespace MyProject.Markup
{
public class SingleValidationBindingExtension : MarkupExtension
{
private Binding _binding;
public SingleValidationBindingExtension(Binding binding, ValidationRule validationRule)
{
_binding = binding;
_binding.ValidationRules.Add(validationRule);
}
public override object ProvideValue(IServiceProvider serviceProvider)
{
return _binding.ProvideValue(serviceProvider);
}
}
}
Then, somewhere in your XAML:
<UserControl xmlns:ext="clr-namespace:MyProject.Markup;assembly=MyProject"
...>
<UserControl.Resources>
<local:ValidationRule1 x:Key="ValidationRule1"/>
</UserControl.Resources>
<!-- Note that you can customize other binding properties within the inner {Binding ...} markup -->
<TextBox Text="{ext:SingleValidationBinding {Binding SomePath}, {StaticResource ValidationRule1}}" />
</UserControl>
VoilĂ ! Now it took only one line to apply the validation rule to the binding.
Granted, having an extension specifically for a single-validation bindings may seem wasteful. However, with this simple proof-of-concept it's easy to expand upon the idea of custom binding markups. For example, you may group commonly used combinations of converters and validations rules into objects, define them as static resources and then apply them to bindings using a custom binding definition.

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.

I need an attached property to monitor parent for INotifyDataErrrorInfo

If I have the following binding
<TextBox Text="{Binding XXX.Name, ValidatesOnNotifyDataErrors=True}"/>
it doesn't work because only the DataContext implements INotifyDataErrorInfo and raises "XXX.Name" errors but ValidatesOnNotifyDataErrors tries to monitor XXX for error events not the data context.
However I am sure somebody could figure out how to write an attached property to do the following
<TextBox Grid.Column="5" Text="{Binding Binding.Name, c:ValidatesOnNotifyDataErrorsOnDataContext=True}"/>
where the data context is monitored not the child. Anybody got an idea how to start with that?
I think this is possible to implement, but because of the flexibility of bindings (RelativeSource, MultiBindings and whatnot) it would be difficult to make something like this that is truly robust. Personally, I think it would be cleaner to to implement INotifyDataErrorInfo at every level of the structure (and for parts of the structure that you don't own, like your Point example, use proxy classes that mirror the properties).
Anyway, Binding is a MarkupExtension, not a DependencyObject, which means attached properties can't be applied to it. You could inherit Binding to add your own properties, but this isn't very useful since it doesn't give you any overridable methods.
It shouldn't be necessary to extend Binding though, since all you want is a custom ValidationRule. Setting ValidatesOnNotifyDataErrors=True is equivalent to adding a NotifyDataErrorValidationRule:
<TextBox>
<TextBox.Text>
<Binding Path="XXX.Name">
<Binding.ValidationRules>
<NotifyDataErrorValidationRule />
</Binding.ValidationRules>
</Binding>
</TextBox>
</TextBox>
So you just need to replace NotifyDataErrorValidationRule with your own rule. If you override this Validate overload (which is passed the binding expression), you should be able to access the full binding path (through ParentBinding) and look up an error.

How to access converter from code in XAML?

I have a converter in the code behind called StringToIntConverter I try using it in xaml binding like this where s is the project namespace:
Converter={s:StringToIntConverter}
But it says that it is missing an assembly reference. What am I doing wrong?
I know there is some way to put it as a resource and then reference the resource but I am not sure how to do it.
<Some.Resources>
<s:StringToIntConverter x:Key="StringToIntConverter"/>
</Some.Resources>
<!-- ... -->
Converter={StaticResource StringToIntConverter}
Curly braces indicate a markup extension, they cannot just be used arbitrarily to instantiate objects, but for convenience you could turn your converter into a markup extension.
Something like:
public class StringToIntConverter : MarkupExtension, IValueConverter
{
//...
public override object ProvideValue(IServiceProvider serviceProvider)
{
return this;
}
}
Then the code you used would work just fine!
Also note that you could use the binding in XML-element syntax to instantiate converters in place as well, e.g.
<TextBox>
<TextBox.Text>
<Binding Path="String">
<Binding.Converter>
<s:StringToIntConverter />
</Binding.Converter>
</Binding>
</TextBox.Text>
</TextBox>
Uhm - normally it goes something like this if I understand your question. You have created a converter right? In you XAML you need to add a reference to the assembly like this.
xmlns:converters="clr-namespace:Shared.Converters;assembly=Shared"
even if it is in the same assembly - something like...
xmlns:local="clr-namespace:ItemMaster"
Now you need to create a staticResource for whatever converter you want to use.
<converters:CostMethodToBooleanConverter x:Key="CostMethodToBooleanConverter"/>
Then you can use it.
IsEnabled="{Binding SelectedItem, Converter={StaticResource ReverseCostMethodToBooleanConverter}, ElementName=OemOriginalCostMethod}"/>
Does that help?

Add ValidationRules into a single xaml line OR shorthand ValidationRules

I'm using a PasswordBox which exposes a dependency property such that I can bind to it. The problem is that by using it like so, I cannot shorthand the Binding.ValidationRules to this syntax:
<PasswordBox services:RPLPasswordBoxBinder.BindPassword="True"
services:RPLPasswordBoxBinder.BoundPassword="{Binding Path=LoginUser.Parola, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}">
</PasswordBox>
I set my ValidationRules to a textbox like this:
<TextBox.Text>
<Binding Path="LoginUser.Parola" UpdateSourceTrigger="PropertyChanged">
<Binding.ValidationRules>
<some validation rule/>
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
Is there any way to specify the ValidationRules collection to my PasswordBox in a single xaml line? Or maybe there's another clever solution for validating user input into my password box?
Some clarifications:
I'm using MVVM and I don't want to use code behind.
I want to add only a single ValidationRule. Maybe the problem with shorthanding Binding.ValidationRules is that this property is a collection. One validationrule would suffice in my situation.
There's a similar question on stackoverflow here. My problem is different as I don't just want to increase readability but actually validate my PasswordBox.
I suggest that you base your data model class on IDataErrorInfo and then validation is performed there and not in the code behind.
There are plenty of examples, but here's one for starters and another here.

ValidationRules within control template

I have control thats inherits from textbox
public class MyTextBox : TextBox
This has a style
<Style TargetType="{x:Type Controls:MyTextBox}">
one of the Setters is
<Setter Property="Template">
I want to be able to set the Binding.ValidationRules to something in the template, thus affecting all instances of this type of textbox.
I can therefore make textboxes for say Times, Dates, Numerics, post/zip codes.. or whatever i want,
I don't want to have to set the validation rules every time i create a textbox. I just want to say i want a NumericTextBox and have it validate in whatever way is set in the template.
Is this possible?
All i have seen so far is the ValidationRules being set on each instance of the control e.g.
<TextBox x:Name="txtEMail" Template={StaticResource TextBoxErrorTemplate}>
<TextBox.Text>
<Binding Path="EMail" UpdateSourceTrigger="PropertyChanged" >
<Binding.ValidationRules>
<local:RegexValidationRule Pattern="{StaticResource emailRegex}"/>
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
(from http://www.wpftutorial.net/DataValidation.html)
As you see, validation rules are set along with bindings. I came across the same problem and the working solution for me was to do something like this:
public MyTextBox()
{
this.Loaded += new RoutedEventHandler(MyTextBox_Loaded);
}
void MyTextBox_Loaded(object sender, RoutedEventArgs e)
{
var binding = BindingOperations.GetBinding(this, TextBox.ValueProperty);
binding.ValidationRules.Add(new MyValidationRule());
}
The problem here is to be sure that the binding is set before we add the validation rule, hence the use of Loaded, but i'm not sure if this will work on every scenario.
I am not sure it is possible to do exactly what you want. You might be able to do it with a lot of trickery in property setters (I wouldn't rely on this, since it would involve modifying bindings, which are by definition dynamic), or with code behind/custom controls.
Instead I suggest you push your validation into your ViewModel, probably with IDataErrorInfo. There are many articles out there. Here's one of the first ones I found with a search just now for "MVVM validation":
MVVM - Validation
Doing this will allow you to use standard OO composition techniques, so you can avoid repeating yourself :)

Resources