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