{"id":436,"date":"2013-03-23T23:21:50","date_gmt":"2013-03-23T21:21:50","guid":{"rendered":"http:\/\/dev.bratched.fr\/fr\/wpf-listview-dimages-mvvm-et-drag-and-drop-partie-1-2\/"},"modified":"2013-03-23T23:21:50","modified_gmt":"2013-03-23T21:21:50","slug":"wpf-listview-dimages-mvvm-et-drag-and-drop-partie-1","status":"publish","type":"post","link":"https:\/\/bratched.com\/fr\/2013\/03\/23\/wpf-listview-dimages-mvvm-et-drag-and-drop-partie-1\/","title":{"rendered":"WPF, ListView d\u2019images, MVVM et Drag and Drop\u2026 \u2013 Partie 1"},"content":{"rendered":"<h1 class=\"jfdefaulttext\">Introduction<\/h1>\n<p>Pour les besoins d\u2019un logiciel orient\u00e9 \u00ab Photos \u00bb, j\u2019ai eu besoin des fonctionnalit\u00e9s essentielles suivantes :<\/p>\n<ul>\n<li><strong>Affichage des images sous forme de vignettes<\/strong> dans une liste et dans une arborescence de type TreeView<\/li>\n<li>Cet affichage doit permettre de g\u00e9rer un <strong>nombre imposant de photos<\/strong> (&gt;5000 !) sans g\u00e9n\u00e9rer de d\u00e9bordement m\u00e9moire ou de ralentissement<\/li>\n<li>L\u2019affichage et la g\u00e9n\u00e9ration des <strong>vignettes <\/strong>doit pouvoir se faire en mode asynchrone (en lisant les informations EXIF si elles existent,\u2026)<\/li>\n<li>La <strong>multi-s\u00e9lection<\/strong> doit permettre de s\u00e9lectionner plusieurs \u00e9l\u00e9ments.<\/li>\n<li>Les \u00e9l\u00e9ments s\u00e9lectionn\u00e9s doivent pouvoir \u00eatre d\u00e9plac\u00e9s ou copi\u00e9s dans la liste (ou l\u2019arbre) par <strong>Drag And Drop <\/strong><\/li>\n<\/ul>\n<p>Et le tout en <strong>DotNet WPF<\/strong> respectant le pattern <strong>MVVM<\/strong> !!!<\/p>\n<p><a href=\"http:\/\/www.bratched.com\/images\/stories\/image.png\"><img decoding=\"async\" loading=\"lazy\" title=\"MVVM Pattern\" src=\"http:\/\/dev.bratched.fr\/fr\/wp-content\/uploads\/sites\/2\/importedmedia\/blogmedia-image-thumb-png.png\" alt=\"MVVM Pattern\" width=\"343\" height=\"285\" border=\"0\" \/><\/a><\/p>\n<p>Je ne ferai pas un gros article sur la n\u00e9cessit\u00e9 de d\u00e9velopper en WPF selon une architecture <strong>MVVM<\/strong> (S\u00e9paration des fonctions, r\u00e9utilisation, r\u00f4le des objets mieux cibl\u00e9s, acc\u00e8s d\u00e9claratifs\u2026). Voici quelques liens au cas o\u00f9 vous ne seriez toujours pas convaincus\u2026<\/p>\n<p><a title=\"http:\/\/www.orbifold.net\/default\/?p=550\" href=\"http:\/\/www.orbifold.net\/default\/?p=550\" rel=\"nofollow\">http:\/\/www.orbifold.net\/default\/?p=550<\/a><br \/>\n<a title=\"http:\/\/japf.developpez.com\/tutoriels\/dotnet\/mvvm-pour-des-applications-wpf-bien-architecturees-et-testables\/\" href=\"http:\/\/japf.developpez.com\/tutoriels\/dotnet\/mvvm-pour-des-applications-wpf-bien-architecturees-et-testables\/\" rel=\"nofollow\">http:\/\/japf.developpez.com\/tutoriels\/dotnet\/mvvm-pour-des-applications-wpf-bien-architecturees-et-testables\/<\/a><br \/>\n<a title=\"http:\/\/msdn.microsoft.com\/fr-fr\/magazine\/dd419663.aspx\" href=\"http:\/\/msdn.microsoft.com\/fr-fr\/magazine\/dd419663.aspx\" rel=\"nofollow\">http:\/\/msdn.microsoft.com\/fr-fr\/magazine\/dd419663.aspx<\/a><br \/>\n<a title=\"http:\/\/www.c2i.fr\/Article\/Detail\/a3809f7b-196a-4d8c-bb48-164f591920bb\" href=\"http:\/\/www.c2i.fr\/Article\/Detail\/a3809f7b-196a-4d8c-bb48-164f591920bb\" rel=\"nofollow\">http:\/\/www.c2i.fr\/Article\/Detail\/a3809f7b-196a-4d8c-bb48-164f591920bb<\/a><\/p>\n<h1>MVVM et ListView<\/h1>\n<p>Cette premi\u00e8re partie pr\u00e9sente l\u2019impl\u00e9mentation de la liste d\u2019images avec le mod\u00e8le MVVM.<\/p>\n<h2>Exemple d\u2019application<\/h2>\n<p>Voici le descriptif fonctionnel de l\u2019exemple que nous allons mettre en place : Un petit croquis vaut mieux qu\u2019un long discours.<\/p>\n<p><a href=\"http:\/\/dev.bratched.fr\/fr\/wp-content\/uploads\/sites\/2\/importedmedia\/blogmedia-wpfdragdrop01-jpg.jpg\"><img decoding=\"async\" loading=\"lazy\" title=\"Draft MVVM application\" src=\"http:\/\/dev.bratched.fr\/fr\/wp-content\/uploads\/sites\/2\/importedmedia\/blogmedia-wpfdragdrop01-thumb-jpg.jpg\" alt=\"Draft MVVM application\" width=\"351\" height=\"247\" border=\"0\" \/><\/a><\/p>\n<p>L\u2019application est extr\u00eamement simpliste. La saisie d\u2019un r\u00e9pertoire se fait en haut, le click sur le bouton affiche la liste des images et leur nombre total.<\/p>\n<h2>D\u00e9coupage de l\u2019application<\/h2>\n<p>L\u2019application est d\u00e9coup\u00e9e en 4 parties :<\/p>\n<ul>\n<li><strong>Model<\/strong> : l\u2019objet \u201cm\u00e9tier\u201d image<\/li>\n<li><strong>ViewModel<\/strong> : les objets n\u00e9cessaires \u00e0 la manipulation des objets \u201cModel\u201d images par la couche \u201cView\u201d (D\u00e9tail de l\u2019image et liste des images)<\/li>\n<li><strong>View<\/strong> : L\u2019affichage XAML-WPF de la liste des images<\/li>\n<li><strong>Controler<\/strong> : Les actions n\u00e9cessaires : Remplissage de la liste par exemple<\/li>\n<\/ul>\n<p><!--more-->Avec un diagramme, cela ressemble \u00e0 ceci :<\/p>\n<p><a href=\"http:\/\/dev.bratched.fr\/fr\/wp-content\/uploads\/sites\/2\/importedmedia\/blogmedia-wpfdragdrop03-jpg.jpg\"><img decoding=\"async\" loading=\"lazy\" title=\"MVVM Pattern\" src=\"http:\/\/dev.bratched.fr\/fr\/wp-content\/uploads\/sites\/2\/importedmedia\/blogmedia-wpfdragdrop03-thumb-jpg.jpg\" alt=\"MVVM Pattern\" width=\"494\" height=\"343\" border=\"0\" \/><\/a><\/p>\n<p>L\u2019organisation des classes dans la solution se traduit comme ceci<\/p>\n<p><a href=\"http:\/\/dev.bratched.fr\/fr\/wp-content\/uploads\/sites\/2\/importedmedia\/blogmedia-wpfdragdrop02-jpg.jpg\"><img decoding=\"async\" loading=\"lazy\" title=\"MVVM solution explorer\" src=\"http:\/\/dev.bratched.fr\/fr\/wp-content\/uploads\/sites\/2\/importedmedia\/blogmedia-wpfdragdrop02-thumb-jpg.jpg\" alt=\"MVVM solution explorer\" width=\"234\" height=\"244\" border=\"0\" \/><\/a><\/p>\n<p>&nbsp;<\/p>\n<h2>D\u00e9tail des couches MVVM<\/h2>\n<h3>La couche Model<\/h3>\n<p>Cette couche n\u2019est pas sp\u00e9cifique \u00e0 la technologie WPF.<br \/>\nJ\u2019ai ajout\u00e9 une interface <strong>IImageFile<\/strong> pour simplifier les \u00e9volutions.<br \/>\nVoici l\u2019interface de manipulation de l\u2019objet Image.<\/p>\n<div id=\"scid:57F11A72-B0E5-49c7-9094-E3A15BD5B5E6:27e12e4d-a38d-4af9-8935-586c364cd48e\" class=\"wlWriterEditableSmartContent\" style=\"margin: 0px;padding: 0px;float: none\">\n<pre>public interface IImageFile\n{\n    string FileName { get; }\n    bool IsAvailable { get; }\n}\n\n<\/pre>\n<\/div>\n<p><strong>FileName<\/strong> contient le chemin complet du fichier Image<br \/>\n<strong>IsAvailable<\/strong> retourne vrai si le fichier existe<\/p>\n<p>Et voici son impl\u00e9mentation<\/p>\n<div id=\"scid:57F11A72-B0E5-49c7-9094-E3A15BD5B5E6:bc9d102c-12e9-4187-8234-242ee8365347\" class=\"wlWriterEditableSmartContent\" style=\"margin: 0px;padding: 0px;float: none\">\n<pre>public class ImageFile: IImageFile\n{\n   public string FileName { get; set; }\n   public bool IsAvailable\n   {\n       get \n       {\n           return File.Exists(FileName);\n       }\n   }\n}\n<\/pre>\n<\/div>\n<p>&nbsp;<\/p>\n<p>&nbsp;<\/p>\n<h3>La couche ViewModel<\/h3>\n<p>Passons maintenant \u00e0 la couche ViewModel qui va exposer les informations n\u00e9cessaires \u00e0 la vue.<\/p>\n<ul>\n<li>La classe <strong>FileViewModel<\/strong> va contenir l\u2019objet ViewModel pour un fichier image<\/li>\n<li>La classe <strong>ImageFileCollectionViewModel<\/strong> va contenir la liste des objets FileViewModel<\/li>\n<\/ul>\n<p>Pour se faire les classes impl\u00e9mentent l\u2019interface <strong>INotifiedProperty<\/strong>.<br \/>\nCette interface n\u00e9cessite l\u2019impl\u00e9mentation d\u2019un \u00e9v\u00e9nement de type <strong>PropertyChangedEventHandler<\/strong> qui remontra<\/p>\n<h3>la classe FileViewModel<\/h3>\n<p>La propri\u00e9t\u00e9 <strong>ShortName<\/strong> permettra d\u2019afficher uniquement le nom du fichier, l\u2019objet m\u00e9tier complet sera stock\u00e9 dans un attribut priv\u00e9 <strong>_ImageFile<\/strong>.<\/p>\n<div id=\"scid:57F11A72-B0E5-49c7-9094-E3A15BD5B5E6:0fe944a3-da00-47da-ac74-a1d34d7a84cf\" class=\"wlWriterEditableSmartContent\" style=\"margin: 0px;padding: 0px;float: none\">\n<pre>public class FileViewModel: INotifyPropertyChanged\n{\n   protected IImageFile _imageFile;\n   public string ShortName\n   {\n       get { return Path.GetFileName(_imageFile.FileName); }\n   }\n\n   public FileViewModel(IImageFile imageFile)\n   {\n       this._imageFile = imageFile;\n   }\n\n   public event PropertyChangedEventHandler PropertyChanged;\n   protected void OnPropertyChanged(string propertyName)\n   {\n       if (PropertyChanged != null)\n       {\n           PropertyChanged(this, new PropertyChangedEventArgs(propertyName));\n       }\n   }\n}\n<\/pre>\n<\/div>\n<h3>La classe ImageFileCollectionViewModel<\/h3>\n<p>La liste des fichiers images sera expos\u00e9e \u00e0 travers une autre classe <strong>ImageFileCollectionViewModel<\/strong>.<br \/>\nPour obtenir la liste des images dans la vue nous utiliserons donc la propri\u00e9t\u00e9 <strong>AllImages<\/strong> de type <strong>ObservableCollection<\/strong> de <strong>FileViewModel<\/strong>.<\/p>\n<p><strong>DataItemCount<\/strong> permettra d\u2019obtenir le nombre total d\u2019images dans la liste. DataItemCount\u00a0 appelle le fameux <strong>OnPropertyChanged<\/strong> qui permettra \u00e0 chaque modification du nombre d\u2019\u00e9l\u00e9ments (de la couche Model)\u00a0 de notifier \u00e0 la vue <strong>XAML<\/strong> de se rafraichir automatiquement.<\/p>\n<p>L\u2019ajout d\u2019\u00e9l\u00e9ments dans la liste se fera avec la m\u00e9thode <strong>AddNewPhotoItem<\/strong>.<\/p>\n<div id=\"scid:57F11A72-B0E5-49c7-9094-E3A15BD5B5E6:d674b6fe-75ac-46d7-913a-ac1bf75f99ba\" class=\"wlWriterEditableSmartContent\" style=\"margin: 0px;padding: 0px;float: none\">\n<pre>public class ImageFileCollectionViewModel: INotifyPropertyChanged\n{\n   private ObservableCollection&lt;FileViewModel&gt; _allImages;\n   private int dataItemsCount;\n\n   public ObservableCollection&lt;FileViewModel&gt; AllImages\n   {\n       get { return _allImages; }\n   }\n\n   public int DataItemsCount\n   {\n       get\n       {\n           return dataItemsCount;\n       }\n       private set\n       {\n           dataItemsCount = value;\n           OnPropertyChanged(\"DataItemsCount\");\n       }\n   }\n\n   public ImageFileCollectionViewModel()\n   {\n       this._allImages = new ObservableCollection&lt;FileViewModel&gt;();\n       this.DataItemsCount = 0;\n   }\n\n   public void AddNewPhotoItem(IImageFile imageFile)\n   {\n       FileViewModel newImageFileViewModel = new FileViewModel(imageFile);\n       this._allImages.Add(newImageFileViewModel);\n       this.DataItemsCount++;\n   }\n          \n   public event PropertyChangedEventHandler PropertyChanged;\n\n   private void OnPropertyChanged(string propertyName)\n   {\n       if (PropertyChanged != null)\n       {\n           PropertyChanged(this, new PropertyChangedEventArgs(propertyName));\n       }\n   }\n}\n<\/pre>\n<\/div>\n<h3>La couche View<\/h3>\n<p>L\u2019affichage de la liste se fait ensuite avec une vue XAML classique. Pour am\u00e9liorer la r\u00e9utilisation et faciliter la maintenance j\u2019ai cr\u00e9\u00e9 un <strong>UserControl <\/strong>\u00ab <strong>ImageFileListView <\/strong>\u00bb qui contient uniquement la liste des images.<\/p>\n<p>Voici l\u2019impl\u00e9mentation de ce UserControl. Notez que le <strong>Binding<\/strong> se fait sur les propri\u00e9t\u00e9s des objets ViewModel<\/p>\n<div id=\"scid:57F11A72-B0E5-49c7-9094-E3A15BD5B5E6:ee98a33f-c5de-4707-ada4-746821280cbb\" class=\"wlWriterEditableSmartContent\" style=\"margin: 0px;padding: 0px;float: none\">\n<pre>&lt;UserControl x:Class=\"wpfListViewSample01.View.ImageFileListView\"\n    xmlns=\"http:\/\/schemas.microsoft.com\/winfx\/2006\/xaml\/presentation\"\n    xmlns:x=\"http:\/\/schemas.microsoft.com\/winfx\/2006\/xaml\"\n    xmlns:local=\"clr-namespace:wpfListViewSample01.View\"&gt;\n    \n    &lt;ListView SelectionMode=\"Extended\"  x:Name=\"ListViewImage\"\n              ItemsSource=\"{Binding Path=AllImages}\"  Margin=\"0,20,0,0\"&gt;\n        &lt;ListView.ItemTemplate&gt;\n            &lt;DataTemplate&gt;\n                &lt;StackPanel Orientation=\"Horizontal\"&gt;\n                    &lt;TextBlock Text=\"{Binding Path=ShortName}\" \/&gt;\n                \u00a0&lt;\/StackPanel &gt;\n            &lt;\/DataTemplate\u00a0\u00a0&gt;\n        &lt;\/ListView.ItemTemplate&gt; \u00a0\u00a0\n    &lt;\/ListView\u00a0&gt;\n\u00a0&lt;\/UserControl\u00a0&gt;\n<\/pre>\n<\/div>\n<p>Ce UserControl est appel\u00e9 de la fa\u00e7on suivante dans la fen\u00eatre principale <strong>Window.xaml<\/strong>.<br \/>\nNotez que \u201c<strong>label2<\/strong>\u201d est <strong>bind\u00e9<\/strong> sur la propri\u00e9t\u00e9 <strong>DataItemsCount<\/strong> de l\u2019objet ViewModel<\/p>\n<p>&nbsp;<\/p>\n<div id=\"scid:57F11A72-B0E5-49c7-9094-E3A15BD5B5E6:154612a7-7c7d-4154-9b9f-9ef3deeb641b\" class=\"wlWriterEditableSmartContent\" style=\"margin: 0px;padding: 0px;float: none\">\n<pre>&lt;Window x:Class=\"wpfListViewSample01.View.Window1\"\n    xmlns=\"http:\/\/schemas.microsoft.com\/winfx\/2006\/xaml\/presentation\"\n    xmlns:x=\"http:\/\/schemas.microsoft.com\/winfx\/2006\/xaml\"\n    xmlns:m=\"clr-namespace:wpfListViewSample01.View\"\n    Title=\"Window1\" Height=\"400\" Width=\"571\"&gt;\n    &lt;Grid&gt;\n        &lt;Grid.ColumnDefinitions&gt;\n            &lt;ColumnDefinition Width=\"515*\" \/&gt;\n            &lt;ColumnDefinition Width=\"34*\" \/&gt;\n        &lt;\/Grid.ColumnDefinitions&gt;\n        &lt;Label Height=\"25\" HorizontalAlignment=\"Left\" Margin=\"12,17,0,0\" \n        Name=\"label1\" VerticalAlignment=\"Top\" Width=\"108\"&gt;Images Directory&lt;\/Label\u00a0&gt;\n        &lt;TextBox Height=\"25\" Margin=\"126,17,12,0\" Name=\"textBoxImageDirectory\"\n        VerticalAlignment=\"Top\" Grid.ColumnSpan=\"2\" \/&gt;\n        &lt;Button Height=\"29\" HorizontalAlignment=\"Left\" Margin=\"12,56,0,0\" \n        Name=\"BtnOk\" VerticalAlignment=\"Top\" Width=\"106\" Click=\"BtnOk_Click\"&gt;    \n        Display!&lt;\/Button\u00a0\u00a0&gt;\n        &lt;m:ImageFileListView x:Name=\"listView1\" Margin=\"12,99,12,22\" \n        ClipToBounds=\"False\" Grid.ColumnSpan=\"2\" \/&gt;\n        &lt;Label Height=\"24\" HorizontalAlignment=\"Right\" Margin=\"0,57,72,0\" \n        Name=\"label2\" VerticalAlignment=\"Top\" Width=\"170\" \n        Content=\"{Binding Path=DataItemsCount}\"&gt;&lt;\/Label&gt;\n  &lt;\/Grid\u00a0\u00a0\u00a0&gt;\n&lt;\/Window\u00a0&gt;\n<\/pre>\n<\/div>\n<p>&nbsp;<\/p>\n<h3>Les liens entre Model, ViewModel et View<\/h3>\n<p>Nous venons de voir que la couche View et ViewModel sont directement reli\u00e9es par le Binding des fichiers <strong>XAML<\/strong>.<br \/>\nAu moment du click sur le bouton, l\u2019alimentation de la liste doit se faire avec le r\u00e9pertoire saisi.<br \/>\nSur le click du bouton, j\u2019ai cod\u00e9 quelques actions.<\/p>\n<ol>\n<li>Cr\u00e9ation de l\u2019objet ViewModel <strong>ImageFileCollectionViewModel<\/strong> qui va contenir l\u2019ensemble des images.<\/li>\n<li>Alimentation de la liste des fichiers images pr\u00e9sents dans le r\u00e9pertoire <strong>textBoxImageDirectory.Text.<\/strong><\/li>\n<li>Initialisation des <strong>View <\/strong>avec la propri\u00e9t\u00e9 DataContext de chaque composant visuel.<\/li>\n<\/ol>\n<p>Voici une \u00e9criture possible.<\/p>\n<div id=\"scid:57F11A72-B0E5-49c7-9094-E3A15BD5B5E6:091f4f1c-4495-4d06-8d3e-5be44724e688\" class=\"wlWriterEditableSmartContent\" style=\"margin: 0px;padding: 0px;float: none\">\n<pre>private void BtnOk_Click(object sender, RoutedEventArgs e)\n{\n  ImageFileCollectionViewModel ImagesViewModel \n    = new ImageFileCollectionViewModel();\n  ImageFileControler.CompleteViewList(ImagesViewModel, textBoxImageDirectory.Text);\n  listView1.DataContext = ImagesViewModel;\n  label2.DataContext = ImagesViewModel;\n};<\/pre>\n<\/div>\n<p><strong>ImageFileControler.CompleteViewList<\/strong> recherche les fichiers *.jp* du r\u00e9pertoire pass\u00e9 en param\u00e8tre et les aoute un par un dans la liste de type ImageFileCollectionViewModel.<\/p>\n<p>&nbsp;<\/p>\n<div id=\"scid:57F11A72-B0E5-49c7-9094-E3A15BD5B5E6:5086af82-34b4-4d0b-9dd5-058064995caa\" class=\"wlWriterEditableSmartContent\" style=\"margin: 0px;padding: 0px;float: none\">\n<pre>public static void CompleteViewList(\n  ImageFileCollectionViewModel imageFileCollecion, string directory)\n{\n  string[] files = Directory.GetFiles(directory, \"*.jp*\");\n  foreach (var f in files)\n  {\n      var im = new ImageFile();\n      im.FileName = f;\n      imageFileCollecion.AddNewPhotoItem(im);\n  }\n}<\/pre>\n<\/div>\n<p>&nbsp;<\/p>\n<h2>Ex\u00e9cution de l\u2019application<\/h2>\n<p>L\u2019application est loin d\u2019\u00eatre termin\u00e9e mais voici une premi\u00e8re ex\u00e9cution.<\/p>\n<p><a href=\"http:\/\/www.bratched.com\/images\/stories\/image_a84701f2aca94a6919a91747c0e66534.png\"><img decoding=\"async\" loading=\"lazy\" title=\"WPF MVVM Application example\" src=\"http:\/\/dev.bratched.fr\/fr\/wp-content\/uploads\/sites\/2\/importedmedia\/blogmedia-image-thumb-66c1fe0864b27ac5527cd5c11d4cbcca-png.png\" alt=\"WPF MVVM Application example\" width=\"362\" height=\"255\" border=\"0\" \/><\/a><\/p>\n<h2>Les tests unitaires<\/h2>\n<p>Et les tests alors?<br \/>\nNormalement les tests unitaires sont fait en m\u00eame temps, voir m\u00eame avant l\u2019\u00e9criture des diff\u00e9rentes impl\u00e9mentations.<\/p>\n<p>J\u2019ai pr\u00e9f\u00e9r\u00e9 poser le d\u00e9cor avant d\u2019aborder cette partie qui a bien sur \u00e9t\u00e9 r\u00e9alis\u00e9e avant l\u2019impl\u00e9mentation des diff\u00e9rentes m\u00e9thodes .<br \/>\nLe d\u00e9coupage MVVM va nous faciliter la t\u00e2che en terme de tests unitaires.<\/p>\n<p>Trois tests unitaires ont \u00e9t\u00e9 ajout\u00e9s sur la partie ViewModel. Le but est de tester les interactions entre l\u2019affichage et les objets m\u00e9tiers du <strong>ImageFileCollectionViewModel<\/strong>.<\/p>\n<ul>\n<li><strong>DataItemsCountTest<\/strong> : Test de ImageFileCollectionViewModel.DataItemsCount<\/li>\n<li><strong>AllImagesTest<\/strong> : Test de ImageFileCollectionViewModel.AllImages<\/li>\n<li><strong>AddNewPhotoItemTest<\/strong> : Test de ImageFileCollectionViewModel.AddNewPhotoItem<\/li>\n<\/ul>\n<p>Les tests unitaires sont cr\u00e9\u00e9s avec l\u2019assistant Visual Studio 2008 (Depuis <strong>ImageFileCollectionViewModel <\/strong>click droit, Cr\u00e9er des Tests unitaires&#8230; )<\/p>\n<h3>DataItemsCountTest ()<\/h3>\n<p>Il s\u2019agit de tester le Nombre d\u2019\u00e9l\u00e9ments d\u2019objets m\u00e9tiers ImageFile ajout\u00e9s dans la liste.<\/p>\n<ol>\n<li>Cr\u00e9ation de l\u2019objet <strong>ImageCollectionViewModel<\/strong><\/li>\n<li>Test si la propri\u00e9t\u00e9 DataItemsCount est bien = 0<\/li>\n<li>Ajout de 2 fichiers images Fictifs<\/li>\n<li>Test si la propri\u00e9t\u00e9 DataItemsCount est bien = 2<\/li>\n<\/ol>\n<p>Voici le code.<\/p>\n<p>&nbsp;<\/p>\n<div id=\"scid:57F11A72-B0E5-49c7-9094-E3A15BD5B5E6:7f17568e-a4ee-4060-84ed-e4879a0200ae\" class=\"wlWriterEditableSmartContent\" style=\"margin: 0px;padding: 0px;float: none\">\n<pre>[TestMethod()]\npublic void DataItemsCountTest()\n{\n  ImageFileCollectionViewModel target = new ImageFileCollectionViewModel();\n  Assert.AreEqual(target.DataItemsCount, 0);\n  target.AddNewPhotoItem(new ImageFile() { FileName = \"c:test1.jpg\" });\n  target.AddNewPhotoItem(new ImageFile() { FileName = \"c:test2.jpg\" });\n  Assert.AreEqual(target.DataItemsCount, 2); \n}<\/pre>\n<\/div>\n<p>&nbsp;<\/p>\n<h3>AllImagesTest()<\/h3>\n<p>Il s\u2019agit de tester les \u00e9l\u00e9ments pr\u00e9sents dans AllImages. La technique est la m\u00eame. Attention ici AllImages repr\u00e9sente le d\u00e9tail des ViewModel et non pas la liste des objets m\u00e9tiers.<\/p>\n<ol>\n<li>\n<ol>\n<li>Cr\u00e9ation de l\u2019objet <strong>ImageCollectionViewModel<\/strong><\/li>\n<li>Test si la propri\u00e9t\u00e9 AllImages.Count est bien = 0<\/li>\n<li>Ajout de 1 \u00e9l\u00e9ment<\/li>\n<li>Test si la propri\u00e9t\u00e9 AllImages.Count est bien = 1<\/li>\n<li>Test si le premier \u00e9l\u00e9ment retourne bien l\u2019affichage attendu<\/li>\n<\/ol>\n<\/li>\n<\/ol>\n<div id=\"scid:57F11A72-B0E5-49c7-9094-E3A15BD5B5E6:ad7ea885-801b-461a-92d6-90d22d5d484f\" class=\"wlWriterEditableSmartContent\" style=\"margin: 0px;padding: 0px;float: none\">\n<pre>[TestMethod()]\npublic void AllImagesTest()\n{\n  ImageFileCollectionViewModel target = new ImageFileCollectionViewModel();\n  Assert.AreEqual(target.AllImages.Count, 0);\n  target.AddNewPhotoItem(new ImageFile() { FileName = @\"ImagesTest1.jpg\" });\n  Assert.AreEqual(target.AllImages.Count, 1);\n  Assert.AreEqual(target.AllImages[0].ShortName, \"Test1.jpg\");\n  \n}\n<\/pre>\n<\/div>\n<h2><strong>AddNewPhotoItemTest<\/strong>()<\/h2>\n<p>Le meilleur pour la fin. L\u2019objectif est de tester l\u2019ajout d\u2019un \u00e9l\u00e9ment. Le but n\u2019est pas de tester cet ajout \u00e0 travers AllImages mais de tester directement l\u2019attribut priv\u00e9 _allImages.<br \/>\nIl faut donc un acc\u00e8s \u00e0 l\u2019attribut priv\u00e9 \u201c_allImages\u201d. La premi\u00e8re \u00e9tape \u00e0 r\u00e9aliser est de se placer sur l\u2019attribut _allImages et de faire click droit puis\u201cCr\u00e9er un accesseur priv\u00e9&rsquo;\u201d.<\/p>\n<p>Ensuite vous pourrez utiliser un code de ce type :<\/p>\n<ol>\n<li>Cr\u00e9ation de l\u2019objet temp de type <strong>ImageCollectionViewModel<\/strong><\/li>\n<li>Cr\u00e9er un <strong>PrivateObject<\/strong> \u00e0 partir de l\u2019objet <strong>temp<\/strong> pr\u00e9c\u00e9dent<\/li>\n<li>Cr\u00e9ation de l\u2019objet <strong>target<\/strong> \u00e0 tester \u00e0 partir de l\u2019objet Accessor <strong>ImageCollectionViewModel_Accessor<\/strong> qui prend en param\u00e8tre l\u2019objet priv\u00e9 .<\/li>\n<li>Ajout d\u20191 \u00e9l\u00e9ment<\/li>\n<li>Test en utilisant _allImages.Count = 1<\/li>\n<li>test du type retourn\u00e9 par le premier \u00e9l\u00e9ment de _allImages<\/li>\n<\/ol>\n<p>&nbsp;<\/p>\n<div id=\"scid:57F11A72-B0E5-49c7-9094-E3A15BD5B5E6:eeb85cbe-2cc8-491d-8c7a-c9cfad201e0c\" class=\"wlWriterEditableSmartContent\" style=\"margin: 0px;padding: 0px;float: none\">\n<pre>[TestMethod()]\npublic void AddNewPhotoItemTest()\n{\n  ImageFileCollectionViewModel temp = new ImageFileCollectionViewModel(); \n  PrivateObject param0 = new PrivateObject(temp);\n  ImageFileCollectionViewModel_Accessor target = new ImageFileCollectionViewModel_Accessor(param0);\n  target.AddNewPhotoItem(new ImageFile() { FileName = @\"c:test1.jpg\" });\n  Assert.AreEqual(target._allImages.Count, 1);\n  Assert.AreEqual(target._allImages[0].GetType().FullName,\n  \"wpfListViewSample01.ViewModel.FileViewModel\");\n}<\/pre>\n<\/div>\n<p>&nbsp;<\/p>\n<h2>Ex\u00e9cution des tests<\/h2>\n<p><a href=\"http:\/\/www.bratched.com\/images\/stories\/tmpEE5B.png\"><img decoding=\"async\" loading=\"lazy\" title=\"Windows Test\" src=\"http:\/\/dev.bratched.fr\/fr\/wp-content\/uploads\/sites\/2\/importedmedia\/blogmedia-tmpee5b-thumb-png.png\" alt=\"Windows Test\" width=\"535\" height=\"128\" border=\"0\" \/><\/a><\/p>\n<p>Magique! tout marche \u00e0 merveille.<\/p>\n<h2>Conclusion<\/h2>\n<p>Nous venons de voir une impl\u00e9mentation possible en WPF avec le pattern MVVM d\u2019une application affichant une liste d\u2019images.<\/p>\n<p>Nous verrons dans la prochaine \u00e9tape l\u2019affichage des vignettes, les contraintes de performances, les probl\u00e8mes de g\u00e8le d\u2019interface et la virtualisation des donn\u00e9es.<\/p>\n<p>Voici le projet complet \u00e0 t\u00e9l\u00e9charger<\/p>\n<p><a title=\"Example project solution\" href=\"http:\/\/www.bratched.com\/download\/WPFListView01.zip\"><img decoding=\"async\" loading=\"lazy\" title=\"download32x32\" src=\"http:\/\/dev.bratched.fr\/fr\/wp-content\/uploads\/sites\/2\/importedmedia\/blogmedia-download32x32-400638461a9291bd7d4307505ba91ef0-gif.gif\" alt=\"download32x32\" width=\"32\" height=\"32\" border=\"0\" \/> WPFListView01.zip<\/a><\/p>\n<p>&nbsp;<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Introduction Pour les besoins d\u2019un logiciel orient\u00e9 \u00ab Photos \u00bb, j\u2019ai eu besoin des fonctionnalit\u00e9s essentielles suivantes : Affichage des images sous forme de vignettes dans une liste et dans une arborescence de type TreeView Cet affichage doit permettre de g\u00e9rer un nombre imposant de photos (&gt;5000 !) sans g\u00e9n\u00e9rer de d\u00e9bordement m\u00e9moire ou de [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[6],"tags":[2,3,4,5,7,8],"aioseo_notices":[],"_links":{"self":[{"href":"https:\/\/bratched.com\/fr\/wp-json\/wp\/v2\/posts\/436"}],"collection":[{"href":"https:\/\/bratched.com\/fr\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/bratched.com\/fr\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/bratched.com\/fr\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/bratched.com\/fr\/wp-json\/wp\/v2\/comments?post=436"}],"version-history":[{"count":0,"href":"https:\/\/bratched.com\/fr\/wp-json\/wp\/v2\/posts\/436\/revisions"}],"wp:attachment":[{"href":"https:\/\/bratched.com\/fr\/wp-json\/wp\/v2\/media?parent=436"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/bratched.com\/fr\/wp-json\/wp\/v2\/categories?post=436"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/bratched.com\/fr\/wp-json\/wp\/v2\/tags?post=436"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}