I want to validate some Text in a TextBlock
TextBlock xaml:
<TextBlock x:Name="numInput" Validation.ErrorTemplate="{StaticResource errorTemplate}" >
<TextBlock.Text>
<Binding Path="Text" RelativeSource="{RelativeSource self}" NotifyOnValidationError="True">
<Binding.ValidationRules>
<local: NumberValidator />
</Binding.ValidationRules>
</Binding>
</TextBlock.Text>
</TextBlock>
The Text is added in codebehind by some button clicks in the GUI (i.e. a touch screen)
errorTemplate
<ControlTemplate x:Key="errorTemplate">
<StackPanel>
<TextBlock Foreground="Red">error msg</TextBlock>
<AdornedElementPlaceholder/>
</StackPanel>
</ControlTemplate>
NumberValidator
class NumberValidator : ValidationRule {
public override ValidationResult Validate(object value, System.Globalization.CultureInfo cultureInfo) {
Console.WriteLine("validating numbers!!");
int num = -1;
try {
num = Int32.Parse(value.ToString());
}
catch (Exception e) {
return new ValidationResult(false, "input must be numbers!");
}
if (num > 999 || num < 1) {
return new ValidationResult(false, string.Format("must be integers from {0} to {1}", 1, 999));
}
return new ValidationResult(true, null);
}
}
Questions:
No error message is shown. In fact, NumberValidator isn't even called. Why?
How to validate the error only when a Button is clicked?
How to pass valid range (i.e min, max) information to the NumberValidator?
Thanks!
I assume that you want to perform validation in source-to-target direction (provide visual feedback for model errors), therefore my answer only applies if this is the case.
Validation rules are by design only checked in target-to-source direction (the main idea here is to validate user input), so when you change the value on the model, validation rules are not checked. In order to perform validation in source-to-target direction, your model should implement either IDataErrorInfo or INotifyDataErrorInfo (the latter being supported only in .NET 4.5 or newer), and ValidatesOnDataErrors should be set to true on the binding.
The validation occurs whenever binding is updated, so if the button click updates the property on the model (or, more specifically, raises PropertyChanged event), the validation will be performed. Note that if property is changed on some other occasion, the validation will also be performed, so in order to perform the validation only on button click make sure the property is changed (or PropertyChanged is raised) only then.
Despite using ValidationRule derivatives is not appropriate approach in assumed scenario, the answer is to define Max and Min properties on the NumberValidator class, and then use them in XAML like so: <local:NumberValidator Min="0" Max="100"/>.
For more information on bindings see Data Binding Overview.
Related
I'm rather new to WPF and XAML, and I'm attempting to create a custom class that extends TextBox, so that I can add some properties for easier validation setup. The validation works fine. The problem is, when I swap out the TextBox in the XAML, the new TextBox updates the border to display an error, but the tool-tip does not show up as expected (see below).
In the picture below, the top 's error tool tip displays correctly, but the 's tool-tip doesn't.
Here's the XAML...
<TextBox x:Name="StrTextBox3" Width="200" Height="50">
<TextBox.Text>
<Binding Path="BinaryIntText" UpdateSourceTrigger="PropertyChanged">
<Binding.ValidationRules>
<validationRules:StrValidationRule ValidatorType="{x:Static validators:StringValidator.BinaryOnly_Validator}"/>
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
<validationRules:ValidatedTextBox x:Name="VText" Width="200" Height="50">
<TextBox.Text>
<Binding Path="BinaryIntText2" UpdateSourceTrigger="PropertyChanged">
<Binding.ValidationRules>
<validationRules:StrValidationRule ValidatorType="{x:Static validators:StringValidator.BinaryOnly_Validator}"/>
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</validationRules:ValidatedTextBox>
And here's the extending class...
class ValidatedTextBox : TextBox
{
public ValidatedTextBox()
{
//
}
}
And the ValidationRule for good measure...
public override ValidationResult Validate(object value, CultureInfo cultureInfo)
{
return ValidatorType.Validate(Convert.ToString(value)).Match (
Right: result => new ValidationResult(true, null),
Left: error => new ValidationResult(false, error));
}
How do I go about enabling this functionality? Have I not initialized a particular field or two? Am I missing calls to something else? Am I plagued by styles, or some other straight-forward thing that is a quicker fix than doing this write up?
Thank you
Turns out I'm dense; by simply extending the TextBox, the default Style for TextBoxes was not applied, and setting the new Style to an existing TextBox's Style, or just setting in at all, fixed the issue.
I'm using ValidationRules in my xaml forms like this
<TextBox Name="email">
<TextBox.Text>
<Binding Path="email" UpdateSourceTrigger="PropertyChanged">
<Binding.ValidationRules>
<local:NotEmptyString ValidationStep="RawProposedValue" />
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
In code, before my transaction begins i check for errors like this
email.GetBindingExpression(TextBox.TextProperty).UpdateSource();
if (Validation.GetHasError(item))
return false;
I have classes that inherit ValidationRule for every validation i need, and this works fine.
But now, i need to call a post method and that method returns me an JSON error when the email already exists, I want to show that error as a validation error. is there a way to set the error to the TextBox?
If you want to stick to this quite unflexible binding validation, then the simplest solution would be to use Binding.ValidatesOnException:
<TextBox Name="email">
<TextBox.Text>
<Binding Path="email"
UpdateSourceTrigger="PropertyChanged"
ValidatesOnException="True">
<Binding.ValidationRules>
<local:NotEmptyString ValidationStep="RawProposedValue" />
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
Then validate your JSON response:
class ViewModel
{
public string Email { get; set; }
private ValidateJsonResponse(Json jsonObject)
{
if (!JsonIsValid(jsonObject))
{
throw new ArgumentException("Email already exists.");
}
}
}
But since exceptions can have some impact on the performance, you generally should avoid them. You can argue, if failed input validation is a good reason to throw an exception. I doubt it is.
Alternatively implement a second ValidationRule and trigger the binding to execute it. You must find a way to access the JSON response to check whether it is valid or not. Since the ValidationRule is instantiated in the XAML and can't have a DependencyProperty to allow binding, this could be slightly complicated. That's why binding validation is not very flexible. The ValidationRule instance of the binding is quite isolated from the rest of the code e.g., the view model.
The recommended pattern is to have your view model implement he INotifyDataErrorInfo interface. See this post for an example or links regarding the implementation: How to add validation to view model properties or how to implement INotifyDataErrorInfo
I got a text box on which I added a validation rule in the xaml. The rule works and it detects errors, but only after the user gives the focus to some other element in the window, like another text box.
This is the defintion:
<TextBox x:Name="textBoxLongitude" Grid.Row="1" Grid.Column="1" Margin="0,0,0,10" VerticalContentAlignment="Center">
<TextBox.Text>
<Binding Path="CustomerLongitude">
<Binding.ValidationRules>
<local:IsDoubleValidationRule MaxValue ="180"/>
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
I've tried solving the issue by adding this to the xaml of the text box:
TextChanged="textBoxTextChanged"
And the implementation:
private void textBoxTextChanged(object sender, TextChangedEventArgs e)
{
CommandManager.InvalidateRequerySuggested();
}
It didn't help..
How can I make the validation rule detect the error and when it's fixed even without the user needing to give the focus to another control?
Set the binding's UpdateSourceTrigger to PropertyChanged to commit and re-evaluate a bound value on every change instead of LostFocus.
Bindings that are TwoWay or OneWayToSource listen for changes in the target property and propagate them back to the source. This is known as updating the source. Usually, these updates happen whenever the target property changes. This is fine for check boxes and other simple controls, but it is usually not appropriate for text fields. Updating after every keystroke can diminish performance and it denies the user the usual opportunity to backspace and fix typing errors before committing to the new value. Therefore, the default UpdateSourceTrigger value of the Text property is LostFocus and not PropertyChanged.
This is how to do it: <Binding Path="CustomerLongitude" UpdateSourceTrigger="PropertyChanged">
I'm learning this wpf stuff and trying to get my head around validation of controls. Specifically what I'm looking for is this...
A form can have 100 controls on it (exaggerating, but possible). The form's layout and flow are a specific order (via tabbed sequence for user). A user may never get to some "required" fields and click on a "Save" button. How can I trigger it so all the controls force triggering their own respective "Validation" events.
Based on above, does the WPF framework process the validation rules in the tab order the user is looking at. If not, how can that be controlled to match the data entry flow instead of bouncing around in the sequential order the application happens to create objects and their respective validation rules.
Is there a way to have ALL failed controls triggered for the default behavior of putting a red border box around the failed control instead of only one at a time.
Thanks
Typically, to accomplish what you are looking for you use an MVVM type pattern. This means that you bind each control that collects data in your WPF form to a backing field or property. You add validation to the binding, with a style that will cause the red border box. For controls with required data, part of the validation is that they are filled in. You could define a single validation rule for this called "ValidWhenHasData" or some such.
To cause the validations to trigger only when you press "save" or the like, there are a number of ways you can do this. I typically make a property in each validation rule called "IsEnabled" and set it to false by default; if set to false, the validation rule always returns valid. I then add a list in the code-behind of the controls that I want to validate. When "save" is clicked, I go through the list and set all the validation rules' IsEnabled to true, clear all errors on the controls in the list, and then refresh the binding on each. This will display the red rectangles on any that are not filled in or whatever else you have defined as an error condition. You can also use this list to set focus to the first control that failed validation, in the order you choose.
Example validation control template, which includes placeholder for validation error tooltip:
<ControlTemplate x:Key="errorTemplate">
<Canvas Width="{Binding Path=AdornedElement.ActualWidth, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type Adorner}}}" Height="{Binding Path=AdornedElement.ActualHeight, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type Adorner}}}">
<Border BorderBrush="Red" BorderThickness="1">
<AdornedElementPlaceholder/>
</Border>
<Border Canvas.Top="-5" Canvas.Right="-5" BorderBrush="Gray" BorderThickness="1" >
<TextBlock x:Name="errorBlock" TextAlignment="Center" Background="Red" Foreground="White" Width="10" Height="10" FontSize="9" ctl:valTooltip.MessageBody="{Binding Path=AdornedElement.(Validation.Errors)[0].ErrorContent,RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type Adorner}}}">*</TextBlock>
</Border>
</Canvas>
</ControlTemplate>
Example validation binding:
<TextBox x:Name="TBNumItems" Margin="2,2,2,2" MinWidth="40" HorizontalAlignment="Left" Validation.ErrorTemplate="{StaticResource errorTemplate}">
<TextBox.Text>
<Binding x:Name="NumItemsBinding" Path="NumItems" NotifyOnSourceUpdated="True" UpdateSourceTrigger="PropertyChanged">
<Binding.ValidationRules>
<cal:UIntValidationRule x:Name="NumItemsValidationRule" MinValue="1" MaxValue="99999" IsEnabled="False"/>
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
Example code behind for validation:
/// <summary>
/// Clears all validation errors
/// </summary>
void ClearAllValidationErrors()
{
Validation.ClearInvalid(TBNumItems.GetBindingExpression(TextBox.TextProperty));
}
/// <summary>
/// Revalidates everything
/// </summary>
void RevalidateAll()
{
ClearAllValidationErrors();
TBNumItems.GetBindingExpression(TextBox.TextProperty).UpdateSource();
}
Make your data object implement IDataErrorInfo, which will perform a validation check on a property when the user changes it, then use the following style to apply the red border to controls that have a validation error:
<!-- ValidatingControl Style -->
<Style TargetType="{x:Type FrameworkElement}" x:Key="ValidatingControl">
<Style.Triggers>
<Trigger Property="Validation.HasError" Value="True">
<Setter Property="ToolTip" Value="{Binding
Path=(Validation.Errors)[0].ErrorContent,
RelativeSource={x:Static RelativeSource.Self}}" />
</Trigger>
</Style.Triggers>
</Style>
This will
Only perform a validation check for a single property when that property gets changed
Only (and always) show the red validation error border on controls that are bound to an invalid property
Edit
Here's a sample of a how I would implement validation on an object:
public class MyObject : ValidatingObject
{
public MyObject()
{
// Add Properties to Validate here
this.ValidatedProperties.Add("SomeNumber");
}
// Implement validation rules here
public override string GetValidationError(string propertyName)
{
if (ValidatedProperties.IndexOf(propertyName) < 0)
{
return null;
}
string s = null;
switch (propertyName)
{
case "SomeNumber":
if (SomeNumber <= 0)
s = "SomeNumber must be greater than 0";
break;
}
return s;
}
}
And my ValidatingObject base class which implements IDataErrorInfo usually contains the following:
#region IDataErrorInfo & Validation Members
/// <summary>
/// List of Property Names that should be validated
/// </summary>
protected List<string> ValidatedProperties = new List<string>();
public abstract string GetValidationError(string propertyName);
string IDataErrorInfo.Error { get { return null; } }
string IDataErrorInfo.this[string propertyName]
{
get { return this.GetValidationError(propertyName); }
}
public bool IsValid
{
get
{
return (GetValidationError() == null);
}
}
public string GetValidationError()
{
string error = null;
if (ValidatedProperties != null)
{
foreach (string s in ValidatedProperties)
{
error = GetValidationError(s);
if (error != null)
{
return error;
}
}
}
return error;
}
#endregion // IDataErrorInfo & Validation Members
I faced the same problem. I wanted controls who know if they are required and report automatically any change to the hosting Window. I didn't want to have to write complicated XAML or other code, just placing the control and setting a property to indicate if user input is required. The control searches then automatically the host window and informs it when the user keys in required data or deletes it, so the window can change the state of the Save button. The final solution looks like this:
<wwl:CheckedWindow x:Class="Samples.SampleWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:wwl="clr-namespace:WpfWindowsLib;assembly=WpfWindowsLib">
<StackPanel>
<wwl:CheckedTextBox x:Name="TestCheckedTextBox" MinWidth="100" MaxLength="20" IsRequired="True"/>
<Button x:Name="SaveButton" Content="_Save"/>
</StackPanel>
</wwl:CheckedWindow>
For this to work added to the most common WPF controls an IChecker, which does the following:
know when the data has changed
know when the data has been unchanged (user undid his change)
know when a "required" control is lacking data
know when a "required" control has data
find automatically the window the control is in and inform the Window about each state change
If the window gets informed by any control that the control's state has changed, the window then queries automatically all other controls about their state. If all required controls have data, the Save Button gets enabled.
Knowing that the user has changed some data it is useful, when the user tries to close the window without saving . He gets then automatically a warning and gives him the choice if he wants to save or discard the data.
I had to write too much code to be posted here, but now life is very easy. Just place the control into the XAML and add few lines of code to the Window for the Save Button, that's all. Here is an detailed explanation: https://www.codeproject.com/Articles/5257393/Base-WPF-Window-functionality-for-data-entry
The code is on GitHub: https://github.com/PeterHuberSg/WpfWindowsLib
I'm using WPF and I've got an Entity bound to a series of controls. The entity is decorated with two class level validators as follows:
[ExampleValidator1, ExampleValidator2]
public class Entity
An Entity has a series of fields of which not all are always shown, dependent on the selection from combo box.
A validator exists for each of these selections, if the "type" of entity does not match a particular validator that validator returns true and obviously the correct validator will validate the actual fields as follows:
public bool IsValid(object value, IConstraintValidatorContext constraintValidatorContext)
{
constraintValidatorContext.DisableDefaultError();
var allPropertiesValid = true;
var entity= (Entity)value;
if (supplier.ParticularEntityType)
{
return true;
}
if (String.IsNullOrEmpty(entity.Name) || entity.Name.Length > 50)
{
constraintValidatorContext.AddInvalid<Entity, string>("must be 50 or shorter and not empty", x => x.Name);
allPropertiesValid = false;
}
and the XAML is as follows:
<TextBox Grid.Row="0" Grid.Column="3">
<TextBox.Text>
<Binding Path="Entity.Name" UpdateSourceTrigger="PropertyChanged" ValidatesOnDataErrors="True">
</Binding>
</TextBox.Text>
</TextBox>
Obviously I get the nice pretty red box and tool tips informing users of the validation requirements.
My issue is that when the selection in the combobox is changed, the red highlighting persists (becomes a small red square when a control is hidden).
Could someone direct me the right way please!
Solved by firing an OnPropertyChanged when the combobox is altered, not an ideal solution but its workable.