Voici la seconde partie de l’article Créer un ViewModel de base.

ObservableCollection, List

Si votre ViewModel doit contenir une liste d’éléments, vous allez peut-être vous poser la question suivante : de quel type doit être ma “liste” ?

  • List ?
  • ObservableCollection ?
  • BlockingCollection ?

Pour répondre à cette question, voici les différences qui existent entre ces trois typologies de liste :

  • La List, n’implémente rien de particulier.
  • L’ObservableCollection implémente ICollectionChanging. Si la collection est modifiée (ajout ou suppression d’éléments) depuis le ViewModel, alors la vue sera notifiée de ces changements, donc les changements apparaitront à l’écran.
  • La BlockingCollection permet la modification de son contenu depuis des threads différents. En revanche cette collection n’implémente pas INotifyCollectionChanged, elle ne peut donc pas être bindée sur une vue.

Pour résumé, si vous devez avoir une liste d’éléments bindée sur une vue, il vaut mieux utiliser une ObervableCollection.

INotifyDataErrorInfo

Cette interface est très intéressante car elle permet de gérer les erreurs de validation d’un ViewModel. Si un ViewModel implémente cette interface, votre vue va automatiquement afficher les erreurs de validation s’il y en a. Voila le détail de l’interface :

public interface INotifyDataErrorInfo 
{ 
    bool HasErrors { get; } 
    event EventHandler<DataErrorsChangedEventArgs> ErrorsChanged; 
    IEnumerable GetErrors(string propertyName); 
}

À chaque fois que l’évènement ErrorsChanged est levé, la vue va consulter la valeur de la propriété HasErrors. Si cette propriété est à True, alors la vue va appeler la méthode GetErrors avec en paramètre le nom de la propriété donnée dans l’évènement d’origine.

Maintenant voyons l’implémentation que nous pouvons en faire dans un ViewModel de base.

#region INotifyDataErrorInfo 

public event EventHandler<DataErrorsChangedEventArgs> ErrorsChanged; 

protected Dictionary<string, List<IError>> _errors; 

public virtual IEnumerable GetErrors(string propertyName) 
{ 
   if (string.IsNullOrEmpty(propertyName)) 
   { 
       foreach (var value in _errors.Values) 
           foreach(var errors in value) 
               yield return errors; 
   } 
   else 
       if (_errors.ContainsKey(propertyName)) 
       { 
           foreach (var error in _errors[propertyName]) 
               yield return error; 
       } 
} 

public bool HasErrors 
{ 
   get 
   { 
       return _errors.Any(); 
   } 
} 

protected void SetError(string message, string propertyName = null) 
{ 
   if (propertyName == null) 
       return; 

   List<IError> errors = null; 

   if (_errors.ContainsKey(propertyName) && _errors[propertyName] != null) 
       errors = _errors[propertyName]; 
   else 
       errors = new List<IError>(); 

   errors.Add(new Error() { Message = message, PropertyName = propertyName }); 
   _errors[propertyName] = errors; 
} 

public void ClearErrors() 
{ 
   _errors = new Dictionary<string, List<IError>>(); 
} 

public void ClearErrors(string propertyName) 
{ 
   if (_errors.ContainsKey(propertyName)) 
   { 
       _errors.Remove(propertyName); 
   } 
} 

#endregion

J’ai ajouté un dictionnaire de liste d’erreur pour stocker toutes les erreurs. Ce dictionnaire est indexé par le nom de la propriété qui est en erreur. La méthode GetErrors retourne la liste d’erreurs contenu dans ce dictionnaire. Dans le cas où le GetErrors est appelé avec une chaîne vide en paramètre cela signifie que les erreurs retournées serons les erreurs du ViewModel. Cette fonctionnalité est très importante car elle permet de remonter les erreurs au niveau du ViewModel et non simplement au niveau de chaque propriété. Dans mon cas, j’ai choisi de remonter toutes les erreurs de toutes les propriétés. Coté vue, cette fonctionnalité nous permettras d’agréger toutes les erreurs à un seul endroit.

Passons maintenant à l’affichage des ces informations coté vue. Dans le XAML, il est possible de récupérer cette liste d’erreurs et de l’afficher au travers de la propriété Validation. ErrorTemplate. Voila un exemple :

<Style TargetType="{x:Type CheckBox}" > 
   <Setter Property="Validation.ErrorTemplate" > 
       <Setter.Value> 
           <ControlTemplate> 
               <DockPanel> 
                   <AdornedElementPlaceholder Margin="0,0,5,0" /> 
                   <Border Width="10" Height="10" CornerRadius="5" HorizontalAlignment="Center" VerticalAlignment="Center" Background="Transparent" BorderThickness="1" BorderBrush="Red"> 
                       <Border.ToolTip> 
                           <ItemsControl ItemsSource="{Binding}" DockPanel.Dock="Right"> 
                               <ItemsControl.ItemTemplate> 
                                   <DataTemplate> 
                                       <TextBlock Text="{Binding ErrorContent.Message}" /> 
                                   </DataTemplate> 
                               </ItemsControl.ItemTemplate> 
                           </ItemsControl> 
                       </Border.ToolTip> 
                   </Border> 
               </DockPanel> 
           </ControlTemplate> 
       </Setter.Value> 
   </Setter> 
</Style>

Ne ratez plus aucunes actualités avec la newsletter mensuelle de SoftFluent