{"id":472,"date":"2013-05-23T07:24:42","date_gmt":"2013-05-23T05:24:42","guid":{"rendered":"http:\/\/dev.bratched.fr\/fr\/wpf-listview-dimages-mvvm-et-drag-and-drop-partie-3-5\/"},"modified":"2013-05-23T07:24:42","modified_gmt":"2013-05-23T05:24:42","slug":"wpf-listview-dimages-mvvm-et-drag-and-drop-partie-3","status":"publish","type":"post","link":"https:\/\/bratched.com\/fr\/2013\/05\/23\/wpf-listview-dimages-mvvm-et-drag-and-drop-partie-3\/","title":{"rendered":"WPF, ListView d\u2019images, MVVM et Drag and Drop\u2026 \u2013 Partie 3"},"content":{"rendered":"<h1>Introduction<\/h1>\n<h2>Vu pr\u00e9c\u00e9demment<\/h2>\n<p>Nous avons vu dans la premi\u00e8re partie comment impl\u00e9menter une liste de fichiers en WPF avec le pattern MVVM.<\/p>\n<p>Dans la deuxi\u00e8me partie, nous avons vus comment optimiser l\u2019interface utilisateur avec le <strong>VirtualizingStackPanel<\/strong>, le multi Thread, les animations d\u2019attente WPF. Nous avons eu \u00e9galement un aper\u00e7u de la manipulation des images pour utiliser lorsqu\u2019elles existent les informations EXIF ou encore une transformation classique de ces images.<\/p>\n<p>Je vous propose de voir dans cette troisi\u00e8me partie :<\/p>\n<ul>\n<li>La mise en cache des images (am\u00e9liorons l\u2019interface utilisateur encore un petit peu)<\/li>\n<li>Le Drag and Drop avec une s\u00e9lection multiple d\u2019\u00e9l\u00e9ments<\/li>\n<\/ul>\n<h1>Optimisation avec mise en cache<\/h1>\n<h2>Impl\u00e9mentation d\u2019un syst\u00e8me de cache<\/h2>\n<p>Le but est de garder les x derni\u00e8res images d\u00e9j\u00e0 charg\u00e9e afin d\u2019\u00e9viter un rechargement permanent de ces images.<\/p>\n<p>Pour cela nous mettons en place une liste partag\u00e9e en appel static, ainsi que son verrou (nous sommes dans un environnement multi-thread) afin de garantir l\u2019\u00e9criture correcte des donn\u00e9es.<\/p>\n<div id=\"scid:57F11A72-B0E5-49c7-9094-E3A15BD5B5E6:29c88816-9c83-4cde-8bd2-1c070602d5fb\" class=\"wlWriterEditableSmartContent\" style=\"margin: 0px;padding: 0px;float: none\">\n<pre>const int maxThumbnailInCache = 300;\nprivate static readonly List&lt;ImageFileViewModel&gt; _cacheThumbnail \n= new List&lt;ImageFileViewModel&gt;();\nprivate static readonly object _lockCacheThumbnail = new object();\n<\/pre>\n<\/div>\n<p>Puis nous impl\u00e9mentons la m\u00e9thode static <strong>AddThumbnailInCache<\/strong><\/p>\n<p>&nbsp;<\/p>\n<div id=\"scid:57F11A72-B0E5-49c7-9094-E3A15BD5B5E6:d03226fc-c5a2-46ea-9d34-9d4814c2ba9e\" class=\"wlWriterEditableSmartContent\" style=\"margin: 0px;padding: 0px;float: none\">\n<pre> private static void AddThumbnailInCache(ImageFileViewModel p)\n{\n  if (p == null) return;\n  if (_cacheThumbnail.Contains(p))\n  {\n      lock (_lockCacheThumbnail)\n      {\n          _cacheThumbnail.Remove(p);\n      }\n  }\n\n  lock (_lockCacheThumbnail)\n  {\n      _cacheThumbnail.Add(p);\n      if (_cacheThumbnail.Count &gt; maxThumbnailInCache)\n          lock (_lockCacheThumbnail)\n          {\n              _cacheThumbnail.Remove(_cacheThumbnail[0]);\n          }\n  }\n}<\/pre>\n<\/div>\n<p>La m\u00e9thode teste si l\u2019\u00e9l\u00e9ment est pr\u00e9sent. Si il est pr\u00e9sent, l\u2019\u00e9l\u00e9ment est replac\u00e9 \u00e0 la fin de la liste.<\/p>\n<p>Si le nombre d\u2019\u00e9l\u00e9ments d\u00e9passe le maximum d\u2019\u00e9l\u00e9ment fix\u00e9s dans le cache (<strong>maxThumbnailInCache<\/strong>), l\u2019\u00e9l\u00e9ment en haut (le plus ancien) est supprim\u00e9.<!--more--><\/p>\n<p>Cette m\u00e9thode est appel\u00e9e lorsque l\u2019image est charg\u00e9e en m\u00e9moire.<\/p>\n<div id=\"scid:57F11A72-B0E5-49c7-9094-E3A15BD5B5E6:36dd5810-489d-43d4-a87a-95e53dac9117\" class=\"wlWriterEditableSmartContent\" style=\"margin: 0px;padding: 0px;float: none\">\n<pre> public ImageSource Thumbnail\n{\n  get\n  {\n      if (_thumbnail == null)\n      {\n          LoadImage();\n          return null;\n      }\n\n      AddThumbnailInCache(this);\n      return _thumbnail;\n  }\n}<\/pre>\n<\/div>\n<p>Nous modifions maintenant l\u2019instruction de lib\u00e9ration des \u00e9l\u00e9ments pour ne supprimer que ceux absent de la liste<\/p>\n<div id=\"scid:57F11A72-B0E5-49c7-9094-E3A15BD5B5E6:b17b707e-c044-4495-af6c-c57fb1b90fc2\" class=\"wlWriterEditableSmartContent\" style=\"margin: 0px;padding: 0px;float: none\">\n<pre> internal bool CleanUp()\n{\n  if (!_cacheThumbnail.Contains(this))\n  {\n      lock(_lockCacheThumbnail)\n      {   lock (_lockListThumbnail)\n          {\n              _listThumbnail.Remove(this);\n              _loading = false;\n              _thumbnail = null;\n          }\n      }\n      return false;\n  }\n  else\n    return true;\n}<\/pre>\n<\/div>\n<p>Puis dans la partie View, nous demandons \u00e0 ne pas d\u00e9charger l\u2019\u00e9l\u00e9ment si CleanUp n\u2019a pas r\u00e9ussi (cas o\u00f9 l\u2019on garde l\u2019\u00e9l\u00e9ment en cache).<\/p>\n<p>&nbsp;<\/p>\n<div id=\"scid:57F11A72-B0E5-49c7-9094-E3A15BD5B5E6:ef6e4e64-3b2a-4e41-813b-00dd0b1d1095\" class=\"wlWriterEditableSmartContent\" style=\"margin: 0px;padding: 0px;float: none\">\n<pre>namespace wpfListViewSample03.View\n{\n    public partial class ImageFileListView : UserControl\n    {\n        public ImageFileListView()\n        {\n            InitializeComponent();\n        }\n\n        private void ListViewImage_CleanUpVirtualizedItem\n        (object sender, CleanUpVirtualizedItemEventArgs e)\n        {\n            if (e.Value is ImageFileViewModel)\n            {\n                e.Cancel = (e.Value as ImageFileViewModel).CleanUp();\n            }\n        }       \n    }\n}\n<\/pre>\n<\/div>\n<p>Ex\u00e9cution :<\/p>\n<p><a href=\"http:\/\/www.bratched.com\/images\/stories\/tmp31B.png\"><img decoding=\"async\" loading=\"lazy\" style=\"border-width: 0px\" title=\"tmp31B\" src=\"http:\/\/dev.bratched.fr\/fr\/wp-content\/uploads\/sites\/2\/importedmedia\/blogmedia-tmp31b-thumb-png.png\" alt=\"tmp31B\" width=\"487\" height=\"519\" border=\"0\" \/><\/a><\/p>\n<p>Et voila, 316 \u00e9l\u00e9ments (300 en cache + 16 affich\u00e9s) sont gard\u00e9s en cache, la vignette (<strong>thumbnail)<\/strong> ne sera pas ainsi constamment r\u00e9g\u00e9n\u00e9r\u00e9e.<\/p>\n<h2>Les tests unitaires<\/h2>\n<p>Les tests unitaires associ\u00e9s sont assez simples. Il faudra penser \u00e0 ajouter le vidage de la liste <strong>_CacheThumbnail<\/strong>.<\/p>\n<div id=\"scid:57F11A72-B0E5-49c7-9094-E3A15BD5B5E6:2f98f9a0-4da0-4f88-8827-70c5b20f21ac\" class=\"wlWriterEditableSmartContent\" style=\"margin: 0px;padding: 0px;float: none\">\n<pre>[TestInitialize()]\npublic void MyTestInitialize()\n{\n  \/\/ Clear Thread processus\n  ImageFileViewModel_Accessor._loaderThreadThumbnail.Abort();\n  \/\/ call the static constructor \n  Type staticType = typeof(ImageFileViewModel);\n  ConstructorInfo ci = staticType.TypeInitializer;\n  object[] parameters = new object[0];\n  ci.Invoke(null, parameters);\n  \/\/ and clear static attribute _ListThumbnails and _cacheThumbnails\n  ImageFileViewModel_Accessor._listThumbnail.Clear();\n  ImageFileViewModel_Accessor._cacheThumbnail.Clear();\n}<\/pre>\n<\/div>\n<h1>Le Drag And Drop dans une ListBox WPF avec M-V-VM<\/h1>\n<h2>Drag and Drop, Quoi de plus ?<\/h2>\n<p>Le drag and drop de ListBox (ou ListView) a d\u00e9j\u00e0 \u00e9t\u00e9 trait\u00e9 dans de nombreux articles de qualit\u00e9 dont je me suis inspir\u00e9 pour r\u00e9aliser cette prochaine version.<\/p>\n<p>Voici la liste de ces articles<\/p>\n<ul>\n<li><a href=\"http:\/\/geekswithblogs.net\/hinshelm\/archive\/2009\/08\/14\/wpf-drag-amp-drop-behaviour.aspx\" target=\"_blank\" rel=\"nofollow noopener noreferrer\">http:\/\/geekswithblogs.net\/hinshelm\/archive\/2009\/08\/14\/wpf-drag-amp-drop-behaviour.aspx<\/a><\/li>\n<li><a href=\"http:\/\/bea.stollnitz.com\/blog\/?p=53\" target=\"_blank\" rel=\"nofollow noopener noreferrer\">http:\/\/bea.stollnitz.com\/blog\/?p=53<\/a><\/li>\n<li><a href=\"http:\/\/www.codeproject.com\/KB\/WPF\/ListViewDragDropManager.aspx\" target=\"_blank\" rel=\"nofollow noopener noreferrer\">http:\/\/www.codeproject.com\/KB\/WPF\/ListViewDragDropManager.aspx<\/a><\/li>\n<li><a href=\"http:\/\/blogs.msdn.com\/jaimer\/archive\/2007\/07\/12\/drag-drop-in-wpf-explained-end-to-end.aspx\" target=\"_blank\" rel=\"nofollow noopener noreferrer\">http:\/\/blogs.msdn.com\/jaimer\/archive\/2007\/07\/12\/drag-drop-in-wpf-explained-end-to-end.aspx<\/a><\/li>\n<li><a href=\"http:\/\/geekswithblogs.net\/sonam\/archive\/2009\/03\/02\/listview-dragdrop-in-wpfmultiselect.aspx\" target=\"_blank\" rel=\"nofollow noopener noreferrer\">http:\/\/geekswithblogs.net\/sonam\/archive\/2009\/03\/02\/listview-dragdrop-in-wpfmultiselect.aspx<\/a><\/li>\n<\/ul>\n<p>Ce qui manquait, une version g\u00e9n\u00e9rale (chaque petit bout \u00e9tant dans dans la plupart de ces articles) avec<\/p>\n<ul>\n<li>La gestion du d\u00e9placement d\u2019un Item dans une ListBox ou ListView. La plupart font une copie des \u00e9l\u00e9ments mais ne g\u00e8rent pas les d\u00e9placements<\/li>\n<li>La multi-s\u00e9lection, la copie ou le d\u00e9placements de plusieurs \u00e9l\u00e9ments s\u00e9lectionn\u00e9s.<\/li>\n<li>Le scroll automatique lorsque l\u2019on d\u00e9place les \u00e9l\u00e9ments en dehors de la fen\u00eatre<\/li>\n<li>Une s\u00e9paration entre la gestion du drag and drop et la couche visuelle d\u2019affichage des Adorners (Aspect graphique indiquant la position des \u00e9l\u00e9ments gliss\u00e9s)<\/li>\n<\/ul>\n<p>J\u2019ai donc r\u00e9alis\u00e9 un premier \u201c<strong>DragAndDropManager<\/strong>\u201d sans les fioritures graphiques puis un h\u00e9ritier qui va uniquement impl\u00e9menter les aspect d\u2019affichage (\u00ab\u00a0Adorners\u00a0\u00bb).<\/p>\n<h2>Organisation du projet Drag And Drop Manager.<\/h2>\n<p>Toute la gestion du drag and drop se retrouve dans un projet unique <strong>DragDropManager<\/strong> qui pourra \u00eatre repris dans un projet ind\u00e9pendant pour vos ListView ou ListBox.<\/p>\n<p>L\u2019organisation du projet est la suivante<\/p>\n<p><a href=\"http:\/\/www.bratched.com\/images\/stories\/tmpAA.png\"><img decoding=\"async\" loading=\"lazy\" style=\"border-width: 0px\" title=\"tmpAA\" src=\"http:\/\/dev.bratched.fr\/fr\/wp-content\/uploads\/sites\/2\/importedmedia\/blogmedia-tmpaa-thumb-png.png\" alt=\"tmpAA\" width=\"320\" height=\"180\" border=\"0\" \/><\/a><\/p>\n<p>La partie Model n\u2019est pas sp\u00e9cifique WPF.<\/p>\n<p>Elle contient 2 classes<\/p>\n<ul>\n<li><strong>DragDropManagerList<\/strong> Le gestionnaire de <strong>DragAndDrop<\/strong> permettant de g\u00e9rer des ensembles, et les m\u00e9canismes de d\u00e9but, fin d\u2019op\u00e9rations de DragAndDrop.<\/li>\n<li><strong>MouseUtilities<\/strong> inclue une fonction permettant de localiser le curseur de la souris au moment du Drag. Il faut savoir que la grosse difficult\u00e9 lors du drag and drop est de pouvoir g\u00e9rer la position du curseur. Microsoft en effet n\u2019a pas pr\u00e9vu jusqu\u2019\u00e0 la version 3.5 de pouvoir suivre le mouvement de la souris lors de cette op\u00e9ration.<\/li>\n<\/ul>\n<p>La partie ViewModel contient 2 classes principales<\/p>\n<ul>\n<li><strong>DragDropManagerListBoxViewModel<\/strong> g\u00e8re les fonctions de drag et drop de la souris avec les composants WPF ListBox. Elle d\u00e9rive de la classe <strong>DragDropManagerList<\/strong> en utilisant ces fonctionnalit\u00e9s de base de DragDropManagerList. Cette classe peut \u00eatre utilis\u00e9e si vous n\u2019avez pas besoin des Adorners<\/li>\n<li><strong>DragDropManagerListBoxAdornerViewModel<\/strong> g\u00e8re l\u2019affichage des 2 Adorners<\/li>\n<\/ul>\n<p>2 classes d\u00e9finissent les Adorners<\/p>\n<ul>\n<li>DropAdorner affiche la partie visuelle (vignette + libell\u00e9 de la s\u00e9lection) en transparence sous le curseur de la souris<\/li>\n<li>InsertionAdorner affiche une ligne noire indiquant l\u2019emplacement destination des \u00e9l\u00e9ments \u00e0 ins\u00e9rer dans la liste.<\/li>\n<\/ul>\n<p>Les 2 classes Adorners ont \u00e9t\u00e9 reprises des projets cit\u00e9s plus haut. Il est possible d\u2019\u00e9crire ses propres Adorners pour personnaliser le type d\u2019affichage<\/p>\n<p><a href=\"http:\/\/www.bratched.com\/images\/stories\/image_21bbc92eeca9de0adb9da46be2156d9a.png\"><img decoding=\"async\" loading=\"lazy\" style=\"border-width: 0px\" title=\"image\" src=\"http:\/\/dev.bratched.fr\/fr\/wp-content\/uploads\/sites\/2\/importedmedia\/blogmedia-image-thumb-69de545397b2fad8f6f582266469836a-png.png\" alt=\"image\" width=\"373\" height=\"110\" border=\"0\" \/><\/a><\/p>\n<p>L\u2019ensemble des classes permettant la gestion du DragAndDrop sont r\u00e9sum\u00e9es ici<\/p>\n<p>&nbsp;<\/p>\n<p><a title=\"class diagram for MVVM Drag and Drop\" href=\"http:\/\/www.bratched.com\/images\/stories\/image_6e1bec6ca779c772fa55d523e64d8824.png\"><img decoding=\"async\" loading=\"lazy\" style=\"border: 0px currentColor\" title=\"image\" src=\"http:\/\/dev.bratched.fr\/fr\/wp-content\/uploads\/sites\/2\/importedmedia\/blogmedia-image-thumb-69cdbddd1536716611a5430175cc2786-png.png\" alt=\"image\" width=\"419\" height=\"443\" border=\"0\" \/><\/a><\/p>\n<p>&nbsp;<\/p>\n<h2>La gestion des \u00e9l\u00e9ments multiples dans le Drag en Drop<\/h2>\n<p>Le drag and drop d\u2019\u00e9l\u00e9ments multiples va passer par une structure Dictionary <strong>_draggedItems<\/strong>.<\/p>\n<p>La cl\u00e9 du dictionnaire va contenir la position de l\u2019\u00e9l\u00e9ment et sa valeur contiendra l\u2019objet de type T G\u00e9n\u00e9rique.<\/p>\n<div id=\"scid:57F11A72-B0E5-49c7-9094-E3A15BD5B5E6:9ee826f6-aca9-4c17-afe4-6b1c8a99e30d\" class=\"wlWriterEditableSmartContent\" style=\"margin: 0px;padding: 0px;float: none\">\n<pre>protected Dictionary&lt;int, T&gt; _draggedItems = new Dictionary&lt;int, T&gt;();<\/pre>\n<\/div>\n<p>La classe DragDropManagerList impl\u00e9mente ensuite 3 m\u00e9thodes statiques permettant de g\u00e9rer le Drop des \u00e9l\u00e9ments s\u00e9lectionn\u00e9s <strong>(_draggedItems<\/strong>).<\/p>\n<ul>\n<li><strong>DoDrop<\/strong> sera appel\u00e9 au moment du Drop des \u00e9l\u00e9ments dans la liste. Il retourne les nouveaux \u00e9l\u00e9ments copi\u00e9s sous forme d\u2019un dictionnaire (SI les \u00e9l\u00e9ments sont d\u00e9plac\u00e9s retourne null). En fonction de la touche de clavier Ctrl utilis\u00e9, les \u00e9l\u00e9ments seront soit copi\u00e9s soit d\u00e9plac\u00e9s.<\/li>\n<li><strong>CopyDraggedItems<\/strong> g\u00e8re la copie des \u00e9l\u00e9ments<\/li>\n<li><strong>MoveDraggedItems<\/strong> g\u00e8re le d\u00e9placement des \u00e9l\u00e9ments<\/li>\n<\/ul>\n<div id=\"scid:57F11A72-B0E5-49c7-9094-E3A15BD5B5E6:490e71b5-a4f1-4ca5-868d-f896792f34ef\" class=\"wlWriterEditableSmartContent\" style=\"margin: 0px;padding: 0px;float: none\">\n<pre>protected static Dictionary&lt;int, T&gt; DoDrop(ObservableCollection&lt;T&gt; itemsSource, Dictionary&lt;int, T&gt; draggedItems\n  , int index, DragDropEffects effects, DragDropKeyStates keyStates)\n{\n  T dropTarget = itemsSource[index];\n  if (draggedItems.ContainsValue(dropTarget)) return null;\n\n  if ((DragDropEffects.Move &amp; effects) == DragDropEffects.Move\n      &amp;&amp; (DragDropKeyStates.ControlKey &amp; keyStates) \n      != DragDropKeyStates.ControlKey)\n      effects = DragDropEffects.Move;\n  if ((DragDropEffects.Copy &amp; effects) == DragDropEffects.Copy)\n      effects = DragDropEffects.Copy;\n\n  switch (effects)\n  {\n      case DragDropEffects.Move:\n          {\n              MoveDraggedItems(itemsSource, draggedItems, index);\n              draggedItems.Clear();\n              return null;\n          }\n\n      case DragDropEffects.Copy:\n          {\n              Dictionary&lt;int, T&gt; newItems = CopyDraggedItems(itemsSource, draggedItems, index);\n              draggedItems.Clear();\n              return newItems;\n          }\n  }\n  return null;\n}\n\nprotected static Dictionary&lt;int, T&gt; CopyDraggedItems(ObservableCollection&lt;T&gt; itemsSource, Dictionary&lt;int, T&gt; draggedItems, int index)\n{\n  Dictionary&lt;int, T&gt; newItems = new Dictionary&lt;int, T&gt;();\n  int i = 0;\n  foreach (int key in draggedItems.Keys.OrderBy(ii =&gt; ii))\n  {\n      T draggedItemClone = draggedItems[key].Clone() as T;\n      if (draggedItemClone != null)\n      {\n          itemsSource.Insert(index + i, draggedItemClone);\n          newItems.Add(index + i, draggedItemClone);\n          i++;\n      }\n  }\n  return newItems;\n}\n\nprotected static void MoveDraggedItems(ObservableCollection&lt;T&gt; itemsSource, Dictionary&lt;int, T&gt; draggedItems, int index)\n{\n  int dest = index;\n  int origin = 0;\n  Dictionary&lt;int, T&gt; completedDraggedItems = draggedItems;\n  T ModelDest = itemsSource[dest];\n  foreach (int key in completedDraggedItems.Keys)\n  {\n      origin = itemsSource.IndexOf(completedDraggedItems[key]);\n      if (itemsSource[dest] != completedDraggedItems[key])\n      {\n          if (draggedItems.ContainsKey(key))\n          {       \n              ModelDest = draggedItems[key];\n          }\n          if (key &gt; index)\n              dest++;\n          itemsSource.Move(origin, dest);\n      }\n  }\n}<\/pre>\n<\/div>\n<p>La classe impl\u00e9mente le m\u00e9canisme de gestion du d\u00e9placement visuel des \u00e9l\u00e9ments durant le Drag.<\/p>\n<p>Les m\u00e9thodes seront surcharg\u00e9es dans les classes h\u00e9rit\u00e9es pour g\u00e9rer les particularit\u00e9s visuelles (ListBox, Adorners,\u2026).<\/p>\n<div id=\"scid:57F11A72-B0E5-49c7-9094-E3A15BD5B5E6:84791d15-3846-4d5c-93ba-0040c7a7e5a2\" class=\"wlWriterEditableSmartContent\" style=\"margin: 0px;padding: 0px;float: none\">\n<pre>protected void DoDragOperation()\n{\n  InitializeDragOperation();\n  try\n  {\n      PerformDragOperation();\n  }\n  finally\n  {\n      FinishDragOperation();\n  }\n}<\/pre>\n<\/div>\n<h2>La gestion du Drag and Drop en M-V-VM<\/h2>\n<p>L\u2019initialisation du DragAndDropManager va se faire de la fa\u00e7on suivante dans la couche View.<\/p>\n<div id=\"scid:57F11A72-B0E5-49c7-9094-E3A15BD5B5E6:11758a11-3b9f-4297-8d69-51aee201525f\" class=\"wlWriterEditableSmartContent\" style=\"margin: 0px;padding: 0px;float: none\">\n<pre>public ImageFileListView()\n{\n  InitializeComponent();\n  new DragDropManagerListBoxViewModel\n     &lt;ImageFileViewModel&gt;(ListViewImage);\n}\n<\/pre>\n<\/div>\n<p>&nbsp;<\/p>\n<p>Le constructeur initialise le composant visuel ListBox ListViewImage avec le type objet ImageFileViewModel.<\/p>\n<p>Dans la classe DragDropManagerListBoxViewModel, les \u00e9v\u00e8nements suivants sont attrap\u00e9s et redirig\u00e9s pour la gestion du Drag and Drop.<\/p>\n<p>&nbsp;<\/p>\n<div id=\"scid:57F11A72-B0E5-49c7-9094-E3A15BD5B5E6:eda24fa5-5f65-4b31-a1e7-3daa9e1dc4cb\" class=\"wlWriterEditableSmartContent\" style=\"margin: 0px;padding: 0px;float: none\">\n<pre>public virtual ListBox ListBox\n{\n  get {return _listBox;}\n  set\n  {\n      if (_listBox != null)\n      {\n          _listBox.PreviewMouseMove -= listBoxPreviewMouseMove;\n          _listBox.PreviewMouseLeftButtonDown -= listBoxPreviewMouseLeftButtonDown;\n          _listBox.PreviewMouseLeftButtonUp -= listBoxPreviewMouseLeftButtonUp;\n          _listBox.DragOver -= listBoxDragOver;\n          _listBox.Drop -= listBoxDrop;\n      }\n      _listBox = value;\n      _listBox.AllowDrop = true;\n      if (_listBox != null)\n      {\n          if (!_listBox.AllowDrop)\n              _listBox.AllowDrop = true;\n          _listBox.PreviewMouseMove += listBoxPreviewMouseMove;\n          _listBox.PreviewMouseLeftButtonDown += listBoxPreviewMouseLeftButtonDown;\n          _listBox.PreviewMouseLeftButtonUp += listBoxPreviewMouseLeftButtonUp;\n          _listBox.DragOver += listBoxDragOver;\n          _listBox.Drop += listBoxDrop;\n\n      }\n  }\n}<\/pre>\n<\/div>\n<ul>\n<li><strong>PreviewMouseLeftButtonDown<\/strong> : Bouton de la souris enfonc\u00e9 : utilis\u00e9 pour initialis\u00e9 l\u2019op\u00e9ration de glisser (drag) et d\u00e9terminer les \u00e9l\u00e9ments \u00e0 placer dans _draggedItems.<\/li>\n<li><strong>PreviewMouseMove<\/strong> : d\u00e9placement de la souris :\u00a0 utilis\u00e9 pour l\u2019affichage des Adorners, l\u2019affichage du curseur (selon appuie du bouton CTRL et la s\u00e9lection de l\u2019item de destination<\/li>\n<li><strong>DragOver<\/strong> : Survol des \u00e9l\u00e9ments lorsque les items sont s\u00e9lectionn\u00e9s. Permet de rafraichir les Adorners et de g\u00e9rer le d\u00e9placement de la barre de scrolling lorsque le curseur est proche des limites Haut et Bas.<\/li>\n<li><strong>Drop<\/strong> : Action d\u00e9finitive du Drag and Drop, Copie ou d\u00e9placement des \u00e9l\u00e9ments<\/li>\n<li><strong>PreviewMouseLeftButtonUp<\/strong> : Bouton de la souris rel\u00e2ch\u00e9, vide les \u00e9l\u00e9ments s\u00e9lectionn\u00e9s (_draggedItems.Clear) .<\/li>\n<\/ul>\n<h2>Le d\u00e9placement automatique des \u00e9l\u00e9ments durant le Drag and Drop<\/h2>\n<div id=\"scid:57F11A72-B0E5-49c7-9094-E3A15BD5B5E6:df727674-b389-4c29-a543-7416174acedf\" class=\"wlWriterEditableSmartContent\" style=\"margin: 0px;padding: 0px;float: none\">\n<pre>protected virtual void listBoxDragOver(object sender, DragEventArgs e)\n{\n  AutoScrollCursorBounds();\n}\n\nprivate void AutoScrollCursorBounds()\n{\n  Point ptMouse = DragDropManager.MouseUtilities.GetMousePosition(_listBox);\n  Rect bounds = VisualTreeHelper.GetDescendantBounds(_listBox);\n  if (ptMouse.Y &gt; bounds.Height - 10 || ptMouse.Y &lt; 10)\n      _listBox.ScrollIntoView(GetCurrentListBoxItem());\n}<\/pre>\n<\/div>\n<p>Durant le d\u00e9placement des \u00e9l\u00e9ments s\u00e9lectionn\u00e9s, la m\u00e9thode <strong>AutoScrollCursorBounds()<\/strong> est appel\u00e9e. Cette m\u00e9thode regarde la position de la Souris, la compare aux bords de la liste et r\u00e9alise le scroll sur l\u2019\u00e9l\u00e9ment s\u00e9lectionn\u00e9 si elle est proche d\u2019un bord.<\/p>\n<h2>La gestion des Adorners (InsertionAdorner et DropAdorner)<\/h2>\n<p align=\"left\">Elle s\u2019effectue avec la classe <strong>DragDropManagerListBoxAdornerViewModel.<\/strong><\/p>\n<p align=\"left\"><strong>InsertionAdorner<\/strong> et <strong>DropAdorner<\/strong> sont 2 classes trouv\u00e9es sur les diff\u00e9rents forums.<\/p>\n<p align=\"left\">Josh Smith a \u00e9crit le <strong>DropAdorner<\/strong> et l\u2019<strong>InsertionAdorner<\/strong> provient du site de bea Stollnitz.<\/p>\n<p align=\"left\"><a href=\"http:\/\/www.bratched.com\/images\/stories\/image_21bbc92eeca9de0adb9da46be2156d9a.png\"><img decoding=\"async\" loading=\"lazy\" style=\"border-width: 0px\" title=\"image\" src=\"http:\/\/dev.bratched.fr\/fr\/wp-content\/uploads\/sites\/2\/importedmedia\/blogmedia-image-thumb-69de545397b2fad8f6f582266469836a-png.png\" alt=\"image\" width=\"373\" height=\"110\" border=\"0\" \/><\/a><\/p>\n<p align=\"left\">La cr\u00e9ation de ces 2 descendants de Adorners intervient \u00e0 2 moment diff\u00e9rents.<\/p>\n<p align=\"left\">Le DropAdorner est cr\u00e9\u00e9 d\u00e8s le d\u00e9but de l\u2019op\u00e9ration de Drag (PreviewMouseMove appelle DoDragOperation qui lance InitializeDragOperation()) pour afficher la vignette des \u00e9l\u00e9ments en d\u00e9placement.<\/p>\n<p>&nbsp;<\/p>\n<div id=\"scid:57F11A72-B0E5-49c7-9094-E3A15BD5B5E6:d93ec3e6-5323-4c42-a0f9-89f33f9aa544\" class=\"wlWriterEditableSmartContent\" style=\"margin: 0px;padding: 0px;float: none\">\n<pre>protected override void InitializeDragOperation() \n{\n  base.InitializeDragOperation();\n  if (ListBox.SelectedItem != null)\n  {\n      T draggeditem = ListBox.SelectedItem as T;\n      if (draggeditem != null)\n      {\n          ListBoxItem lvi = GetVisualItem(draggeditem) as ListBoxItem;\n          if (lvi != null)\n              CreateAdornerLayer(lvi);\n      }\n  }\n}<\/pre>\n<\/div>\n<p>Le <strong>InsertionAdorner<\/strong> est cr\u00e9\u00e9 uniquement au survol d\u2019un \u00e9l\u00e9ment s\u00e9lectionnable et d\u00e9truit d\u00e8s que le curseur change de ligne.<\/p>\n<div id=\"scid:57F11A72-B0E5-49c7-9094-E3A15BD5B5E6:318195cf-c286-4672-b6c4-548a3e81ad7c\" class=\"wlWriterEditableSmartContent\" style=\"margin: 0px;padding: 0px;float: none\">\n<pre>private void ListBoxDragEnter(object sender, DragEventArgs e)\n{\n  if (dragAdorner != null &amp;&amp; dragAdorner.Visibility != Visibility.Visible)\n  {\n      UpdateDragAdornerLocation();\n      dragAdorner.Visibility = Visibility.Visible;\n  }\n\n  object draggedItem = e.Data.GetData(format.Name);\n  if (draggedItem != null)\n  {\n      CreateInsertionAdorner();\n  }\n}\n\nprivate void ListBoxPreviewDragLeave(object sender, DragEventArgs e)\n{\n  object draggedItem = e.Data.GetData(format.Name);\n  if (draggedItem != null)\n  {\n      RemoveInsertionAdorner();\n  }\n}<\/pre>\n<\/div>\n<p>&nbsp;<\/p>\n<p>Lors de l\u2019arr\u00eat de la suppression du d\u00e9placement par rel\u00e2chement de la souris, les Adorners sont supprim\u00e9s.<\/p>\n<div id=\"scid:57F11A72-B0E5-49c7-9094-E3A15BD5B5E6:6f41ca68-fad1-4f09-888e-4125c73a6ec6\" class=\"wlWriterEditableSmartContent\" style=\"margin: 0px;padding: 0px;float: none\">\n<pre>protected override void FinishDragOperation()\n{\n  RemoveDragAdorner();\n  RemoveInsertionAdorner();\n  base.FinishDragOperation();\n}<\/pre>\n<\/div>\n<p>Au cours du d\u00e9placement du pointeur de la souris, la position du DropAdorner est mise \u00e0 jour.<\/p>\n<p>Le nouveau positionnement est calcul\u00e9 et pass\u00e9 \u00e0 l\u2019objet Adorner (dragAdorners.SetOffsets(left, top);<\/p>\n<div id=\"scid:57F11A72-B0E5-49c7-9094-E3A15BD5B5E6:ef262c84-75cb-4dd8-a21e-93d242af40e0\" class=\"wlWriterEditableSmartContent\" style=\"margin: 0px;padding: 0px;float: none\">\n<pre>private void UpdateDragAdornerLocation()\n{\n  if (this.dragAdorner != null)\n  {\n      Point ptCursor = DragDropManager.MouseUtilities.GetMousePosition(ListBox);\n      double left = ptCursor.X - ptMouseDown.X;\n      double top = ptCursor.Y;\n      dragAdorner.SetOffsets(left, top);\n  }\n}<\/pre>\n<\/div>\n<p>Le DragAndDrop avec Adorners s\u2019utilise simplement, vous devez utiliser la classe <strong>DragDropManagerListBoxAdornerViewModel<\/strong> dans le constructeur de la fa\u00e7on suivante :<\/p>\n<div id=\"scid:57F11A72-B0E5-49c7-9094-E3A15BD5B5E6:0156c640-b872-4b78-8a36-e27af7433a68\" class=\"wlWriterEditableSmartContent\" style=\"margin: 0px;padding: 0px;float: none\">\n<pre>public ImageFileListView()\n{\n  InitializeComponent();\n  new DragDropManagerListBoxAdornerViewModel\n     &lt;ImageFileViewModel&gt;(ListViewImage);\n}\n<\/pre>\n<\/div>\n<h2>Le probl\u00e8me de s\u00e9lection avec la multi-s\u00e9lection et le Drag and Drop<\/h2>\n<p>Vous l\u2019avez peut \u00eatre d\u00e9j\u00e0 remarqu\u00e9, la multi-s\u00e9lection pose un probl\u00e8me avec le Drag and Drop dans une LisView.<\/p>\n<p>Le bouton de la souris lorsqu\u2019il est press\u00e9 au moment du Drag sur un \u00e9l\u00e9ment d\u00e9j\u00e0 s\u00e9lectionn\u00e9 provoque la d\u00e9-s\u00e9lection de tous les autres \u00e9l\u00e9ments.<\/p>\n<p><a href=\"http:\/\/www.bratched.com\/images\/stories\/image_8eb9762ec4042161a5b6f93096fecea9.png\"><img decoding=\"async\" loading=\"lazy\" style=\"border-width: 0px\" title=\"image\" src=\"http:\/\/dev.bratched.fr\/fr\/wp-content\/uploads\/sites\/2\/importedmedia\/blogmedia-image-thumb-0c71151b4a6a2d8edd6c80ef48e3940e-png.png\" alt=\"image\" width=\"244\" height=\"171\" border=\"0\" \/><\/a> <a href=\"http:\/\/www.bratched.com\/images\/stories\/image_1dc26c151594bb3cbf1537010d1ff0a3.png\"><img decoding=\"async\" loading=\"lazy\" style=\"border-width: 0px\" title=\"image\" src=\"http:\/\/dev.bratched.fr\/fr\/wp-content\/uploads\/sites\/2\/importedmedia\/blogmedia-image-thumb-4c3d9d39fbc7ced19a1d3cdc591ba771-png.png\" alt=\"image\" width=\"244\" height=\"168\" border=\"0\" \/><\/a><\/p>\n<p>Comme il s\u2019agit d\u2019un probl\u00e8me de composant visuel WPF, nous allons traiter ce disfonctionnement dans la partie View en modifiant le comportement du ListViewItem.<\/p>\n<p>Pour cela il faut surcharger la classe ListViewItem et modifier le type d\u2019\u00e9l\u00e9ments renvoy\u00e9 par le ListView<\/p>\n<div id=\"scid:57F11A72-B0E5-49c7-9094-E3A15BD5B5E6:6025e249-f05d-41d2-beab-0f44ce50aff2\" class=\"wlWriterEditableSmartContent\" style=\"margin: 0px;padding: 0px;float: none\">\n<pre>public class ListViewMultiSelect : ListView\n{\n   protected override DependencyObject GetContainerForItemOverride()\n   {\n       return new ListViewItemMultiSelect();\n   }\n}<\/pre>\n<\/div>\n<p>Ensuite, le nouveau ListViewItem (ici ListViewItemMultiSelect) modifie le comportement du click.<\/p>\n<ul>\n<li>Si l\u2019\u00e9l\u00e9ment n\u2019est pas d\u00e9j\u00e0 s\u00e9lectionn\u00e9, la s\u00e9lection est faite normalement \u00e0 la pression du bouton de la souris<\/li>\n<li>Si l\u2019\u00e9l\u00e9ment est s\u00e9lectionn\u00e9, rien n\u2019est fait au moment de\u00a0 la pression du bouton (afin de garder les autres \u00e9l\u00e9ments s\u00e9lectionn\u00e9s) et la gestion de la s\u00e9lection est report\u00e9e au moment du rel\u00e2chement du bouton de la souris.<\/li>\n<\/ul>\n<div id=\"scid:57F11A72-B0E5-49c7-9094-E3A15BD5B5E6:387b4ce9-7a29-4790-ae63-a4e4818f18f6\" class=\"wlWriterEditableSmartContent\" style=\"margin: 0px;padding: 0px;float: none\">\n<pre>public class ListViewItemMultiSelect : ListViewItem\n{\n   private bool _mouseWasDownAndSelected = false;\n   protected override void OnPreviewMouseLeftButtonDown(MouseButtonEventArgs e)\n   {\n       if (!IsSelected)\n       {\n           \/\/ it wasn't selected, so just do the normal thing\n           base.OnPreviewMouseLeftButtonDown(e);\n           return;\n       }\n       \/\/ it was selected, so we're going to totally ignore the mouse down...\n       e.Handled = true;\n       \/\/ but we will mark it ...\n       _mouseWasDownAndSelected = true;\n   }\n\n   protected override void OnPreviewMouseLeftButtonUp(MouseButtonEventArgs e)\n   {\n       \/\/ if we were watching this one, we'll unselect it if it were already selected\n       if (IsSelected &amp;&amp; _mouseWasDownAndSelected)\n       {\n           IsSelected = _mouseWasDownAndSelected = false;\n       }\n       base.OnMouseLeftButtonUp(e);\n   }\n}\n<\/pre>\n<\/div>\n<p>Il ne reste plus qu\u2019\u00e0 modifier le UserControl ImageFileListView.xaml de la partie View du projet pour utiliser ce nouveau composant Multi Select.<\/p>\n<div id=\"scid:57F11A72-B0E5-49c7-9094-E3A15BD5B5E6:edf74eeb-59d8-443b-b1be-7888390e1371\" class=\"wlWriterEditableSmartContent\" style=\"margin: 0px;padding: 0px;float: none\">\n<pre>&lt;local:ListViewMultiSelect SelectionMode=\"Extended\" ... &gt;<\/pre>\n<\/div>\n<p>&nbsp;<\/p>\n<h1>Tests unitaires<\/h1>\n<h2>Les tests unitaires du Drag and Drop<\/h2>\n<p>Le drag and drop peut \u00eatre test\u00e9 avec les tests unitaires gr\u00e2ce au d\u00e9coupage MVVM.<\/p>\n<p>Une classe <strong>SimpleTestClass<\/strong> a \u00e9t\u00e9 sp\u00e9cialement cr\u00e9\u00e9e afin de r\u00e9aliser les tests<\/p>\n<div id=\"scid:57F11A72-B0E5-49c7-9094-E3A15BD5B5E6:ffb94bb7-2c96-422f-8895-3496cffddc3d\" class=\"wlWriterEditableSmartContent\" style=\"margin: 0px;padding: 0px;float: none\">\n<pre>public class SimpleTestClass : Object, ICloneable\n{\n   public object Clone()\n   {\n       return new SimpleTestClass();\n   }\n}<\/pre>\n<\/div>\n<p>Le test unitaire peut ainsi \u00eatre impl\u00e9ment\u00e9<\/p>\n<div id=\"scid:57F11A72-B0E5-49c7-9094-E3A15BD5B5E6:df7b425c-52fa-4c0a-9436-a4ef4e1b90d7\" class=\"wlWriterEditableSmartContent\" style=\"margin: 0px;padding: 0px;float: none\">\n<pre>[TestMethod()]\n[DeploymentItem(\"DragDropManager.dll\")]\npublic void MoveDraggedItemsTest()\n{  \n  ObservableCollection&lt;SimpleTestClass&gt; collection = \n      new ObservableCollection&lt;SimpleTestClass&gt;();\n  SimpleTestClass a1 = new SimpleTestClass();\n  SimpleTestClass a2 = new SimpleTestClass();\n  SimpleTestClass a3 = new SimpleTestClass();\n  SimpleTestClass a4 = new SimpleTestClass();\n  collection.Add(a1);\n  collection.Add(a2);\n  collection.Add(a3);\n  collection.Add(a4);\n  Dictionary&lt;int, SimpleTestClass&gt; newItems =\n      new Dictionary&lt;int, SimpleTestClass&gt;();\n  newItems.Add(3, collection[3]);\n  newItems.Add(1, collection[1]);\n  \/\/ Test Count and Order moved Items\n  DragDropManagerList_Accessor&lt;SimpleTestClass&gt;\n    .MoveDraggedItems(collection, newItems, 2);\n  Assert.AreEqual(4, collection.Count);\n  Assert.AreEqual(a1, collection[0]);\n  Assert.AreEqual(a3, collection[1]);\n  Assert.AreEqual(a4, collection[2]);\n  Assert.AreEqual(a2, collection[3]);\n}<\/pre>\n<\/div>\n<p>Les autres tests unitaires se retrouvent dans le projet zip.<\/p>\n<h2>Les tests unitaires de la partie View WPF<\/h2>\n<p>Ce type de test n\u2019est pas courant car normalement le mod\u00e8le M-V-VM doit nous permettre d\u2019\u00e9viter de tester un comportement WPF.<\/p>\n<p>Seulement ici dans le cas du drag and drop, nous souhaitons tester un comportement essentiel de la fonction <strong>GetVisualItem<\/strong>.<\/p>\n<p>Cette fonction est tr\u00e8s utilis\u00e9e pour faire le lien entre le curseur de la souris et la donn\u00e9e point\u00e9e par ce curseur.<\/p>\n<p><strong>GetVisualItem<\/strong> permet de r\u00e9cup\u00e9rer l\u2019\u00e9l\u00e9ment visuel WPF correspondant \u00e0 la donn\u00e9e <strong>object<\/strong> \u201cBind\u00e9\u201d dans le composant visuel. Exemple GetVisualItem(data1) va retourner le sous \u00e9l\u00e9ment de type <strong>ListBoxItem<\/strong> contenant l\u2019objet data1.<\/p>\n<p>Pour tester de fa\u00e7on unitaire cette fonction, il faut donc cr\u00e9er un <strong>ObservableCollection<\/strong> qui contiendra les donn\u00e9es et une ListBox UI WPF qui contiendra les \u00e9l\u00e9ments affich\u00e9s. Comme nous sommes dans un test unitaire, il n\u2019y a malheureusement pas cr\u00e9ation de fen\u00eatre WPF ni donc de g\u00e9n\u00e9ration interne des composants <strong>ListBoxItem<\/strong> li\u00e9s \u00e0 l\u2019ObservableCollection. Ainsi le code suivant ne fonctionnera pas car le composant ListBox ne contiendra pas d\u2019UI \u00e9l\u00e9ments g\u00e9n\u00e9r\u00e9s.<\/p>\n<div id=\"scid:57F11A72-B0E5-49c7-9094-E3A15BD5B5E6:9ce0b0e6-1726-4f1b-965d-95ffc07fd2e8\" class=\"wlWriterEditableSmartContent\" style=\"margin: 0px;padding: 0px;float: none\">\n<pre> public void GetVisualItemTest1Helper&lt;T&gt;()\n            where T : class, ICloneable\n{\n  ListBox listbox = new ListBox();\n  T testClass = new Object() as T;\n  ObservableCollection&lt;T&gt; collection = new ObservableCollection&lt;T&gt;();\n  collection.Add(testClass);\n  listbox.ItemsSource = collection;\n\n  DragDropManagerListBoxViewModel&lt;T&gt; dragdropManagerListBoxViewModel = new DragDropManagerListBoxViewModel&lt;T&gt;(listbox);\n  PrivateObject param0 = new PrivateObject(dragdropManagerListBoxViewModel); \n  DragDropManagerListBoxViewModel_Accessor&lt;T&gt; target = new DragDropManagerListBoxViewModel_Accessor&lt;T&gt;(param0);\n  Assert.IsTrue(target.GetVisualItem(testClass) is ListBoxItem);\n}<\/pre>\n<\/div>\n<p>&nbsp;<\/p>\n<p>&nbsp;<\/p>\n<p>&nbsp;<\/p>\n<p>&nbsp;<\/p>\n<p>Afin de simuler l\u2019affichage WPF, j\u2019ai construit une m\u00e9thode qui va s\u2019occuper de la g\u00e9n\u00e9ration des sous ensembles WPF comme si nous \u00e9tions au moment de la construction d\u2019une fen\u00eatre WPF.<\/p>\n<div id=\"scid:57F11A72-B0E5-49c7-9094-E3A15BD5B5E6:9452fa13-8be6-4f14-8b12-4be4f3087f82\" class=\"wlWriterEditableSmartContent\" style=\"margin: 0px;padding: 0px;float: none\">\n<pre>private static void PrepareUIItems(ListBox listbox)\n{\n  IItemContainerGenerator itemContainerGenerator = ((IItemContainerGenerator)(listbox.ItemContainerGenerator));\n  using (itemContainerGenerator.StartAt(new GeneratorPosition(-1, 0), GeneratorDirection.Forward, true))\n  {\n      DependencyObject cntr = null;\n      do\n      {\n          cntr = itemContainerGenerator.GenerateNext();\n          if (cntr != null)\n              itemContainerGenerator.PrepareItemContainer(cntr);\n      } while (cntr != null);\n  }\n}<\/pre>\n<\/div>\n<p><span style=\"text-decoration: underline\">Remarque 1<\/span> : Le \u201c<strong>using \u2026 StartAt<\/strong>\u201d est tr\u00e8s important car c\u2019est uniquement \u00e0 la sortie de ce <strong>using<\/strong> que le <strong>ItemContainerGenerator<\/strong> WPF aura termin\u00e9 enti\u00e8rement la pr\u00e9paration de l\u2019organisation de ces \u00e9l\u00e9ments internes.<\/p>\n<p><span style=\"text-decoration: underline\">Remarque 2<\/span>: Il est imp\u00e9ratif d\u2019utiliser l\u2019interface <strong>IItemContainerGenerator<\/strong> pour pouvoir appeler StartAt,\u2026<\/p>\n<p>En ins\u00e9rant cette m\u00e9thode dans le code \u00e0 tester, la fonction appelant du composant WPF peut \u00eatre test\u00e9e et le test unitaire fonctionne maintenant correctement.<\/p>\n<div id=\"scid:57F11A72-B0E5-49c7-9094-E3A15BD5B5E6:8de84202-a25d-47b5-a594-f6efc22ae8d0\" class=\"wlWriterEditableSmartContent\" style=\"margin: 0px;padding: 0px;float: none\">\n<pre>public void GetVisualItemTestHelper&lt;T&gt;()\n            where T : class, ICloneable\n{\n  ListBox listbox = new ListBox();\n  T testClass = new Object() as T;\n  DragDropManagerListBoxViewModel&lt;T&gt; dragdropManagerListBoxViewModel = new DragDropManagerListBoxViewModel&lt;T&gt;(listbox);\n  PrivateObject param0 = new PrivateObject(dragdropManagerListBoxViewModel);\n  DragDropManagerListBoxViewModel_Accessor&lt;T&gt; target = new DragDropManagerListBoxViewModel_Accessor&lt;T&gt;(param0);\n  Assert.IsNull((target.GetVisualItem(0) as ListBoxItem));\n  \n  ObservableCollection&lt;T&gt; collection = new ObservableCollection&lt;T&gt;();\n  collection.Add(testClass);\n  listbox.ItemsSource = collection;\n  \/\/ Generate WPF UI Items\n  PrepareUIItems(listbox);\n  Assert.IsTrue(target.GetVisualItem(0) is ListBoxItem);\n  Assert.IsNull((target.GetVisualItem(1) as ListBoxItem));\n}\n\n[TestMethod()]\n[DeploymentItem(\"DragDropManager.dll\")]\npublic void GetVisualItemTest()\n{\n  GetVisualItemTestHelper&lt;GenericParameterHelper&gt;();\n}<\/pre>\n<\/div>\n<p><span style=\"text-decoration: underline\">Remarque <\/span>: Le <strong>helper<\/strong> cr\u00e9\u00e9 avec l\u2019assistant de test unitaire Visual Studio nous simplifie la t\u00e2che et nous permet de ne pas devoir cr\u00e9er une classe sp\u00e9cifique : le test peut ainsi \u00eatre effectu\u00e9 avec le Generic .<\/p>\n<h1>Conclusion<\/h1>\n<p>Nous venons de voir comment impl\u00e9menter le DragAndDrop suivant le mod\u00e8le MVVM pour une ListView ou ListBox.<\/p>\n<p>La Multi-S\u00e9lection est pr\u00e9sent, la s\u00e9lection et la destination des \u00e9l\u00e9ments est am\u00e9lior\u00e9e avec des Adorners, la copie ainsi que le d\u00e9placement des \u00e9l\u00e9ments sont g\u00e9r\u00e9s et le scroll est automatique avec le d\u00e9placement de la souris sur les bords.<\/p>\n<p>Dans les prochaines parties, nous verrons comment g\u00e9rer ces m\u00eames fonctionnalit\u00e9s avec un look TreeView.<\/p>\n<p>Voici le lien pour t\u00e9l\u00e9charger la solution avec le code source du projet et les tests unitaires<\/p>\n<p><a title=\"download http:\/\/www.bratched.com\/download\/WPFListView03.zip\" href=\"http:\/\/www.bratched.com\/download\/WPFListView03.zip\"><img decoding=\"async\" loading=\"lazy\" title=\"download32x32\" src=\"http:\/\/dev.bratched.fr\/fr\/wp-content\/uploads\/sites\/2\/importedmedia\/blogmedia-download32x32-b64acb5051597c4460f5796cc7975efe-gif.gif\" alt=\"download32x32\" width=\"32\" height=\"32\" border=\"0\" \/><\/a> <a title=\"Download http:\/\/www.bratched.com\/download\/WPFListView02.zip\" href=\"\/download\/WPFListView03.zip\">wpfListView03.zip [174 ko]<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<p>Introduction Vu pr\u00e9c\u00e9demment Nous avons vu dans la premi\u00e8re partie comment impl\u00e9menter une liste de fichiers en WPF avec le pattern MVVM. Dans la deuxi\u00e8me partie, nous avons vus comment optimiser l\u2019interface utilisateur avec le VirtualizingStackPanel, le multi Thread, les animations d\u2019attente WPF. Nous avons eu \u00e9galement un aper\u00e7u de la manipulation des images pour [&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":[16,17,18,19,2,20,21,4,22,23],"aioseo_notices":[],"_links":{"self":[{"href":"https:\/\/bratched.com\/fr\/wp-json\/wp\/v2\/posts\/472"}],"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=472"}],"version-history":[{"count":0,"href":"https:\/\/bratched.com\/fr\/wp-json\/wp\/v2\/posts\/472\/revisions"}],"wp:attachment":[{"href":"https:\/\/bratched.com\/fr\/wp-json\/wp\/v2\/media?parent=472"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/bratched.com\/fr\/wp-json\/wp\/v2\/categories?post=472"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/bratched.com\/fr\/wp-json\/wp\/v2\/tags?post=472"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}