Se soffrite da dipendenza acuta da M-V-VM questa è una discreta rottura perchè il drag & drop è “roba” totalmente da Presentation (aka View) ma la logica è totalmente da Business (aka ViewModel).
La gestione del drag & drop è quindi un ottimo candidato per divertirci con gli attached behavior, nome figoso per le ormai legacy attached properties.
Quello che vogliamo ottenere è questo:
<ListView HorizontalAlignment="Stretch"
SelectedItem="{Binding Path=Selection}"
behaviors:DragDropManager.DataObject="{Binding Path=Selection}"
behaviors:DragDropManager.DataObjectType="mySampleType"
ItemsSource="{Binding Path=TestList}">
ergo poter definire, data ad esempio una ListView, ma funziona su qualsiasi UIElement, quale sia il dato (utilizzando il potentissimo strumento di Binding) da “draggare” e quale sia, opzionale, il DataFormat del “dragged object”.
E sul drop target limitarci a fare:
<Border Grid.Column="1" Margin="10"
behaviors:DragDropManager.OnDropCommand="{Binding DropCommand}"
HorizontalAlignment="Stretch"
AllowDrop="True"
VerticalAlignment="Stretch" Background="Gainsboro" />
Utilizzando un ICommand come “target” del drop. Utilizziamo un ICommand perchè si sposa molto bene con il concetto di “drop”, il drop è fondamentalmente fatto da 2 fasi:
- una fase di analisi, durante di DragOver, per capire se la “roba” che stiamo draggando possa essere droppata sul target –> ICommand.CanExecute( DragOverArgs e );
- una fase in cui effettivamente, se il DragOver ha dato feedback positivo, i dati vengono droppati –> ICommand.Execute( DropArgs e );
Il nostro codice nel VM si potrebbe quindi limitare ad una cosa di questo tipo:
this.DropCommand = DelegateCommand.Create()
.OnCanExecute( o =>
{
var e = ( DragOverArgs )o;
return e.Data.GetDataPresent( "mySampleType" );
} )
.OnExecute( o =>
{
var e = ( DropArgs )o;
var data = e.Data.GetData( "mySampleType" );
//Use "data"...
} );
L’attached behavior DragDropManager si occupa di tutte le fasi essenziali della gestione del Drag & Drop e per ora lo fa in maniera decisamente minimale, ma lo fa.
Quello che viene fatto è decisamente semplice (fonti: D&D #1 e D&D #2):
- Sull’elemento sorgente (aka DragSource):
- vengono agganciati i 2 eventi fondamentali: PreviewMouseMove e PreviewMouseLeftButtonDown, non stiamo prendendo in considerazione l’eventualità che i pulsanti del mouse siano invertiti, in cui gestiamo l’inizio dell’operazione di D&D;
- Sull’elemento destinazione (aka DropTarget):
- Settiamo a true la proprietà AllowDrop;
- Agganciamo gli eventi DragOver e Drop, nel primo recuperiamo una reference al command e invochiamo CanExecute, mentre nel secondo invochiamo Execute;
Partendo dal codice di Jaime non è difficile pssare ad una attached property/behavior, sopratutto se ci accontentiamo di una gestione minimale del D&D senza quindi icone custom o adorner layer che fanno cose mirabolanti.
.m