How To Validation This Class?(WPF)
I can not understand is the Property Value for each.
For this method : public override ValidationResult Validate(object value.
name maximum char must be 10;
age maximum value must be 150;
public class Person : ValidationRule
{
string _Name;
public string Name
{
get
{
return _Name;
}
set
{
_Name = value;
}
}
int _age = 20;
public int Age
{
get { return _age; }
set { _age = value; }
}
string _Phone = "000-0000";
public string Phone
{
get { return _Phone; }
set { _Phone = value; }
}
public override ValidationResult Validate(object value, System.Globalization.CultureInfo cultureInfo)
{
**//is value Which Property?**
//I can not understand is the Property Value for each
return new ValidationResult(true, null);
}
}
You can't make your class derive from ValidationRule: it's a person, not a rule.
First, I don't recommend that WPF developers use validation rules at all. Use MVVM, and have your view model implement IDataErrorInfo as described (for instance) here.
If you want to create a single ValidationRule class to validate your Person class, you can, but you'll need to create a PropertyName property on the class and set it in your XAML, e.g.:
<TextBox>
<TextBox.Text>
<Binding Path="Age"
Mode="TwoWay">
<Binding.ValidationRules>
<local:PersonValidationRule PropertyName="Age"/>
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
Then the Validate method in this class can look at the PropertyName and branch accordingly. Of course, now you've implemented a new point of failure - what happens if you put down the wrong property name in your XAML? If you use data-error validation, that can't happen.
Related
In my WPF MVVM app I have a TextBox which has bound a validation rule.
In validation rule class I have below property:
public bool CanBeValidated { get; set; } = false;
Then in the view my TextBox has below validation rule bound (I only put the relevant part):
<TextBox.Text>
<Binding Path="myPath"
UpdateSourceTrigger="PropertyChanged"
ValidatesOnDataErrors="True">
<Binding.ValidationRules>
<vRules:RootPathValidationRule
ValidatesOnTargetUpdated="True"
CanBeValidated="{Binding Path=ValidationEnabled}"/>
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
In my view model the property is defined as below:
public bool ValidationEnabled
{
get { return _isValidationEnabled; }
set { this._isValidationEnabled = value; OnPropertyChanged(); }
}
So I receive below compilation error:
A 'Binding' cannot be set on the 'CanBeValidated' property of type
'MyPathValidatorRule'. A 'Binding' can only be set on a
DependencyProperty of a DependencyObject.
For first time when TextBox is loaded I want to avoid validation rule to fire until user edits it and avoid throwing a validation error since TextBox is empty.
Once user edits the TextBox, I would like to enable validation rule by performing a simple this.ValidationEnabled = true from view model.
How can I achieve this without using dependency properties? Is it possible?
You could create a wrapper class that derives from DependencyObject and exposes a dependency property. Then you add a CLR property to the ValidationRule class that returns an instance of this wrapper type:
public class RootPathValidationRule : ValidationRule
{
public Wrapper Wrapper { get; set; }
public override ValidationResult Validate(object value, CultureInfo cultureInfo)
{
bool canBeValidated = Wrapper?.CanBeValidated == true;
...
}
}
public class Wrapper : DependencyObject
{
public static readonly DependencyProperty CanBeValidatedProperty =
DependencyProperty.Register(nameof(CanBeValidated), typeof(bool),
typeof(Wrapper));
public bool CanBeValidated
{
get { return (bool)GetValue(CanBeValidatedProperty); }
set { SetValue(CanBeValidatedProperty, value); }
}
}
Finally, you'll also need a binding proxy object that captures the DataContext where the source property is defined:
public class BindingProxy : System.Windows.Freezable
{
protected override Freezable CreateInstanceCore()
{
return new BindingProxy();
}
public object Data
{
get { return (object)GetValue(DataProperty); }
set { SetValue(DataProperty, value); }
}
public static readonly DependencyProperty DataProperty =
DependencyProperty.Register("Data", typeof(object), typeof(BindingProxy), new PropertyMetadata(null));
}
XAML:
<TextBox>
<TextBox.Resources>
<vRules:BindingProxy x:Key="proxy" Data="{Binding}"/>
</TextBox.Resources>
<TextBox.Text>
<Binding Path="myPath"
UpdateSourceTrigger="PropertyChanged"
ValidatesOnDataErrors="True">
<Binding.ValidationRules>
<vRules:RootPathValidationRule ValidatesOnTargetUpdated="True">
<vRules:RootPathValidationRule.Wrapper>
<vRules:Wrapper CanBeValidated="{Binding Data.ValidationEnabled,
Source={StaticResource proxy}}"/>
</vRules:RootPathValidationRule.Wrapper>
</vRules:RootPathValidationRule>
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
Please refer to this article for details.
This is my GridViewColumn:
<GridViewColumn Width="180" Header="Status">
<GridViewColumn.CellTemplate>
<DataTemplate>
<TextBlock x:Name="Txt" Text="{Binding Status}" Foreground="Yellow" />
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
The Status field is a property of my binding object and all i want to do is change this GridViewColumn color but this time base on condition:
I have another propgerty called StatusMessage which is simple enum:
public enum StatusMessage
{
InProcess,
Done,
Wait
}
So this enum property is changing all the time and for every value of this enum i want to define different color.
Is it possible ?
Edit
My View model class inherit from BaseObservableObject:
public class BaseObservableObject : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
var handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
protected virtual void OnPropertyChanged<T>(Expression<Func<T>> raiser)
{
var propName = ((MemberExpression)raiser.Body).Member.Name;
OnPropertyChanged(propName);
}
protected bool Set<T>(ref T field, T value, [CallerMemberName] string name = null)
{
if (!EqualityComparer<T>.Default.Equals(field, value))
{
field = value;
OnPropertyChanged(name);
return true;
}
return false;
}
}
My properties:
public string Status
{
get { return _status; }
set
{
_status = value;
OnPropertyChanged();
}
}
public StatusMsg StatusMessage
{
get { return _statusMsg; }
set {
_statusMsg = value;
OnPropertyChanged();
}
}
XAML:
<GridViewColumn Width="180" Header="Status" DisplayMemberBinding="{Binding Status}">
<GridViewColumn.CellTemplate>
<DataTemplate>
<TextBlock Foreground="{Binding StatusMsg,Converter={c:StatusMessageToColorConverter}}" />
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
StatusMessageToColorConverter is the same as #grek40 suggested and still my TextBlock Foreground not changing.
I first focus on the value conversion, then I say something about the other requirement ("So this enum property is changing all the time and for every value of this enum i want to define different color.")
Since you have an enum value but you want to have a color specification, you need to convert the value. This can be done with the help of an implementation of the IConverter interface. There are different ways to reference a converter in XAML, I additionally inherit from MarkupExtension so I am able to access the converter directly.
public class StatusMessageToColorConverter : MarkupExtension, IValueConverter
{
// one way converter from enum StatusMessage to color
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (value is StatusMessage && targetType == typeof(Brush))
{
switch ((StatusMessage)value)
{
case StatusMessage.InProcess:
return Brushes.Yellow;
case StatusMessage.Done:
return Brushes.Green;
case StatusMessage.Wait:
return Brushes.Red;
}
}
return null;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
public StatusMessageToColorConverter()
{
}
// MarkupExtension implementation
public override object ProvideValue(IServiceProvider serviceProvider)
{
return this;
}
}
As you can see, I convert if the input value is of type StatusMessage and the target type is typeof(Brush), which is the Type of the Foreground property. I just chose some colors, you may use different colors or even more complex brushes if you like.
In XAML, the namespace of the converter needs to be referenced. In my case its just WpfApplication2 associated to the XAML namespace name c
<Window
[... other properties]
xmlns:c="clr-namespace:WpfApplication2">
Now, when binding to the foreground property, utilize the converter
<TextBlock Text="{Binding Status}" Foreground="{Binding StatusMessage,Converter={c:StatusMessageToColorConverter}}" />
This should do the trick.
Now to the other part about dynamically changing the value. Your viewmodel class needs to implement INotifyPropertyChanged and raise the PropertyChanged event whenever a value is changed. As an example, see the following class that only contains the two properties of your example and the necessary notification logic.
public class ItemModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
void NotifyPropertyChanged([CallerMemberName]string prop = null)
{
var tmp = PropertyChanged;
if (tmp != null) tmp(this, new PropertyChangedEventArgs(prop));
}
private string _Status;
public string Status
{
get { return _Status; }
set { _Status = value; NotifyPropertyChanged(); }
}
private StatusMessage _StatusMessage;
public StatusMessage StatusMessage
{
get { return _StatusMessage; }
set { _StatusMessage = value; NotifyPropertyChanged(); }
}
}
More complex viewmodels can follow the same approach. For less update overhead, compare the current value and the new value in the setter and only notify if the value actually changes.
In your view model you could add a property called GetColour that looks at the current enum value and returns a colour. Then just bind the GetColour property in your xaml.
I am trying to bind the value of an element to a first resource , if present, then another one otherwise.
In other words, if the resources look like
<s:String x:Key="first">Hello<s:String>
<s:String x:Key="second">World<s:String>
my element's value would hold Hello
But if the resources have only
<s:String x:Key="second">World<s:String>
the value would be World
I have tried a number of solution but none seems to work or is elegant enough.
I wish I could write
<MyElement>
<MyElement.Value><MultiBinding Converter=...><DynamicResource Key=First/><DynamicResource Key=Second/> ...
where the converter takes care of finding the first non null value.
However, WPF does not allow mixing DynamicResource and MultiBinding
Do you have a solution?
Edit 1: I may have read your question a little too fast... you're binding to dynamic resources... not class properties... so the solution below is probably not what your looking for. But I'll leave it for now in case it helps you come up a solution.
Edit 2: I tried the following code in Visual Studio 2010 SP1 and it works like it should in the designer (commentout/uncomment the resource 'first'). But it fails to successfully build... which I find strange:
<TextBlock>
<TextBlock.Resources>
<!--<s:String x:Key="first">Hello</s:String>-->
<s:String x:Key="second">World</s:String>
<l:NullItemConverter x:Key="NullItemConverter" />
</TextBlock.Resources>
<TextBlock.Text>
<MultiBinding Converter="{StaticResource NullItemConverter}">
<Binding Source="{DynamicResource first}" />
<Binding Source="{DynamicResource second}"/>
</MultiBinding>
</TextBlock.Text>
</TextBlock>
public class NullItemConverter : IMultiValueConverter
{
object IMultiValueConverter.Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return values[0] ?? values[1];
}
object[] IMultiValueConverter.ConvertBack(object value, Type[] targetTypes, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
Original Answer (that doesn't answer your question but may help depending on your situation):
Assuming your two properties are in the same class, could you make a third property that smartly outputs the correct value and bind to that instead:
public class MyObject : INotifyPropertyChanged
{
private string property1;
public string Property1
{
get { return this.property1; }
set
{
if (this.property1 != value)
{
this.property1 = value;
NotifyPropertyChanged("Property1");
NotifyPropertyChanged("PropertyForBinding");
}
}
}
private string property2;
public string Property2
{
get { return this.property2; }
set
{
if (this.property2 != value)
{
this.property2 = value;
NotifyPropertyChanged("Property2");
NotifyPropertyChanged("PropertyForBinding");
}
}
}
public string PropertyForBinding
{
get
{
return this.Property1 ?? this.Property2;
}
}
public MyObject() { }
#region -- INotifyPropertyChanged Contract --
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(String info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
#endregion INotifyPropertyChanged Contract
}
I want to bind the attached property or dependency property in xaml for the ValidationRule in xaml and then based on the value of the attached property or dependency property I want to make sum decision in the Validation rule. I can't find any solution
how can I Pass bindable value to the Validation Rule.
I supply you a sample code to help you. I have defined a ValidationRule to validate a texbox user input. The type of validation is performed according value of one enum parameter. Type of validation available are: user input cannot be empty, user input must be numeric, user input must be an IP address. A second parameter allows to specificy warning message displayed. As you know a variable for binding purposes should be a DependendyProperty, so here you find class with paramaters declaration.
public class ValidationParams : DependencyObject
{
// Dependency Properties
public static readonly DependencyProperty MessageProperty = DependencyProperty.Register("Message",
typeof(string),
typeof(ValidationParams),
new FrameworkPropertyMetadata(string.Empty));
public static readonly DependencyProperty ValidationTypeProperty = DependencyProperty.Register("ValidationType",
typeof(FieldValidationRule.EnmValidationType),
typeof(ValidationParams),
new FrameworkPropertyMetadata(FieldValidationRule.EnmValidationType.FieldNotEmpty));
// Properties
[Category("Message")]
public string Message
{
get { return (string)GetValue(MessageProperty); }
set { SetValue(MessageProperty, value); }
}
[Category("ValidationType")]
public FieldValidationRule.EnmValidationType ValidationType
{
get { return (FieldValidationRule.EnmValidationType)GetValue(ValidationTypeProperty); }
set { SetValue(ValidationTypeProperty, value); }
}
Then here is the validationrule class:
public class FieldValidationRule : ValidationRule
{
public enum EnmValidationType
{
FieldNotEmpty,
FieldNumeric,
FieldIPAddress
}
// Local variables and objects
private ValidationParams mParams = new ValidationParams();
public ValidationParams Params
{
get { return mParams; }
set { mParams = value; }
}
// Override
public override ValidationResult Validate(object value, System.Globalization.CultureInfo cultureInfo)
{
ValidationResult objResult = null;
string sValue = value as string;
objResult = new ValidationResult(true, null);
switch (Params.ValidationType)
{
case EnmValidationType.FieldNotEmpty:
if(string.IsNullOrEmpty(sValue) == true)
objResult = new ValidationResult(false, Params.Message);
break;
case EnmValidationType.FieldNumeric:
int iValue = 0;
if(int.TryParse(sValue, out iValue) == false)
objResult = new ValidationResult(false, Params.Message);
break;
case EnmValidationType.FieldIPAddress:
IPAddress objValue = IPMatrix.CreateHostAddr();
if(IPAddress.TryParse(sValue, out objValue) == false)
objResult = new ValidationResult(false, Params.Message);
break;
}
return objResult;
}
}
And finally here is the XAML code:
<TextBox Style="{DynamicResource FieldValue}" Grid.Column="1" IsReadOnly="False">
<TextBox.Text>
<Binding Source="{StaticResource XmlItemChannel}" XPath="#Name" Mode="TwoWay" UpdateSourceTrigger="LostFocus">
<Binding.ValidationRules>
<data:FieldValidationRule>
<data:FieldValidationRule.Params>
<data:ValidationParams Message="{DynamicResource ERR002}" ValidationType="FieldNotEmpty" />
</data:FieldValidationRule.Params>
</data:FieldValidationRule>
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
You can see that parameter Message is binded to a resource, but you can classically bind it too.
Quick question.
I have a validator configured in WPF that checks to ensure that a value is within a specific range. This works great. See code below:
<TextBox Name="valueTxt" Style="{StaticResource SquareBox}" GotKeyboardFocus="amountTxt_GotKeyboardFocus" GotMouseCapture="amountTxt_GotMouseCapture" LostFocus="boxLostFocus" Height="25" Width="50">
<TextBox.Text>
<Binding Path="UnitCost" NotifyOnValidationError="True">
<Binding.ValidationRules>
<local:ValidDecimal MaxAmount="1000"></local:ValidDecimal>
</Binding.ValidationRules>
<Binding.Converter>
<local:CurrencyConverter addPound="False" />
</Binding.Converter>
</Binding>
</TextBox.Text>
</TextBox>
However, I would like to pass the validator another piece of data from the data that is being bound. I assumed that I could just add this to the delcaration of the validator like so:
<local:ValidDecimal MaxAmount="1000" SKU="{Binding Path=tblProducts.ProductSKU}"></local:ValidDecimal>
However, is appears that I can't access the SKU value in this way.
Any suggestions?
Thanks,
EDIT
May well be worth pointing out that SKU is simply a string declared in my validator, like so:
public class ValidDecimal : ValidationRule
{
public int MaxAmount { get; set; }
public string SKU { get; set; }
public override ValidationResult Validate(object value, System.Globalization.CultureInfo cultureInfo)
{
//validate the value as a decimal in to two decimal places
string cost = (string)value;
try
{
decimal decCost = decimal.Parse(cost);
if (SKU != "85555")
{
if (decCost <= 0 || decCost > MaxAmount)
{
return new ValidationResult(false, "Amount is not within valid range");
}
}
return new ValidationResult(true, null);
}
catch (Exception ex)
{
return new ValidationResult(false, "Not a valid decimal value");
}
}
}