Polar
Polar

Reputation: 3537

Communication between Object of ObservableCollection and ViewModel

Let's say I have this in my ViewModel class. (Notice the OnSubmit function)

public class MyViewModel : ViewModelBase{
    
    private string? _SomeName;
    public string? SomeName{
        get => _SomeName;
        set {
            Validate(nameof(SomeName)); //Validate data on changes
            SetProperty(ref _SomeName, value);
        }
    }
    
    //Call this function to Validate the input data
    private void Validate(string propertyName){
        ClearError(propertyName);
        switch(propertyName){
            case nameof(SomeName):
                if (string.IsNullOrWhiteSpace(_SomeName)) 
                    AddError(nameof(SomeName), "Name cannot be empty.");
            break;  
            ...
            //other validation for propeties here if present
        }
    }
    
    private ObservableCollection<TransactionItem>? _Transactions;
    public ObservableCollection<TransactionItem>? Transactions{
        get => _CodeItemDisbursement;
        set {
            SetProperty(ref _Transactions, value); 
        }
    }
    
    //ICommand Add new item to ObservableCollection
    private readonly ButtonCommand _AddItem;
    public ICommand AddItem => _AddItem;
    private void OnAddItem(object commandParamater){
        _Transactions.Add(new TransactionItem(){
            ... //add the data here...
        });
    }
    
    //ICommand Form Submission
    private readonly ButtonCommand _Submit;
    public ICommand Submit => _Submit;
    private void OnSubmit(object commandParamater){ //Validate all data before processing it.
        //Validate all input is correct before we proceed.
        Validate(nameof(SomeName));

        // How do I call here, the `Validate()` function 
        // of each item in my `ObservableCollection<TransactionItem>`
        // and check if `HasErrors` property has some value?
    
        if(HasErrors) return; //There are errors, do not proceed.
        
        //If no errors, then do process the data here...
    }
    
    //ViewModel Constructor
    public MyViewModel() {
        _Submit = new ButtonCommand(OnSubmit);
        _AddItem = new ButtonCommand(OnAddItem);
    }
}

As you can see, I have an ObservableCollection with an object of TransactionItem, and here is the TransactionItem class.

public class TransactionItem : ViewModelBase{
  
  public int ID { get; set; }
  public string? Description { get; set; }
  public string? OtherDetails { get; set; }
  
  private string? _TransactionName; 
  public string? TransactionName { 
    get => _TransactionName; 
    set {
        Validate(nameof(TransactionName)); //Validate data on changes
        SetProperty(ref _TransactionName, value); 
    }
  }
 
  private decimal _Amount;
  public decimal Amount { 
    get => _Amount; 
    set {
        Validate(nameof(Amount)); //Validate data on changes
        SetProperty(ref _Amount, value); 
    }
  }
  
  //Call this function to Validate the input data
  private void Validate(string propertyName){
    ClearError(propertyName);
    switch(propertyName){
        case nameof(TransactionName):
            if (string.IsNullOrWhiteSpace(_TransactionName)) 
                AddError(nameof(TransactionName), "Name of transaction cannot be empty.");
        break;
        case nameof(Amount):
            if (string.IsNullOrWhiteSpace(_Amount.ToString()) || _Amount < 1) 
                AddError(nameof(Amount), "Please input a valid amount");
        break;  
    }
  }

}

I also have a class of ViewModelBase that implements the INotifyPropertyChanged, and INotifyDataErrorInfo.

public abstract class ViewModelBase : INotifyPropertyChanged, INotifyDataErrorInfo{
    
    #region INotifyPropertyChanged 
    public event PropertyChangedEventHandler? PropertyChanged;

    protected void OnPropertyChanged([CallerMemberName] string? propertyName = null)
        => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));

    protected virtual bool SetProperty<T>(ref T member, T value, [CallerMemberName] string? propertyName = null){
        if (EqualityComparer<T>.Default.Equals(member, value)) return false;
        
        member = value;
        OnPropertyChanged(propertyName);
        return true;
    }
    #endregion

    #region INotifyDataErrorInfo | Error Handling
    private readonly Dictionary<string, string> _propertyErrors = new();
       
    public bool HasErrors => _propertyErrors.Any();
    public event EventHandler<DataErrorsChangedEventArgs>? ErrorsChanged;

    public void ClearError(string? propertyName){
        if (propertyName!=null && _propertyErrors.Remove(propertyName)) OnErrorsChanged(propertyName);
    }

    public void AddError(string propertyName, string errorMessage){
        if (!_propertyErrors.ContainsKey(propertyName)) {
            _propertyErrors.Add(propertyName, errorMessage);
        }else{ 
            _propertyErrors[propertyName] = errorMessage;
        }
        if (propertyName != null) OnErrorsChanged(propertyName); 
    }
    
    private void OnErrorsChanged(string propertyName){
        ErrorsChanged?.Invoke(this, new DataErrorsChangedEventArgs(propertyName));
    }

    public IEnumerable GetErrors(string? propertyName){
        if (string.IsNullOrEmpty(propertyName))
            yield break;
        if (_propertyErrors.TryGetValue(propertyName, out string? existingError))
            yield return existingError;
    }
    #endregion
}

What happened here is that, when the OnSubmit function is triggered from the ViewModel class, I want to call the Validate() function to check if every required property has the correct input. Then if the HasErrors property from my ViewModelBase has a value, then do not proceed to process the data.

The problem is in my ObservableCollection<TransactionItem>. I also want to call the Validate() function in the object TransactionItem to validate the TransactionName, and Amount of each item and not proceed if HasErrors has a value. But I'm lost and I don't know what should I do next.

Upvotes: 0

Views: 74

Answers (1)

Polar
Polar

Reputation: 3537

You could make a public function in your TransactionItem that triggers the Validate() and returns the value of HasErrors, then run a loop in your OnSubmit() and call that function in every item of your ObservableCollection<TransactionItem>. If any of it returns true, then do not proceed.

For example, create a public function in your TransactionItem

public bool ContainsError(){
    Validate(nameof(TransactionName)); //Validate data on changes
    Validate(nameof(Amount)); //Validate data on changes
    return HasErrors;
  }

Then in your ViewModel class, in OnSubmit() function do this

private void OnSubmit(object commandParamater){ 

    Validate(nameof(SomeName)); //First, validate your SomeName or other propeties.
    
    bool ContainsError = false;
    foreach(TransactionItem item in _Transactions){
        ContainsError = item.ContainsError();
    }
    
    if(HasErrors || ContainsError) return; //There are errors, do not proceed.
    
    //If no errors, then do process the data here...
}

That should do.

Upvotes: 1

Related Questions