I am new to WPF. The form's data context includes a StartTime and an EndTime field (using MVVM), which I have successfully bound to their own text boxes. I am trying to create a validation to check that a new user-entered StartTime is before the EndTime value. The following code does not seem to bind the EndTime field to the validation parameter Maximum.
XAML:
<TextBox>
<TextBox.Text>
<Binding Path="StartTime" UpdateSourceTrigger="LostFocus" StringFormat="{}{0:hh}:{0:mm}">
<Binding.ValidationRules>
<local:ValidateTime>
<local:ValidateTime.Maximum>
<local:ValidationParameter Parameter="{Binding EndTime, StringFormat=hh\\:mm}" />
</local:ValidateTime.Maximum>
</local:ValidateTime>
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
View model:
public class ValidationParameter : DependencyObject
{
public static readonly DependencyProperty ParameterProperty = DependencyProperty.Register(
"Parameter",
typeof(string),
typeof(ValidationParameter),
new FrameworkPropertyMetadata(null));
public string Parameter
{
get { return (string)GetValue(ParameterProperty); }
set { SetValue(ParameterProperty, value); }
}
}
public class ValidateTime : ValidationRule
{
private TimeSpan _Minimum = new TimeSpan(0, 0, 0);
private TimeSpan _Maximum = new TimeSpan(23, 59, 9);
private ValidationParameter _MinimumProperty;
private ValidationParameter _MaximumProperty;
public ValidationParameter Minimum
{
get
{
return _MinimumProperty;
}
set
{
TimeSpan ts;
if (TimeSpan.TryParse(value.Parameter, out ts))
{
_Minimum = ts;
_MinimumProperty = value;
}
}
}
public ValidationParameter Maximum
{
get
{
return _MaximumProperty;
}
set
{
TimeSpan ts;
if (TimeSpan.TryParse(value.Parameter, out ts))
{
_Maximum = ts;
_MaximumProperty = value;
}
}
}
public override ValidationResult Validate(object value, System.Globalization.CultureInfo cultureInfo)
{
string formattedValue = value.ToString();
if (Regex.IsMatch(formattedValue, #"^\d{4}$"))
{
formattedValue = string.Format("{0}:{1}", formattedValue.Substring(0, 2), formattedValue.Substring(2, 2));
}
TimeSpan convertedValue;
if (TimeSpan.TryParseExact(formattedValue, "g", System.Globalization.CultureInfo.CurrentCulture, out convertedValue))
{
if (convertedValue > _Maximum)
{
return new ValidationResult(false, string.Format("Time must be before {0}.", _Maximum.ToString("g")));
}
else if (convertedValue < _Minimum)
{
return new ValidationResult(false, string.Format("Time must be after {0}.", _Minimum.ToString("g")));
}
return ValidationResult.ValidResult;
}
else
{
return new ValidationResult(false, string.Format("'{0}' is not a valid time entry.", value.ToString()));
}
}
}
The code works if I set the parameter to a static value like the following, but I need this validation to be dynamic:
<local:ValidateTime.Maximum>
<local:ValidationParameter Parameter="12:00" />
</local:ValidateTime.Maximum>
First you want to give your TextBox a name like so:
<TextBox Name="StartTime"/>
Then you set the parameter for validation like so:
<local:ValidateTime.Maximum>
<local:ValidationParameter Parameter="{Binding ElementName=StartTime, Path=Text" />
</local:ValidateTime.Maximum>
If the validation works like you said you should be all set.
You can do the same thing with your EndTime TextBox if you want as well assuming you write a working rule for it
You probably need to work around the fact that ValidationRules are not in the visual or logical trees, which causes bindings to DataContext, ElementName and RelativeSource to fail.
In my case
<local:RangeValidation ValidationStep="UpdatedValue"/>
helped. So you pass the DependencyObject as value(.dataitem) including your Datacontext and have no need to pass a parameter to the Validation.
BR,
Daniel
BTW credits not on mee, just found it but can't remember who gave the hint :)
Related
I am new to the WPF MVVM and wanted to ask a follow up question to this article:
Enable Disable save button during Validation using IDataErrorInfo
I am trying to enable/disable the button save/update if any of the many controls on the form validation failed/passed.
I have the IsValid method, that checks the validation logic on the Model and returns True/False, that will be passed on to the DelegateCommand as a predicate.
The question is: my button has the following property IsEnabled{binding IsValid}, this should check all fields to make sure that it matches the criteria in the model, returns true/false to the view model and then enables the button if all true. The problem is: Once the view model is instantiated, the DelegateCommand object is created with the validation (IsValid) at at a false state and it stays that way throughout the life of the object even though the user is filling data in the textboxes. How do I turn on the button once all the conditions are met? in other words, how to constantly keep validating and updating the IsValid in order to switch the button on once every textbox validate to true??
Thanks,
I have the following code:
The Model
public class UserModel : ObservePropertyChanged,IDataErrorInfo
{
private string name;
public string Name
{
get { return name; }
set { name = value; OnPropertyChanged("Name"); }
}
// if there is an error throw an exception
public string Error
{
get { throw new NotImplementedException(); }
}
public string this[string columnName]
{
get
{
string result = null;
if (columnName == "Name")
{
if (string.IsNullOrEmpty(Name))
result = "Please enter a Name";
}
return result;
}
}
// the Josh Way
static readonly string[] ValidatedProperties =
{
"Name"
};
public bool IsValid
{
get
{
foreach (string property in ValidatedProperties)
{
if (GetValidationError(property) != null) // there is an error
return false;
}
return true;
}
}
// a method that checks validation error
private string GetValidationError(string propertyName)
{
string error = null;
switch (propertyName)
{
case "Name":
error = this.ValidateName();
break;
default:
error = null;
throw new Exception("Unexpected property being validated on Service");
}
return error;
}
private string ValidateName()
{
string ErrorMsg = null;
if (string.IsNullOrWhiteSpace(Name))
{
ErrorMsg = "Name can't be empty!";
};
return ErrorMsg;
}
}
** the View Model **
public class UserViewModel:ObservePropertyChanged
{
UserModel model;
public UserViewModel()
{
presentCommand = new DelegateCommand(param => PresentDataMethod(), param => CanSave);
model = new UserModel();
}
private string name;
public string Name
{
get { return name; }
set { name = value; OnPropertyChanged("Name"); }
}
private string info;
public string Info
{
get { return info; }
set { info = value; OnPropertyChanged("Info"); }
}
private DelegateCommand presentCommand;
public DelegateCommand PresentCommand
{
get
{
if (presentCommand==null)
{
presentCommand = new DelegateCommand(param => PresentDataMethod(), param => CanSave);
}
return presentCommand;
}
}
private void PresentDataMethod()
{
Info = $"Your Name is: {Name}.";
}
// The ViewModel then contains a CanSave Property that reads the IsValid property on the Model:
protected bool CanSave
{
get
{
return model.IsValid;
}
}
}
** The View**
<TextBox x:Name="Name" HorizontalAlignment="Left" Height="34" Margin="285,145,0,0" TextWrapping="Wrap"
VerticalAlignment="Top" Width="248">
<Binding Path="Name"
ValidatesOnDataErrors="True"
UpdateSourceTrigger="PropertyChanged"
Mode="TwoWay">
</Binding>
</TextBox>
<Button Content="Present" FontSize="20" HorizontalAlignment="Left"
Margin="285,184,0,0" VerticalAlignment="Top" Width="248" Height="35"
Command="{Binding Path=PresentCommand}"
IsEnabled="{Binding IsValid}"
>
</Button>
If all you want to do is refresh the button via the IsValid value, all you have to do is listen for any of the OTHER property changes in your ViewModel, and when that happens, tell it to refresh the IsValid binding as well (which is actually your CanSave property). Here is one way to do that:
** The View Model **
// ...
public UserViewModel()
{
// ...
this.PropertyChanged += OnViewModelPropertyChanged;
}
public void OnViewModelPropertyChanged(object sender, PropertyEventArgs e)
{
// On any property that implements "OnPropertyChanged(propname)", refresh the CanSave binding too!
OnPropertyChanged(nameof(this.CanSave));
}
// ...
BTW, it is generally good coding practice to avoid magic words like OnPropertyChanged("Name") or OnPropertyChanged("Info") because you never know when a developer will have to rename their properties, and if that happens, you won't get a compile error here and it might be hard to debug. It is best practice to use the nameof, such as OnPropertyChanged(nameof(Name)) so that you'll know you'll get a compilation error if you ever decide to change the property to, say, FirstName instead of Name.
I have applied the validations of required field validation on the wpf textbox, using the ValidationRule class. My code is :
public class RequiredField : ValidationRule
{
private String _errorMessage = String.Empty;
public string ErrorMessage
{
get { return _errorMessage; }
set { _errorMessage = value; }
}
public override ValidationResult Validate(object value, CultureInfo cultureInfo)
{
var str = value as string;
if (String.IsNullOrEmpty(str))
{
return new ValidationResult(false, this.ErrorMessage);
}
return new ValidationResult(true, null);
}
}
And the XAML code is below :
<TextBox Name="txtName" MaxLength="50">
<TextBox.Text>
<Binding Path="Name" UpdateSourceTrigger="PropertyChanged">
<Binding.ValidationRules>
<myValidtaion:RequiredField ErrorMessage="Please enter Name." />
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
This seems to work fine. But the issue is that after i save the valid data in database and clear the database, this validation is fired again and validation message appears again.
How can I avoid this situation ?
This seems to be a common validation 'problem', but it is in fact the expected behviour of WPF validation... this also occurs when using the IDataErrorInfo interface.
Both of these validation methods will validate their specified properties whenever their values change, which is exactly what we want (most of the time).
I believe that the system is called predictive validation and it enables us to show the user what they have to fill in before they try to save. I personally believe that this is a better system than the old system of letting the user try to save fields with errors and then telling them afterwards that they have made an error.
Perhaps you can delay clearing the fields until the user clicks a New button, in which case the errors will only show up then?
UPDATE >>>
If you were to use the IDataErrorInfo interface, then you could add a bool IsValidating property to your data type classes that you could use to switch validation on and off. I don't have much time, so I found a post that implements this interface that I have adapted to demonstrate my point.
public class Person : IDataErrorInfo
{
private int age;
private bool isValidating = true;
public int Age
{
get { return age; }
set { age = value; }
}
public bool IsValidating
{
get { return isValidating; }
set { isValidating = value; }
}
public string Error
{
get
{
return this["Age"];
}
}
public string this[string name]
{
get
{
string result = null;
if (IsValidating)
{
if (name == "Age")
{
if (this.age < 0 || this.age > 150)
{
result = "Age must not be less than 0 or greater than 150.";
}
}
}
return result;
}
}
}
Adapted from the code in the accepted answer to Creating WPF Validation error manually post
When you switch this IsValidating property to false, the property changes won't be validated:
person.IsValidating = false;
I have read a lot of Blog post on WPF Validation and on DataAnnotations. I was wondering if there is a clean way to use DataAnnotations as ValidationRules for my entity.
So instead of having this (Source) :
<Binding Path="Age" Source="{StaticResource ods}" ... >
<Binding.ValidationRules>
<c:AgeRangeRule Min="21" Max="130"/>
</Binding.ValidationRules>
</Binding>
Where you must have your
public class AgeRangeRule : ValidationRule
{...}
I want the WPF Binding to go see the Age property and look for DataAnnotation a bit like this:
[Range(1, 120)]
public int Age
{
get { return _age; }
set
{
_age = value;
RaisePropertyChanged<...>(x => x.Age);
}
}
Any ideas if this is possible ?
The closest approach I found is :
// This loop into all DataAnnotations and return all errors strings
protected string ValidateProperty(object value, string propertyName)
{
var info = this.GetType().GetProperty(propertyName);
IEnumerable<string> errorInfos =
(from va in info.GetCustomAttributes(true).OfType<ValidationAttribute>()
where !va.IsValid(value)
select va.FormatErrorMessage(string.Empty)).ToList();
if (errorInfos.Count() > 0)
{
return errorInfos.FirstOrDefault<string>();
}
return null;
Source
public class PersonEntity : IDataErrorInfo
{
[StringLength(50, MinimumLength = 1, ErrorMessage = "Error Msg.")]
public string Name
{
get { return _name; }
set
{
_name = value;
PropertyChanged("Name");
}
}
public string this[string propertyName]
{
get
{
if (porpertyName == "Name")
return ValidateProperty(this.Name, propertyName);
}
}
}
Source and Source
That way, the DataAnnotation works fine, I got a minimum to do on the XAML ValidatesOnDataErrors="True" and it's a fine workaround of Aaron post with the DataAnnotation.
In your model you could implement IDataErrorInfo and do something like this...
string IDataErrorInfo.this[string columnName]
{
get
{
if (columnName == "Age")
{
if (Age < 0 ||
Age > 120)
{
return "You must be between 1 - 120";
}
}
return null;
}
}
You will also need to notify the binding target of the newly defined behavior.
<TextBox Text="{Binding Age, ValidatesOnDataErrors=True}" />
EDIT:
If you only want to use Data Annotations you can follow this blog post which outlines how to accomplish the task.
UPDATE:
Historical representation of the aforementioned link.
Sounds good Aaron. I'm just into WPF and will study databindings next week at work ;) So cannot completely judge your answer...
But with winforms I have used Validation Application Block from the Entlib and implemented IDataErrorInfo (actually IDXDataErrorInfo because we work with DevExpress controls) on a base entity (business object) and that works pretty fine!
It's a bit more sophisticated than the solution you sketched in this way that you place your validation logic on the object and not in the interface implementation. Making it more OOP and maintainable. At the ID(XD)ataErrorInfo I just call Validation.Validate(this), or even better get the validator for the property that the interface is called for and validate the specific validator. Don't forget to call the [SelfValidation] as well because of validation for combinations of properties ;)
You might be interested in the BookLibrary sample application of the WPF Application Framework (WAF). It uses the DataAnnotations Validation attributes together with WPF Binding.
Recently I've had the same idea using the Data Annotation API to validate EF Code First POCO classes in WPF. Like Philippe's post my solution uses reflection, but all necessary code is included in a generic validator.
internal class ClientValidationRule : GenericValidationRule<Client> { }
internal class GenericValidationRule<T> : ValidationRule
{
public override ValidationResult Validate(object value, CultureInfo cultureInfo)
{
string result = "";
BindingGroup bindingGroup = (BindingGroup)value;
foreach (var item in bindingGroup.Items.OfType<T>()) {
Type type = typeof(T);
foreach (var pi in type.GetProperties()) {
foreach (var attrib in pi.GetCustomAttributes(false)) {
if (attrib is System.ComponentModel.DataAnnotations.ValidationAttribute) {
var validationAttribute = attrib as System.ComponentModel.DataAnnotations.ValidationAttribute;
var val = bindingGroup.GetValue(item, pi.Name);
if (!validationAttribute.IsValid(val)) {
if (result != "")
result += Environment.NewLine;
if (string.IsNullOrEmpty(validationAttribute.ErrorMessage))
result += string.Format("Validation on {0} failed!", pi.Name);
else
result += validationAttribute.ErrorMessage;
}
}
}
}
}
if (result != "")
return new ValidationResult(false, result);
else
return ValidationResult.ValidResult;
}
}
The code above shows a ClientValidatorRule which is derived from the generic GenericValidationRule class. The Client class is my POCO class which will be validated.
public class Client {
public Client() {
this.ID = Guid.NewGuid();
}
[Key, ScaffoldColumn(false)]
public Guid ID { get; set; }
[Display(Name = "Name")]
[Required(ErrorMessage = "You have to provide a name.")]
public string Name { get; set; }
}
I use custom validation rule to validate my data. But I can't access/determine the property value.
here is my code
public class MandatoryRule: ValidationRule
{
public MandatoryRule()
{
ValidationStep = System.Windows.Controls.ValidationStep.UpdatedValue;
}
public override ValidationResult Validate(object value, System.Globalization.CultureInfo cultureInfo)
{
BindingExpression exp = value as BindingExpression;
if (value == null)
return new ValidationResult(true, null);
return new ValidationResult(true, null);
}
}
I need to set the ValidationStep to UpdatedValue (for further business logic)
Then comes the problem: I don't know what's the property value? Because:
It is a generic validator, can't bound to a specific model
The value in parameter of Validate method is a BindingExpression
So how can I read the real value?
Thanks
At last, I come up with this idea.
Create a class DummyObject : DependencyObject.
Create a public static DependencyProperty DummyProperty.
Then create a new databinding, copy the source, binding path, element name, converter, etc from the (value as BindingExpression).ParentBinding.
Set the new databinding target to the dummyobject.
Then use the binding to UpdateTarget()
And now you can access the value from the dummyproperty.
Had the same issue and came accross this question, Gary's answer seems to be the way to go, but it lacked the source code. So here's my interpretation.
public class BindingExpressionEvaluator : DependencyObject
{
public object Value
{
get { return (object)GetValue(ValueProperty); }
set { SetValue(ValueProperty, value); }
}
public static readonly DependencyProperty ValueProperty =
DependencyProperty.Register("ValueProperty", typeof(object),
typeof(BindingExpressionEvaluator), new UIPropertyMetadata(null));
public static object Evaluate(BindingExpression expression)
{
var evaluator = new BindingExpressionEvaluator();
var binding = new Binding(expression.ParentBinding.Path.Path);
binding.Source = expression.DataItem;
BindingOperations.SetBinding(evaluator, BindingExpressionEvaluator.ValueProperty, binding);
var value = evaluator.Value;
BindingOperations.ClearBinding(evaluator, BindingExpressionEvaluator.ValueProperty);
return value;
}
}
I'm animating a 'race' on a map. The race takes 45 minutes, but the animation runs for 60 seconds.
You can watch the 2008 City2Surf race demo to see what I mean.
The 'race clock' in the top-left must show "real time", and had to be set-up in the .xaml.cs with a System.Windows.Threading.DispatcherTimer which seems a bit of a hack.
I thought maybe there'd be a DependencyProperty on the animation rather than just StoryBoard.GetCurrentTime(), but instead I have had to
// SET UP AND START TIMER, before StoryBoard.Begin()
dt = new System.Windows.Threading.DispatcherTimer();
dt.Interval = new TimeSpan(0, 0, 0, 0, 100); // 0.1 second
dt.Tick +=new EventHandler(dt_Tick);
winTimeRatio = (realWinTime.TotalSeconds * 1.0) / animWinTime.TotalSeconds;
dt.Start();
and then the Tick event handler
void dt_Tick(object sender, EventArgs e)
{
var sb = LayoutRoot.Resources["Timeline"] as Storyboard;
TimeSpan ts = sb.GetCurrentTime();
TimeSpan toDisplay = new TimeSpan(0,0,
Convert.ToInt32(ts.TotalSeconds * winTimeRatio));
RaceTimeText.Text = toDisplay.ToString();
}
This works and seems to perform OK - but my question is: am I missing something in the Silverlight animation/storyboard classes that would do this more neatly? I have to remember to stop the DispatcherTimer too!
Or to put the question another way: any better suggestions on 'animation' of TextBox content (the .Text itself, not the location/dimensions/etc)?
That is one way. It's nice and simple, but a bit messy. You could get rid of the storyboard and on each tick, increment a local value by the tick interval and use that to set your time. You would then only have one time piece.
Or... A more elegant and re-usable way would be to create a helper class that is a DependencyObject. I would also just use a StoryBoard with a DoubleAnimation an bind the Storyboard.Target to an instance of the DoubleTextblockSetter. Set the storyboard Duration to your time and set the value to your time in seconds. Here is the DoublerBlockSetterCode.
public class DoubleTextBlockSetter : DependencyObject
{
private TextBlock textBlock { get; private set; }
private IValueConverter converter { get; private set; }
private object converterParameter { get; private set; }
public DoubleTextBlockSetter(
TextBlock textBlock,
IValueConverter converter,
object converterParameter)
{
this.textBlock = textBlock;
this.converter = converter;
this.converterParameter = converterParameter;
}
#region Value
public static readonly DependencyProperty ValueProperty =
DependencyProperty.Register(
"Value",
typeof(double),
typeof(DoubleTextBlockSetter),
new PropertyMetadata(
new PropertyChangedCallback(
DoubleTextBlockSetter.ValuePropertyChanged
)
)
);
private static void ValuePropertyChanged(
DependencyObject obj,
DependencyPropertyChangedEventArgs args)
{
DoubleTextBlockSetter control = obj as DoubleTextBlockSetter;
if (control != null)
{
control.OnValuePropertyChanged();
}
}
public double Value
{
get { return (double)this.GetValue(DoubleTextBlockSetter.ValueProperty); }
set { base.SetValue(DoubleTextBlockSetter.ValueProperty, value); }
}
protected virtual void OnValuePropertyChanged()
{
this.textBlock.Text = this.converter.Convert(
this.Value,
typeof(string),
this.converterParameter,
CultureInfo.CurrentCulture) as string;
}
#endregion
}
Then you might have a format converter:
public class TicksFormatConverter : IValueConverter
{
TimeSpanFormatProvider formatProvider = new TimeSpanFormatProvider();
public object Convert(object value,
Type targetType,
object parameter,
CultureInfo culture)
{
long numericValue = 0;
if (value is int)
{
numericValue = (long)(int)value;
}
else if (value is long)
{
numericValue = (long)value;
}
else if (value is double)
{
numericValue = (long)(double)value;
}
else
throw new ArgumentException("Expecting type of int, long, or double.");
string formatterString = null;
if (parameter != null)
{
formatterString = parameter.ToString();
}
else
{
formatterString = "{0:H:m:ss}";
}
TimeSpan timespan = new TimeSpan(numericValue);
return string.Format(this.formatProvider, formatterString, timespan);
}
public object ConvertBack(
object value,
Type targetType,
object parameter,
CultureInfo culture)
{
throw new NotImplementedException();
}
}
I almost forgot the TimespanFormatProvider. There is no format provider for timespan in Silverlight, so it appears.
public class TimeSpanFormatProvider : IFormatProvider, ICustomFormatter
{
public object GetFormat(Type formatType)
{
if (formatType != typeof(ICustomFormatter))
return null;
return this;
}
public string Format(string format, object arg, IFormatProvider formatProvider)
{
string formattedString;
if (arg is TimeSpan)
{
TimeSpan ts = (TimeSpan)arg;
DateTime dt = DateTime.MinValue.Add(ts);
if (ts < TimeSpan.FromDays(1))
{
format = format.Replace("d.", "");
format = format.Replace("d", "");
}
if (ts < TimeSpan.FromHours(1))
{
format = format.Replace("H:", "");
format = format.Replace("H", "");
format = format.Replace("h:", "");
format = format.Replace("h", "");
}
// Uncomment of you want to minutes to disappear below 60 seconds.
//if (ts < TimeSpan.FromMinutes(1))
//{
// format = format.Replace("m:", "");
// format = format.Replace("m", "");
//}
if (string.IsNullOrEmpty(format))
{
formattedString = string.Empty;
}
else
{
formattedString = dt.ToString(format, formatProvider);
}
}
else
throw new ArgumentNullException();
return formattedString;
}
}
All that stuff is re-usable and should live in your tool box. I pulled it from mine. Then, of course, you wire it all together:
Storyboard sb = new Storyboard();
DoubleAnimation da = new DoubleAnimation();
sb.Children.Add(da);
DoubleTextBlockSetter textBlockSetter = new DoubleTextBlockSetter(
Your_TextBlock,
new TicksFormatConverter(),
"{0:m:ss}"); // DateTime format
Storyboard.SetTarget(da, textBlockSetter);
da.From = Your_RefreshInterval_Secs * TimeSpan.TicksPerSecond;
da.Duration = new Duration(
new TimeSpan(
Your_RefreshInterval_Secs * TimeSpan.TicksPerSecond));
sb.begin();
And that should do the trick. An it's only like a million lines of code. And we haven't even written Hello World just yet...;) I didn't compile that, but I did Copy and Paste the 3 classes directly from my library. I've used them quite a lot. It works great. I also use those classes for other things. The TickFormatConverter comes in handy when data binding. I also have one that does Seconds. Very useful. The DoubleTextblockSetter allows me to animate numbers, which is really fun. Especially when you apply different types of interpolation.
Enjoy.