I'm developing WPF
application with MVVM
pattern and using Prism
Framework.
I have a basic data class as follow.
public class ProductDecorator : DecoratorBase<Product>
{
private string _ProductShortName;
private Boolean _IsSelected = false;
// I have omitted some code for clarity here.
[Required]
public int ProductID
{
get { return BusinessEntity.ProductID; }
set
{
SetProperty(() => BusinessEntity.ProductID == value,
() => BusinessEntity.ProductID = value);
}
}
public Boolean IsSelected
{
get { return _IsSelected; }
set
{
SetProperty(ref _IsSelected, value);
}
}
}
I create the observable collection of the above data class in the ViewModel.
public class SaleInvoiceViewModel {
private ObservableCollection<ProductDecorator> _productDecorators;
public ObservableCollection<ProductDecorator> ProductDecorators
{
get { return _productDecorators; }
set { SetProperty(ref _productDecorators, value); }
}
}
And I bounded this observable collection to the listbox in the View.
<telerik:RadListBox ItemsSource="{Binding ProductDecorators}" HorizontalAlignment="Stretch" Margin="5,10,5,5" Grid.Column="1" VerticalAlignment="Top">
<telerik:RadListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<CheckBox Margin="2" IsChecked="{Binding IsSelected}" />
<TextBlock Text="{Binding ProductShortName}" FontSize="14" />
</StackPanel>
</DataTemplate>
</telerik:RadListBox.ItemTemplate>
</telerik:RadListBox>
From the above context, I want to validate "the user must select at least one item in the list box". In other words, IsSelected
property must be true in one of the ProductUmDecorator
class from the observable collection ProductUmDecorators
.
Currently I use INotifyDataErrorInfo
Interface and Data Annotations
for validation rule. I've lost that how should I implement my problem to achieve this validation?
There are a lot of questions on stackoverflow related with this topic but no solid answer. So I decided to post my solution as the answer to this problem. The context of the problem is to check "the user must select one item in the listbox which is bound with observable collection".
First step, the item (entity) in the ObservableCollection
need IsSelected
property.
public class ProductDecorator : DecoratorBase<Product>
{
private string _ProductShortName;
private Boolean _IsSelected = false;
// I have omitted some code for clarity here.
public Boolean IsSelected
{
get { return _IsSelected; }
set
{
SetProperty(ref _IsSelected, value);
}
}
}
Second step, each item in the ObservableCollection
must implement INotifyPropertyChanged
interface. Then you can access the PropertyChanged
eventhandler.
Third step, you need to attach the following method to CollectionChanged
event handler of ObservableCollection
.
public class SaleInvoiceViewModel {
private ObservableCollection<ProductDecorator> _productDecorators;
public ObservableCollection<ProductDecorator> ProductDecorators
{
get { return _productDecorators; }
set { SetProperty(ref _productDecorators, value); }
}
public SaleInvoiceViewModel() {
_productDecorators= new ObservableCollection<ProductDecorator>();
_productDecorators.CollectionChanged += ContentCollectionChanged;
}
public void ContentCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
if (e.Action == NotifyCollectionChangedAction.Remove)
{
foreach(ProductDecorator item in e.OldItems)
{
//Removed items
item.PropertyChanged -= EntityPropertyChanged;
}
}
else if (e.Action == NotifyCollectionChangedAction.Add)
{
foreach(ProductDecorator item in e.NewItems)
{
//Added items
item.PropertyChanged += EntityPropertyChanged;
}
}
}
}
Look carefully EntityPropertyChanged
method in the above code. This method will trigger whenever any properties changed in any items in the ObservableCollection
. Then, you can simply call Validate
method in the EntityPropertyChanged
method.
private void EntityPropertyChanged( object sender, PropertyChangedEventArgs e )
{
if (e.PropertyName == "IsSelected")
this.Product.ValidateProperty("ProductUmDecorators");
}
If the changed property is IsSelected
, the ValidatedProperty
method will be run.
I will omit the detail implementation of ValidateProperty
method. This method will try to validate the property with DataAnnotations
and trigger the ErrorChanged event when there is any errors. You can learn detail here.
Finally, you need to implement custom DataAnnotation
ValidationAttribute
in your Entity/ViewModel/Decorator class where your ObservableCollection
property existed as the following code.
public class SaleInvoiceViewModel {
private ObservableCollection<ProductDecorator> _productDecorators;
[AtLeastChooseOneItem(ErrorMessage = "Choose at least one item in the following list.")]
public ObservableCollection<ProductDecorator> ProductDecorators
{
get { return _productDecorators; }
set { SetProperty(ref _productDecorators, value); }
}
}
public class AtLeastChooseOneItem : ValidationAttribute
{
protected override ValidationResult IsValid( object value, ValidationContext validationContext )
{
ProductDecorator tmpEntity = (ProductDecorator) validationContext.ObjectInstance;
var tmpCollection = (ObservableCollection<ProductUmDecorator>) value;
if ( tmpCollection.Count == 0 )
return ValidationResult.Success;
foreach ( var item in tmpCollection )
{
if ( item.IsSelected == true )
return ValidationResult.Success;
}
return new ValidationResult( ErrorMessage );
}
}
That is a little complex solution but that is the most solid solution I find out so far.