Custom ValidationRules inside Expander.Header Binding not firing - c#

I am having trouble getting a custom ValidationRule to fire, when it is associated with an Expander.Header binding. In fact, the only place I can seem to get these custom rules to fire is in a DataGrid.RowValidationRules block...
The expander is defined in my Window XAML file like so;
<Expander Style="{StaticResource ValidatedSecondLevelExpanderStyle}">
<Expander.Header>
<Binding Path="Name" Mode="OneWay" ValidatesOnDataErrors="True" NotifyOnValidationError="True">
<Binding.ValidationRules>
<ValidationRules:BoundObjectIsValid />
</Binding.ValidationRules>
</Binding>
</Expander.Header>
</Expander>
The bound property 'Name' is displayed correctly, but the validation rule 'BoundObjectIsValid' does not get invoked. Is this possible, and if so, what am I missing?
I know that I could alternately implement IDataErrorInfo on the bound object, however the object can't sensibly validate itself without some context that is provided by other parts of the system. Refactoring is possible, but I'd love to get the ValidationRules to work first!

Refer to the msdn.
The binding engine checks each ValidationRule that is associated with a binding every time it transfers an input value, which is the binding target property value, to the binding source property.
So here in your case, you don't have an inpurt value being transfered to the source property since your Expander.header is not a control which you can use to input values.
Edit: But there is a property named ValidatesOnTargetUpdated' in the ValidationRule. When setting it to true, the validationrule will be applied when the target property is updated

Related

How do you set the ValidationRules of a DataGridTextColumn via attribute and not nested tags? [duplicate]

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.

WPF Binding inside Text attribute differs from actual Binding tag syntax in MultiBinding

So the issue is that when I normally bind a single text item to the Text in a TextBlock, the syntax is as follows:
<TextBlock ... Text="{Binding Attributes[StatusDateTime]}" /> //WORKS GREAT!
Now, background is that the DataContext for this part of the app is the output of a 3rd party API. "Attributes" is a collection of KeyValuePair, which is a property of the parent object. StatusDateTime is the key for the value I am returning. The syntax above works just great! So the object would look something like: theDataContextObject.Attributes[StatusDateTime].
BUT, if I need to combine multiple attributes into one TextBlock that's when things get hairy. I have no idea how to access the key from an actual Binding tag:
<TextBlock Grid.Row="0" Grid.ColumnSpan="3" Style="{StaticResource popupText}">
<TextBlock.Text>
<MultiBinding StringFormat="{}{0} at {1}">
<Binding Path="{Attributes[Type]}"/>
<!--<Binding Path="StatusDateTime" />-->
<Binding Path=Attributes[Type]/>
<Binding Path="Type" ElementName="Attributes"/> //the element has no
//name it's just the datacontext
<Binding Path="[StatusDateTime]" />
<Binding Path="Attributes[StatusDateTime]"/>
</MultiBinding>
</TextBlock.Text>
I know the number of examples above does not match the stringformat, I was just pasting examples of my guesses into the multibinding of how to get these values bound.
Apologies if this is a duplicate with another question. I am not sure what to even call this kind of binding where I'm just passing Attributes[StatusDateTime] directly to binding. Keep in mind that Attributes is not the name of an object, it's a property of the object passed to datacontext of the TextBlock's parent control.
So how do I bind to the value of key in the KeyValuePair collection when i have to use a Binding tag inside a MultiBinding?

WPF validation without updating the data source

I have a TextBox displaying the time part of a DateTime:
<TextBox HorizontalAlignment="Left" Height="23" Margin="0,13,0,0" TextWrapping="Wrap" VerticalAlignment="Top" Width="120" Validation.Error="Validation_OnError">
<TextBox.Text>
<Binding Path="MyDate" StringFormat="HH:mm" NotifyOnValidationError="True" UpdateSourceTrigger="PropertyChanged">
<Binding.ValidationRules>
<c:TimeValidator></c:TimeValidator>
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
Is it possible to do the validation on property change, and the conversion on lost focus?
I want to have the validation on property changed, but I want to have my data source updated on lost focus. Otherwise, the converter will kick in while the user is editing in the TextBox. This might be a problem if the values is 10:50 and the user deletes the last number, so that the value becomes 10:5. The converter will then convert this to 10:50. This is okay to do on lost focus, but not on property changed. But for the sake of the validator, i want to validate on property change so the user have the red border as long as the entered value is not valid.
Yes! I was just wrestling with this. AFAIK, there is no XAML combination for this--it must be done in the codebehind, and you need a direct reference to the element.
Element.GetBindingExpression(PropertyName).ValidateWithoutUpdate();
You'll probably want to check that GetBindingExpression doesn't return null; this will run any converters you have attached (presumably to supply the converted value to converters with ValidationStep set to ConvertedProposedValue), but will not update the source. And, of course, you'll have to call this in some event, perhaps TextChanged or somesuch. Here is the MSDN documentation for it: https://msdn.microsoft.com/en-us/library/system.windows.data.bindingexpressionbase.validatewithoutupdate(v=vs.110).aspx
Use this code:
BindingExpression expression =
txtStudentName.GetBindingExpression(TextBox.TextProperty);
expression.ValidateWithoutUpdate();
If you want to update it's source after check use this code:
BindingExpression expression =
txtStudentName.GetBindingExpression(TextBox.TextProperty);
expression.ValidateWithoutUpdate();
if (expression!=null && !expression.HasError)
expression.UpdateSource();

Apply value converter in XAML without databinding

I have a TextBlock in my view that I want to always display the number 1, converted into the local currency. (e.g. $1 in US, £1 in UK, etc). I have a value converter that can do this, but I don't know how to apply a value converter to the value of 1 without getting my data from a databinding.
I can think of two solutions, but they each have their problems, and I'm looking for something more elegant:
Create a property on my ViewModel that just holds and returns the value 1 and bind to it. Then add my converter to this binding. This seems backwards, particularly as this is view-only code.
Make a binding point to an existing property and modify my converter to ignore the value given to it, and instead use the parameter to give it the number 1. This feels unintuitive to other programmers, as they'll be confused as to why I'm binding to a different property there.
Is there some way of applying a converter without first creating a binding?
If you want for this to be relatively readable from XAML alone, you can always do it like this:
<Label>
<Label.Resources>
<system:Int32 x:Key="defaultValue">1</system:Int32>
</Label.Resources>
<Label.Content>
<Binding Source="{StaticResource defaultValue}"
Converter="{StaticResource CurrencyConverter}" />
</Label.Content>
</Label>
I had similar situation, I needed to convert static enum value placed directly in xaml to be converted by my custom converter without using databinding.
<Border>
<Border.Background>
<Binding Source="{x:Static enumeration:ColorType.Main}"
Converter="{StaticResource ColorConverter}" />
</Border.Background>
</Border>

Setting Binding Properties in a Template

Is there a way to template Binding.Converter and Binding.ValidationRules within a style?
Eg: I have the following textbox:
<TextBox x:Name="DepartTime" Height="23" HorizontalContentAlignment="Left" HorizontalAlignment="Left"
Margin="3" Width="140"
Style="{DynamicResource TimeOfDayTextBox}">
<TextBox.Text>
<!-- Textbox notifies changes when Text is changed, and not focus. -->
<Binding Path="FlightDepartTime" StringFormat="{}{0:hh:mm tt}" >
<Binding.Converter>
<convert:TimeOfDayConverter />
</Binding.Converter>
<Binding.ValidationRules>
<!-- Validation rule set to run when binding target is updated. -->
<validate:ValidateTimeOfDay ValidatesOnTargetUpdated="True" />
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
.. I can't figure out how to incorporate the Converter and the Validation rule into my TimeOfDayTextBox style.
Many Thanks.
Unfortunately, no. The style could only set the Text property itself to a Binding. It cannot set attributes of the binding. Also, since Binding is not a DependencyObject there is no way to style a binding.
One option you have to make your code more concise is to use a custom MarkupExtension that creates the binding you want:
public class TimeOfDayBinding
: MarkupExtension
{
public PropertyPath Path { get; set; }
public override object ProvideValue(IServiceProvider serviceProvider)
{
var binding = new Binding()
{
Path = Path,
Converter = new TimeOfDayConverter(),
};
binding.ValidationRules.Add(new ValidateTimeOfDay()
{
ValidatesOnTargetUpdated = true,
});
return binding.ProvideValue(serviceProvider);
}
}
Given your control names, you may also want to use a time picker control instead of a TextBox. Check out this question: What is currently the best, free time picker for WPF?
A style can contain only a common set of property which can be applied to multiple controls. In your case, the converter and the validation rule aren't applied to the textbox, but to the content of the binding, so they are specific for a single element and cannot be used in a style.

Categories

Resources