How to validate view controls against data in the viewmodel - c#

I'm new at WPF, and I'm getting my head around validators, it seems that you need to inherit from ValidationRule and override the Validate function, this totally separated from the view model, but what if I want to validate against some list/collection/set/dictionary in the viewmodel, to check whether this new input is not already in the list, a good example would be creating a validation to see whether or not an username is not already taken.

There are several different ways to do Validation in WPF. There's two main ways I can think of off the top of my head
Create Validation Rules then apply them in XAML
Implement IDataErrorInfo in your ViewModel
Validation Rules are specified in your XAML (GUI), while implementing IDataErrorInfo moves the Validation logic into your ViewModel (Business logic). While, ValidationRules are nice because you can create your own and reuse them, they also fail to provide validation in your business logic which is most likely required.
The concept of Client vs. Server side validation is interesting, perhaps as it pertains to Silverlight, but since you tagged this as WPF, I'm assuming the only difference is whether the validation occurs in the Views or ViewModels (UI or Business logic). It seem to me that even if your UI validated inputs, your ViewModels would still need to do proper validation.
Therefore, I suggest implementing IDataErrorInfo. By the way, the reason IDataErrorInfo works, is because a ValidationRule exists that checks for the IDataErrorInfo Interface! Here's an example of how I would do it in my ViewModelBase class:
Note: the immediately following examples ignore the fact you will likely need INotifyPropertyChanged notifications to update your bindings and instead focuses simply on Validation.
public class ViewModelBase : IDataErrorInfo
{
private Dictionary<string, string> errors = new Dictionary<string, string>();
// required for IDataErrorInfo
public virtual string Error
{
get { return String.Join(Environment.NewLine, errors); }
}
// required for IDataErrorInfo
public string this[string propertyName]
{
get
{
string result;
errors.TryGetValue(propertyName, out result);
return result;
}
}
// Useful property to check if you have errors
public bool HasErrors
{
get
{
return errors.Count > 0;
}
}
protected void SetError<T>(string propertyName, String error)
{
if (error == null)
errors.Remove(propertyName);
else
errors[propertyName] = error;
OnHasErrorsChanged();
}
protected string GetError<T>(string propertyName, String error)
{
String s;
errors.TryGetValue(propertyName, out s);
return s;
}
protected virtual void OnHasErrorsChanged()
{
}
}
Then your ViewModels can implement it like this:
public class MyViewModel : ViewModelBase
{
private string someProperty;
public string SomeProperty
{
get
{
return someProperty;
}
set
{
if(someProperty != null)
{
someProperty = value;
SetError("SomeProperty", ValidateSomeProperty());
}
}
}
private string ValidateSomeProperty()
{
if(String.IsNullOrEmpty(SomeProperty))
return "Value is required";
return null;
}
}
In your UI, you'll need to add ValidatesOnDataErrors and NotifyOnValidationError like this:
Text="{Binding SomeProperty, UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True, NotifyOnValidationError=True}"
Note: Passing in strings to represent properties is kinda ugly (it's not refactor safe if you rename the property but forget to rename the string). INotifyPropertyChanged is the same way when you want to notify of property changes for DataBindings. Prism's NotificationObject has a refactor safe solution to this and it looks like this instead:
Replace GetError / SetError in the previous example with this:
protected void SetError<T>(Expression<Func<T>> prop, String error)
{
String propertyName = PropertySupport.ExtractPropertyName(prop);
if (error == null)
errors.Remove(propertyName);
else
errors[propertyName] = error;
OnHasErrorsChanged();
}
protected string GetError<T>(Expression<Func<T>> prop, String error)
{
String propertyName = PropertySupport.ExtractPropertyName(prop);
String s;
errors.TryGetValue(propertyName, out s);
return s;
}
And then my ViewModelBase is something like this:
public class ViewModelBase : NotificationObject, IDataErrorInfo
Then to implement properties:
public class MyViewModel : ViewModelBase
{
private string someProperty;
public string SomeProperty
{
get
{
return someProperty;
}
set
{
if(someProperty != null)
{
someProperty = value;
SetError( () => SomeProperty, ValidateSomeProperty()); // update validation for property
RaisePropertyChanged( () => SomeProperty); // notify data bindings
}
}
}
I didn't show the implementation for RaisePropertyChanged but it is in Prism's NotificationObject which is open source and a free download. You can instead implement INotifyPropertyChanged yourself and either raise the event with strings (not refactor safe) or implement it yourself similar to the above implemention for SetError (extract the property name and fire the event with it).
You'll need this helper method:
public static class PropertySupport
{
public static string ExtractPropertyName<T>(Expression<Func<T>> propertyExpresssion)
{
if (propertyExpresssion == null)
{
throw new ArgumentNullException("propertyExpression");
}
var memberExpression = propertyExpresssion.Body as MemberExpression;
if (memberExpression == null)
{
throw new ArgumentException("The expression is not a member access expression.", "propertyExpression");
}
var property = memberExpression.Member as PropertyInfo;
if (property == null)
{
throw new ArgumentException("The member access expression does not access a property.", "propertyExpression");
}
var getMethod = property.GetGetMethod(true);
if (getMethod.IsStatic)
{
throw new ArgumentException("The referenced property is a static property.", "propertyExpression");
}
return memberExpression.Member.Name;
}
}
Edit: Simplified Refactor Safe Alternative
If you are using .NET 4.5 or later, you can use the CallerMemberAttribute like this example shows for INotifyPropertyChanged and my SetError implementation. Then you wouldn't need to extract the property name yourself via reflection as above (It simplifies it quite a bit).
Sorry to get off track talking about Property Change Notifications, but they go hand in hand if you want your DataBindings and Validation to work!

I Finally did it, this is how :
Xaml:
<Window.Resources>
<l:DataResource x:Key="ValidateFieldMethod" BindingTarget="{Binding IsFieldValid}"/>
</Window.Resources>
<xctk:IntegerUpDown Width="50" Maximum="300" Minimum="0">
<xctk:IntegerUpDown.Value>
<Binding Path="SelectedItem.TargetPosition" Mode="TwoWay">
<Binding.ValidationRules>
<l:CustomValidationRule Validator="{l:DataResourceBinding DataResource={StaticResource ValidateFieldMethod}}" />
</Binding.ValidationRules>
</Binding>
</xctk:IntegerUpDown.Value>
</xctk:IntegerUpDown>
The ValidatorRule:
public delegate bool CheckAgainstDataDelegate(object newValue, string fieldName);
public class CustomValidationRule : ValidationRule
{
public CheckAgainstDataDelegate Validator { get; set; }
public override ValidationResult Validate(object value, CultureInfo cultureInfo, BindingGroup owner)
{
return Validate((object)owner, cultureInfo);
}
public override ValidationResult Validate(object value, CultureInfo cultureInfo, BindingExpressionBase owner)
{
switch (ValidationStep)
{
case ValidationStep.UpdatedValue:
case ValidationStep.CommittedValue:
value = (object)owner;
break;
}
return new ValidationResult(Validator(value, ((BindingExpression) owner).ResolvedSourcePropertyName), null);
}
[Obsolete("Use Validator property of type delegate instead to validate the data",true)]
public override ValidationResult Validate(object value, CultureInfo cultureInfo) { return null; } //not used anymore
}
In the viewModel :
private CheckAgainstDataDelegate _isFieldValid;
public CheckAgainstDataDelegate IsFieldValid
{
get
{
return _isFieldValid
?? (_isFieldValid = delegate (object newValue,string propertyName)
{
switch (propertyName)
{
case "TargetPosition":
var newV = (int) newValue;
return Items.All(e => e.TargetPosition != newV);
default:
throw new Exception("Property Assigned to unknown field");
}
});
}
}
I used the help of http://www.wpfmentor.com/2009/01/how-to-add-binding-to-property-on.html to Bind in the ValidationrRule.
What do you Think?

Related

ViewModel with IDataErrorInfo always calls getter after setting property?

I want to give this question as much context as possible, but, in summary, I'm basically asking two questions:
Does WPF always call the getter after setting a bound property when the setter doesn't throw an exception?
Is it possible to prevent the getter of a bound property from being called after an error has occurred in the setter when the ViewModel implements IDataErrorInfo?
I currently have a Model class that implements validation by throwing an exception from the property setter. Additionally, many of the properties are coupled, so modifying the value of one of them may cause several others to be recalculated. It implements INotifyPropertyChanged to alert outside listeners whenever a recalculation has occurred. It looks something like this:
public class Model : INotifyPropertyChanged
{
private double property1;
public double Property1
{
get { return property1; }
set
{
if (value < 0.0)
throw new Exception("Property1 cannot be less than 0.0.");
property1 = value;
OnPropertyChanged(new PropertyChangedEventArgs("Property1"));
}
}
// ...Everything needed to support INotifyPropertyChanged...
}
Initially, I implemented the ViewModel for this class to act as a thin wrapper around the model properties, providing additional view-specific behaviors whenever an error occurs (flagging invalid data, disabling buttons, etc.):
public class ViewModel : INotifyPropertyChanged
{
private readonly Model model;
public ViewModel()
{
model = new Model();
model.PropertyChanged += (sender, args) => OnPropertyChanged(args);
}
public string Property1
{
get { return model.Property1.ToString(); }
set
{
try
{
model.Property1 = Double.Parse(value);
}
catch (Exception)
{
// Perform any view-specific actions
throw;
}
}
}
// ...Everything needed to support INotifyPropertyChanged
}
Notably, the ViewModel doesn't have any additional backing fields; all of its properties are directly linked to the corresponding properties in the Model, and any PropertyChanged notifications from the Model are passed along by the ViewModel.
But, I've frequently heard that using exceptions for validation can be limiting, and I'm starting to realize the same, specifically as the need for cross-coupled validation rules has increased in this application.
I didn't want to change behaviors of the Model, since it is already being used in several other places, but I went about changing the ViewModel to implement IDataErrorInfo:
public class ViewModel : INotifyPropertyChanged, IDataErrorInfo
{
private readonly Model model;
public ViewModel()
{
model = new Model();
model.PropertyChanged += (sender, args) =>
{
errorList.Remove(args.PropertyName);
OnPropertyChanged(args);
};
}
public string Property1
{
get { return model.Property1.ToString(); }
set
{
try
{
model.Property1 = Double.Parse(value);
}
catch(Exception ex)
{
// Perform any view-specific actions
errorList["Property1"] = ex.Message;
}
}
}
private readonly Dictionary<string, string> errorList = new Dictionary<string, string>();
public string this[string propertyName]
{
get
{
string errorMessage;
if (errorList.TryGetValue(propertyName, out errorMessage))
return errorMessage;
return String.Empty;
}
}
public string Error { get { return String.Empty; } }
// ...Everything needed to support INotifyPropertyChanged
}
However, this caused a drastic unwanted change in behavior. When using exceptions, after the user entered an invalid value, the Validation.ErrorTemplate for the control is displayed and the invalid value remains in the control, thus giving the user the opportunity to correct their mistake. When using IDataErrorInfo, WPF seems to call the property getter after the setter has completed. Since the Model hasn't changed whenever an error occurs, the invalid value is replaced by the previous value. So now I have a control displaying the Validation.ErrorTemplate but with a VALID being value displayed!
It seems crazy to me that WPF would automatically call a property getter without receiving a PropertyChanged notification (after the window has been initialized). And it doesn't attempt to call the getter after an exception is thrown, so why would it do it when IDataErrorInfo is used?
Am I doing something wrong that's causing this behavior? Is there a way to prevent WPF from calling the property getter in the ViewModel after an error has occurred in the setter when the ViewModel implements IDataErrorInfo?
I've tried adding backing fields to the ViewModel to store the invalid value, but due to the fact Model properties can be modified outside of the ViewModel (that's the reason it implements INotifyPropertyChanged in the first place), the solution ends up being quite complex and basically unsustainable in an environment with programmers of varying skill levels.
Here's the approach you want to have for form validation in MVVM
Your model
public class Product:IDataErrorInfo
{
public string ProductName {get;set;}
public string this[string propertyName]
{
get
{
string validationResult = null;
switch (propertyName)
{
case "ProductName":
validationResult = ValidateName();
}
}
}
}
Then in your ViewModel
public string ProductName
{
get { return currentProduct.ProductName; }
set
{
if (currentProduct.ProductName != value)
{
currentProduct.ProductName = value;
base.OnPropertyChanged("ProductName");
}
}
}
As another consideration, when I want to validate numbers (such as your double validation), keep the model as having a double instead of a string
public double? Property1 {get;set;}
then you can do this
<Textbox Name="myDoubleTextbox" >
<Binding ValidatesOnDataErrors="true" Path="Property1" TargetValueNull="" />
/>
so when they type something incorrect into the double box, it sends null to your model and you can check against that.
Although it doesn't answer the questions I asked, my coworker suggested that the desired behavior could be achieved by adding the invalid value to the errorList and modifying the getter in the ViewModel to return the invalid value whenever there is an error.
So the properties in the ViewModel look like this:
public string Property1
{
get
{
ErrorInfo error;
if (errorList.TryGetValue("Property1", out error))
return error.Value;
return model.Property1.ToString();
}
set
{
try
{
model.Property1 = Double.Parse(value);
}
catch (Exception ex)
{
// Perform any view-specific actions
errorList["Property1"] = new ErrorInfo(value, ex.Message);
}
}
}
With the following updates to the IDataErrorInfo methods:
private struct ErrorInfo
{
public readonly string Value;
public readonly string Message;
public ErrorInfo(string value, string message)
{
Value = value;
Message = message;
}
}
private readonly Dictionary<string, ErrorInfo> errorList = new Dictionary<string, ErrorInfo>();
public string this[string propertyName]
{
get
{
ErrorInfo error;
if (errorList.TryGetValue(propertyName, out error))
return error.Message;
return String.Empty;
}
}
public string Error { get { return String.Empty; } }
This allows the PropertyChanged event handler to stay the same, and only requires small changes to the property getters and setters.

Container for properties values

When .NET 4.5 was released i started using such great Attribute as CallerMemberName. It's easier to understand code, developers can write it faster also. It's like a snippet, not only a feature for debug/test purposes.
So I have a question. Is it normal to create and use something like this?
public class PropertyStore
{
Dictionary<string, object> data = new Dictionary<string,object>();
ViewModelBase modelBase;
internal PropertyStore(ViewModelBase _base)
{
modelBase = _base;
}
public void SetValue<T>(T value = default(T), [CallerMemberName] string prop = "")
{
T prev = GetValue<T>(prop);
if ((prev == null && value == null) || (prev != null && prev.Equals(value))) return;
data[prop] = value;
modelBase.OnPropertyChanged(prop);
}
public T GetValue<T>([CallerMemberName] string prop = "")
{
if (!data.ContainsKey(prop))
data[prop] = default(T);
return (T)data[prop];
}
}
Class-helper, that makes other class more readable, and also we have list of our properties without need to use Reflection.
The usage is:
public class SampleClass : ViewModelBase
{
PropertyStore PropertyStore;
public SampleClass ()
{
PropertyStore = new PropertyStore(this);
}
public string Key
{
get { return PropertyStore.GetValue<string>(); }
set { PropertyStore.SetValue(value); }
}
public DateTime Date
{
get { return PropertyStore.GetValue<DateTime>(); }
set { PropertyStore.SetValue(value); }
}
public bool IsSelected
{
get { return PropertyStore.GetValue<bool>(); }
set { PropertyStore.SetValue(value); }
}
}
The class ViewModelBase here simply implements INotifyPropertyChanged interface.
As I understand, this approach is something like Microsoft Dependency Properties, but I don't need all power of DependencyObject class, and I don't want inherit it.
With something like this I can use Binding, because it's enough to implement INotifyPropertyChanged, also we have no fields (as for me, i try to use properties smarter, than using fields directly (however, there is no problem to use Dictionary directly ^_^))
Sorry for my bad English... Not main language and not much practice.
Another Sample (after moving Methods to base class)
public class SampleClass : ViewModelBase
{
public string Key
{
get { return GetValue<string>(); }
set { SetValue(value); }
}
public DateTime Date
{
get { return GetValue<DateTime>(); }
set { SetValue(value); }
}
public bool IsSelected
{
get { return GetValue<bool>(); }
set { SetValue(value); }
}
}
No diff with Microsoft's WPF Property System.
Only feature you'll get with it is an ability to access property values via Dictionary.Get|Set methods.
You can get this ability with field based implementation of INotifyPropertyChanged. You can access property values by its name using dictionary, with property name to precompiled delegate mapping like it done in Yappi project.
var dateValue= Property<SampleClass>.Get<DateTime>(this,"Date");
Property<SampleClass>.Set<DateTime>(this,"Date",DateTime.Now);
Both can be rewritten as extension methods.
Nice idea, property bag without reflection and it will even work with obfuscation.
I don't see major problems with it but you may consider the following:
The prop parameter is optional so potentially a bug can be introduced by given a value in the call.
Value types will get boxed.
Access to the fields is relatively more expensive, can be a factor more expensive as you have much more code in a simple get (especially with boxing).
Dictionary takes more space than the number of properties you keep in (especially with boxing).
Each property also stores a string of the property name adding to the overhead.

How to provide validation for child classes that implement different properties?

I have a parent class implementing INotifyPropertyChanged, and the parent class has multiple children. The children have different properties that all call PropertyChanged. I want to add validation, but I really don't want to have to write validations for every child class. The validation rules are supplied from the database, so I would have to eventually pull the validation rules for each child, then check the value against the rules. If I did that, I think that it would have too much redundant code, and I would like to have it placed at the parent level since PropertyChanged triggers on the string value of the value itself.
Is it possible to have the validation method on the parent class so I wouldn't have to write a validation method for every child class? Mind you, the properties in every child class are different.
Below is what I currently have, with validation in the child class.
public Parent : INotifyChanged {
/// <summary>
/// Occurs when a property is changed
/// </summary>
public event PropertyChangedEventHandler PropertyChanged;
/// <summary>
/// Raises the <see cref="PropertyChanged"/> for a given
/// property.
/// </summary>
/// <param name="propertyName"></param>
protected void OnPropertyChanged(String propertyName) {
// Get the hanlder
PropertyChangedEventHandler handler = this.PropertyChanged;
// Check that the event handler is not null
if(null != handler) {
// Fire the event
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
Child1 Class:
public Child1 : Parent, IDataErrorInfo {
private Dictionary<string, string> m_validationErrors = new Dictionary<string, string>();
private void Validate() {
this.RemoveError("Child1Description");
if(!Regex.IsMatch(Child1Description, "^([a-zA-Z '-]+)$") && !String.IsNullOrWhiteSpace(Description)) {
this.AddError("Child1Description", "Only non-numerics allowed.");
}
}
private void AddError(string columnName, string msg) {
if(!m_validationErrors.ContainsKey(columnName)) {
m_validationErrors.Add(columnName, msg);
}
}
private void RemoveError(string columnName) {
if(m_validationErrors.ContainsKey(columnName)) {
m_validationErrors.Remove(columnName);
}
}
public string Error {
get {
if(m_validationErrors.Count > 0) {
return "Field data is invalid.";
}
else return null;
}
}
public string this[string columnName] {
get {
if(m_validationErrors.ContainsKey(columnName)) {
return m_validationErrors[columnName];
}
else {
return null;
}
}
}
/// <summary>
/// Description of the air entity
/// </summary>
public string Child1Description {
get {
return Child1description;
}
set {
description = value;
Validate();
OnPropertyChanged("Child1Description");
}
}
}
Child2 Class:
public Child2 : Parent, IDataErrorInfo {
private Dictionary<string, string> m_validationErrors = new Dictionary<string, string>();
private void Validate() {
this.RemoveError("Child2Description");
if(!Regex.IsMatch(Child2Description, "^([a-zA-Z '-]+)$") && !String.IsNullOrWhiteSpace(Description)) {
this.AddError("Child2Description", "Only non-numerics allowed.");
}
}
private void AddError(string columnName, string msg) {
if(!m_validationErrors.ContainsKey(columnName)) {
m_validationErrors.Add(columnName, msg);
}
}
private void RemoveError(string columnName) {
if(m_validationErrors.ContainsKey(columnName)) {
m_validationErrors.Remove(columnName);
}
}
public string Error {
get {
if(m_validationErrors.Count > 0) {
return "Field data is invalid.";
}
else return null;
}
}
public string this[string columnName] {
get {
if(m_validationErrors.ContainsKey(columnName)) {
return m_validationErrors[columnName];
}
else {
return null;
}
}
}
/// <summary>
/// Description of the air entity
/// </summary>
public string Child2Description {
get {
return Child2description;
}
set {
description = value;
Validate();
OnPropertyChanged("Child2Description");
}
}
}
I don't believe you're going to be able to do what you want.
In the trivial case, you may be able to make it work. Once you move into more complex types, I don't know that you're saving yourself much effort by having the validation in the parent instead of the child.
The trivial case is going to be where multiple children have similar types of properties. You can enforce calling the properties the same way and then you can write a validation rule within the parent that triggers on the name of the property. However, you could argue that those properties should then be part of the parent and inherited by the child.
The more complex case is individual properties at each child with little to no similarity to the properties of other children. Whether you put the validation code at the child or the parent makes no difference. You have to write the validation code for each individual property you wish to validate.
Given that your validation rules will be stored in a DB, you could write a method in the parent that would allow the children to retrieve the validation rule(s) for their properties. The child would still validate its own properties, but you would have common code for accessing the rules.
I ended up passing the property name, the property value and the list of rules I wanted to use to validate. Storing the Validate method in the parent, so it would run regardless if which child used it, it would use its own rules, but keep the same validation error message dictionary.
protected void Validate(string propertyName, string propertyValue, List<ValidRule> validRules) {
string temp = propertyValue.ToString();
this.RemoveError(propertyName);
if(propertyName.Equals("Description")) {
foreach(ValidRule validRule in validRules) {
if(!Regex.IsMatch(propertyValue, validRule.Rule) && !String.IsNullOrWhiteSpace(propertyValue)) {
this.AddError(propertyName, validRule.ErrorMessage);
break;
}
}
}
}
Actually it is do able just not the way you think you want it to occur.
The below would be the steps I would follow to do something similar.
get the Microsoft enterprise library for starters as you will be using the Microsoft.Practices.EnterpriseLibrary.Validation reference.
Create a validation class that inherits from Validator<T> (this is part of the Enterprise library).
Override the method DoValidate(T objectToValidate, object currentTarget, string key, ValidationResults validationResults) and currentTarget is the object being validated. You can pull the validation rules from the current target.
You then create attribute for that validate making it inherit from ValueValidatorAttribute.
You override the method DoCreateValidator(Type targetType, Type ownerType, MemberValueAccessBuilder memberValueAccessBuilder, ValidatorFactory validatorFactory) in the attribute class.
Once these first 5 steps are done it means you can attribute properties you want to validate and let the validator pick up the validation rule to use from the class (list of rules or dictionary or rules to perform to the property totally your choice).
The next step is to move the interface IDataErrorinfo to the parent class, create a Validate() method that takes the results from the call Microsoft.Practices.EnterpriseLibrary.Validation.Validation.Validate(this); which returns validation errors should they have occurred.
Its up to you on how where you want to place the method call. The simplest way when testing would be to place it in the OnPropertyChanged method you have.

C# - Dynamic properties and RaisePropertyChanged

I have following class which I use for radio button binding
public class RadioButtonSwitch : ViewModelBase
{
IDictionary<string, bool> _options;
public RadioButtonSwitch(IDictionary<string, bool> options)
{
this._options = options;
}
public bool this[string a]
{
get
{
return _options[a];
}
set
{
if (value)
{
var other = _options.Where(p => p.Key != a).Select(p => p.Key).ToArray();
foreach (string key in other)
_options[key] = false;
_options[a] = true;
RaisePropertyChanged("XXXX");
else
_options[a] = false;
}
}
}
XAML
<RadioButton Content="Day" IsChecked="{Binding RadioSwitch[radio1], Mode=TwoWay}" GroupName="Monthly" HorizontalAlignment="Left" VerticalAlignment="Center" />
ViewModel
RadioSwitch = new RadioButtonSwitch(
new Dictionary<string, bool> {{"radio1", true},{"radio2", false}}
);
I'm having problem with RaisePropertyChanged() in my Class. I'm not sure what value I should be putting in order to raise the change.
I tried putting:
Item[]
a
[a]
I keep getting following error:
This is so in case of any change I can handle it in my view accordingly. Please do not give me solutions for List for radio buttons etc.
The problem is that you are implementing an indexer, not an ordinary property. Although the binding subsystem supports indexers, MVVMLight and INotifyPropertyChanged do not.
If you want to use an indexer you need to:
Use a collection base class such as ObservableCollection<T>
Implement INotifiyCollectionChanged and raise that event instead
The first option isn't realistic because you are already deriving from ViewModelBase and have to continue to do that. Since implementing INotifiyCollectionChanged is a little bit of work, the easiest approach is to:
add a property to RadioButtonSwitch that is an observable collection of boolean values (ObservableCollection<bool>)
Then change you binding to add one more path element and you are done.
Edit:
Based on your comment and rereading your question, I think implementing INotifyCollectionChanged is the easiest. Here is the rewrite of your RadioButtonSwitch class which actually no longer needs to derive from the MVVMLight base class, although you still could if you wanted to.
The careful reader will notice that we use a sledgehammer and "reset" the whole collection when any element of the collection is modified. This is not just laziness; it is because the indexer uses a string index instead of an integer index and INotifyCollectionChanged doesn't support that. As a result, when anything changes we just throw up our hands and say the whole collection has changed.
public class RadioButtonSwitch : INotifyCollectionChanged
{
public event NotifyCollectionChangedEventHandler CollectionChanged;
protected void RaiseCollectionChanged()
{
if (CollectionChanged != null)
CollectionChanged(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
}
IDictionary<string, bool> _options;
public RadioButtonSwitch(IDictionary<string, bool> options)
{
this._options = options;
}
public bool this[string a]
{
get
{
return _options[a];
}
set
{
if (value)
{
var other = _options.Where(p => p.Key != a).Select(p => p.Key).ToArray();
foreach (string key in other)
_options[key] = false;
_options[a] = true;
RaiseCollectionChanged();
}
else
_options[a] = false;
}
}
}
GalaSoft.MvvmLight has the following code to check property name before raising PropertyChanged event.
public void VerifyPropertyName(string propertyName)
{
if (GetType().GetProperty(propertyName) == null)
throw new ArgumentException("Property not found", propertyName);
}
GetType().GetProperty("Item[]") obviously returns null.
This is why it is failing.
I think, the quickest workaround for you would be not to use ViewModelBase from this library, but implement your own version, that doesn't do this check:
public class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void RaisePropertyChanged(string propertyName)
{
var handler = PropertyChanged;
if (handler != null)
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
If you implement this class you will be able to run RaisePropertyChanged("Item[]").

Designing a generic data class

I want to popuate a listbox with objects of different types. I display the type (humanized) of the object in the listbox.
I created a class ListBoxView that overrides the ToString method and returns a string according to the type. I create a List of ListBoxView and databind this to the listbox.
public class ListBoxView
{
private object m_value;
public ListBoxView(Object value)
{
m_value = value;
}
public override string ToString()
{
if (m_value is Car)
return "Car";
else if (m_value is User)
return "Human";
else
return "Not defined: " + m_value.GetType().ToString();
}
public T Value
{
get { return m_value; }
set { m_value = value; }
}
}
Can this better be solved with generics? Since I'm new to generics I'm having difficulties implementing using it.
Thanks
UPDATE
The classes like Human and Car are part of a datamodel I can't change. I need to solve this on the user interface side
Actually I think I'd just implement ToString() on Car, Human etc and not bother with this class at all. Otherwise you'll have to update this class every time you add a new type.
If you're worried about I18n of the type names, then keep this class, but only so you can put the results of ToString() through a search on a resource file for the "localised" version of the name.
One very short solution, but maybe not the best, is to move the logic from your ToString method to the listBox1_Format event handler.
private string FormatForListbox(object m_value)
{
if (m_value is Car)
return "Car";
else if (m_value is User)
return "Human";
else
return "Not defined: " + m_value.GetType().ToString();
}
private void listBox1_Format(object sender, ListControlConvertEventArgs e)
{
e.Value = FormatForListbox(e.ListItem);
}
Your solution scales poorly. In particular, look what happens if you want to handle special cases in ToString for other types than just Car and User.
You should follow Neil's advice to put this kind of logic elsewhere, namely in the specific classes – either as the ToString method or as a different method altogether (see code). In summary, using generics here won't help.
interface IInfoProvider {
String Info { get; }
}
class User : IInfoProvider {
…
public override String Info { get { return "Human"; } }
}
class ListBoxView {
…
public override string ToString()
{
IInfoProvider infovalue = m_value as IInfoProvider;
if (infovalue != null)
return infovalue.Info;
else
return "Not defined: " + m_value.GetType().ToString();
}
}

Categories