WPF, ListView d’images, MVVM et Drag and Drop… – Fin

Introduction

Les projets précédents ont été redécoupés afin de fournir 2 assemblies directement utilisable.

Pour ceux qui souhaitent ajouter rapidement un treeview d’images avec une gestion de Drag&Drop (Voir les 4 précédents chapitres ) voici les étapes de réalisation.

L’exemple suivant montre comment réaliser une application respectant l’architecture M-V-VM avec les briques Drag and Drop et Listview d’images

Application M-V-VM exemple avec Liste en arbre de type TreeView

Il vous faudra :

  1. Ajouter les assemblies
  2. Définir un objet possédant l’Interface IImageFile (et IParentItem si vous souhaiter des structures hiérarchiques)
  3. Définir un objet ViewModel dérivant de CustomImageFileViewModel si vous voulez gérer le DragAndDrop provenant d’applications externes
  4. Initialiser les images dans CollectionImageViewModel

Implémentation

Ajouts des assemblies

La première étape est de télécharger ou compiler les assemblies suivantes (à la fin de l’article) :

  • ImageFileListView : Contient la partie visuelle, le View Model et les interfaces du modèle à implémenter
  • DragDropManager : Contient les mécanismes de Drag and Drop qui pourront être appliqués sur la lise

Ajout de la liste dans la View

Pour ajouter la liste dans la View vous devez définir une référence dans le XAML à l’assemblie ImageFileListView.

xmlns:m="clr-namespace:ImageFileListView.View;assembly=ImageFileListView"

Dans le code XAML ajouter le composant de la façon suivante

 <m:ImageFileListView x:Name="listView1" ClipToBounds="False" Grid.ColumnSpan="2" />

Implémentation de la couche Model

Les objets utilisés doivent implémenter l’interface IImageFile. Si vous souhaitez gérer une hiérarchie (par exemple des répertoires) vous devez en plus utiliser l’interface IParentItem.

Voici un exemple d’implémentation (disponible dans l’exemple).

Exemple d’implémentation pour gérer des fichiers  :

 

public class ImageFile: IImageFile
{
   public string FileName { get; set; }
   public IParentItem Parent { get; set; }

   public virtual bool IsAvailable
   {
       get 
       {
           return File.Exists(FileName);
       }
   }

   public virtual Stream LoadImage()
   {
       byte[] buffer = File.ReadAllBytes(FileName);
       return new MemoryStream(buffer);
   }

   #region ICloneable Membres

   public virtual object Clone()
   {
       ImageFile o = new ImageFile();
       o.FileName = this.FileName;
       return o;
   }

   #endregion
}

 

Exemple d’implémentation pour gérer les répertoires d’un disque

 

public class ParentImageFile : ImageFile, IImageFile, IParentItem
{
   public override bool IsAvailable
   {
       get
       {
           return Directory.Exists(FileName);
       }
   }
   
   public override Stream LoadImage()
   {
       return null;
   }

   public List<IImageFile> ImageFiles { get; set; }

   public override object Clone()
   {
       ImageFile clonedItem = null;
       ParentImageFile o = new ParentImageFile();
       o.FileName = this.FileName;
       o.ImageFiles = new List<IImageFile>();
       foreach (IImageFile item in this.ImageFiles)
       {
           clonedItem = item.Clone() as ImageFile;
           clonedItem.Parent = o;
           o.ImageFiles.Add(clonedItem);
       }
       return o;
   }
}

 

Implémentation de la couche View-Model

Vous pouvez directement utiliser l’héritage d’un objet CustomImageFileViewModel.

Le lien entre la couche Model et la couche View-Model est réalisée par le code suivant :

namespace WpfListViewMVVMSample.ViewModel
{
    public class ImageFileViewModel: CustomImageFileViewModel
    {
        public override void CreateFromFileName(string imageFileName)
        {
            ImageFile = new ImageFile() { FileName = imageFileName };
        }
    }
}

Initialisation des comportements DragAndDrop dans la vue

Initialisation

Dans l’initialisation de la fenêtre, sur la partie View  :

namespace WpfListViewMVVMSample
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
  
        public MainWindow()
        {
            InitializeComponent();
            this.DataContext = new MainWindowViewModel();
            DragDropManagerListBoxAdornerViewModel<ImageFileViewModel> dragDropManager
                = new DragDropManagerListBoxAdornerViewModel<ImageFileViewModel>
                (listViewImages.ImageFilesListBox);
            dragDropManager.RegisterDragDropManagerOperation(
                new DragDropManagerOperationTreeView<ImageFileViewModel>());

        }       
    }
}

 

  • La vue initialise le ViewModel : (this.DataContext = new MainWindowViewModel)
  • La vue initialise le DragDropManager avec l’objet ViewModel (ImageFileViewModel) décrit plus haut et indique que les actions de Glisser-Déplacer interviendront sur la liste listViewImages
  • Le dragdrop est initialisé pour pouvoir gérer une structure d’arbre récursive.

Initialisation du ViewModel de la liste

Le viewModel dérive naturellement de INotifyPropertyChanged et l’on retrouve classiquement PropertyChangedEventHandler et RaisePropertyChanged.

Pour notre interface, nous avons 3 propriétés :

  • ImagesPath : La donnée représentant de la zone de texte du répertoire contenant les images
  • _imagesFilesData : Les fichiers images (partiellement affichées dans la liste)
  • LoadFilesCommand : L’action de rechargement des fichiers depuis le répertoire

Voici le code source de MainWindowViewModel qui sera “bindé” sur la vue MainWindow

namespace WpfListViewMVVMSample.ViewModel
{
    public class MainWindowViewModel : INotifyPropertyChanged
    {
        private ImageFileCollectionViewModel<ImageFileViewModel> _imagesFilesData;
        private string _imagesPath;

        public ICommand LoadFilesCommand { get; private set; }    
        
        public string ImagesPath
        {
            get { return _imagesPath; }
            set
            {
                _imagesPath = value;
                RaisePropertyChanged("ImagesPath");
            }
        }

        public ImageFileCollectionViewModel<ImageFileViewModel> ImagesFilesData
        {
            get
            {
                return _imagesFilesData;
            }
        }
       
        public MainWindowViewModel()
        {
          _imagesFilesData = new ImageFileCollectionViewModel<ImageFileViewModel>();
          ImagesPath = Environment.GetFolderPath(Environment.SpecialFolder.MyPictures);
          LoadFilesCommand = new SimpleDelegateCommand(
                (x) => { InitImagesFilesDataWithDirectory(_imagesPath); });
        }

        private void InitImagesFilesDataWithDirectory(string directory)
        {
            var files = from file in Directory.GetFiles(directory, "*.*")
                        where file.ToUpper().EndsWith(".JPG")
                        select new ImageFileViewModel() 
                          {ImageFile = new ImageFile() {FileName = file} };
            foreach (var img in files)
            {
               _imagesFilesData.AllImages.Add(img);
            }
        }

        public event PropertyChangedEventHandler PropertyChanged;

        private void RaisePropertyChanged(string propertyName)
        {
            if (PropertyChanged == null) return;
            PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }
    }
}

LoadFilesCommand est initialisé avec un SimpleDelegateCommand décrit ci dessous. Il permet de lancer sous forme de delegate une action et de traiter ainsi le chargement de la liste depuis le texte saisi.

SimpleDelegateCommand peut être utilisé pour n’importe quelle action d’une commande nécessitant de personnaliser le “execute” avec un délégué.

public class SimpleDelegateCommand: ICommand
{
   Action<object> _executeDelegate;


   public SimpleDelegateCommand(Action<object> executeDelegate)
   {
       _executeDelegate = executeDelegate;
   }

   public void Execute(object parameter)
   {
       _executeDelegate(parameter);
   }

   public bool CanExecute(object parameter) 
   { 
       return true; 
   }

   public event EventHandler CanExecuteChanged;
}

Binding des données sur le XAML

Le code XAML n’a plus qu’à récupérer le binding de la zone de texte, de la commande du bouton et de la liste des fichiers :

<Window x:Class="WpfListViewMVVMSample.MainWindow"
   xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
   xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
   xmlns:m="clr-namespace:ImageFileListView.View;assembly=ImageFileListView"
   Title="MainWindow" Height="350" Width="525">
<Grid>
   <Border x:Uid="Border_1" BorderBrush="#FF4355C1" BorderThickness="2,2,2,2" 
           CornerRadius="4,4,4,4" Margin="8,60,2,8">
       <m:ImageFileListView x:Name="listViewImages" ClipToBounds="False" 
           DataContext="{Binding ImagesFilesData}" />
   </Border>
   <Button Content="Ok" Height="23" Margin="0,22,12,0" Name="button1" 
           VerticalAlignment="Top" HorizontalAlignment="Right" Width="95" 
           Command="{Binding LoadFilesCommand}"/>
   <TextBox Height="23" Margin="12,22,113,0" Name="textBox1" VerticalAlignment="Top" 
            Text="{Binding ImagesPath}"/>
</Grid>
</Window>

Résultat

Application exemple avec une liste d'images simple

Ajout de la gestion des répertoires

Nous allons modifier l’interface pour prendre en charge la gestion des répertoires par une case à cocher. Lorsque la case sera cochée, la liste contiendra les répertoires, lorsqu’elle sera décochée elle n’affichera que les images.

Pour cela nous ajoutons un balise CheckBox qui sera “Bindé” sur une propriété ViewModel: IsDirectory

<CheckBox Content="Directories" Height="16" HorizontalAlignment="Left" 
                  Margin="20,50,0,0" VerticalAlignment="Top" 
                  IsChecked="{Binding IsDirectory}" />

Dans le MainWindowViewModel, nous ajoutons la propriété IsDirectory

public bool IsDirectory
{
  get { return _isDirectory; }
  set
  {
      _isDirectory = value;
      RaisePropertyChanged("IsDirectory");
  }
}

Dans la Commande du bouton OK, nous modifions l’appel :

public MainWindowViewModel()
{
_imagesFilesData = new ImageFileCollectionViewModel<ImageFileViewModel>();
ImagesPath = Environment.GetFolderPath(Environment.SpecialFolder.MyPictures);
LoadFilesCommand = new SimpleDelegateCommand(
      (x) => { InitImagesFilesDataWithDirectory(ImagesPath, IsDirectory); });
}

 

InitImagesFilesDataWithDirectory appelle soit

  • la liste des images ou soit
  • la liste des images avec les répertoires récursifs.

Le code ne présente pas d’intérêt ici et se trouve dans l’exemple.

Résultat avec les répertoires

Application M-V-VM exemple avec Liste en arbre de type TreeView

 

Téléchargements et exemples

Voici le lien pour télécharger la solution avec le code source du projet et des tests unitaires

download32x32 WPFImageFileListView.zip [449 ko]

 

Laissez un commentaire

Votre adresse de messagerie ne sera pas publiée. Les champs obligatoires sont indiqués avec *

quatre × deux =

Ce site utilise Akismet pour réduire les indésirables. En savoir plus sur comment les données de vos commentaires sont utilisées.