Technology Experience
.NET World

Una classe DragService per gestire il drag’n’drop in ambito WPF & MVVM

Il titolo non potrebbe essere più esplicativo. Per lo stesso progetto che ho accennato nel mio post di ieri ho dovuto cercare una soluzione per:

  1. sfruttare il drag’n’drop nella UI dell’applicazione WPF
  2. fare in modo che tale meccanismo non sia troppo invasivo, e non mi costringa a riscrivere da zero i miew ViewModel già pronti
  3. fare in modo che tale meccanismo utilizzi i Command già predisposti nei miei ViewModel
  4. il drag’n’drop deve poter utilizzare gli stessi Command già funzionanti per la parte non drag’n’drop

Mi spiego meglio sul punto (4). Ho sempre preferito fare in modo di adattare la stessa UI, in modo tale che possa essere usabile in diversi modi, facendo in modo che l’utente possa scegliere istintivamente quello che preferisce. Quindi, supponiamo di avere questa interfaccia utente:

E’ una cosa piuttosto semplice: sulla sinistra abbiamo un elenco di nomi, a destra abbiamo una TextBox che mostrerà la persona che abbiamo scelto. In questo caso voglio dare la possibilità all’utente di scegliere una persona dall’elenco in due modi differenti:

  • selezionandola dall’elenco e poi cliccando sul bottoncino con la freccia “—>”
  • trascinandola dall’elenco sopra la TextBox

Sebbene si trattino di due interazioni diverse, vorrei poter riciclare gli stessi Command (sia per quanto riguarda la parte Execute, che la parte CanExeCute)

Sul Web, googlando, ho trovato un po’ di soluzioni, ma lo ho scartate tutte quante, un po’ perchè invasive (soprattutto per essere inserite su un progetto già avviato), un po’ perchè complesse (capire decine, decine e decine di linee di codice non tue può rivelarsi poco piacevole). Quindi mi sono messo di buona lena ed ho realizzato una classe DragService (283 linee di codice ben formattate) che soddisfa tutti i miei requisiti.

Vediamo quindi come si utilizza questa mia classe.

Come utilizzare VivendoByte.WPF.DragService
Lo screenshot qui sopra è ovviamente un’applicazione WPF, implementata secondo i soliti metodi richiesti dal pattern MVVM:

xmlns:l="clr-namespace:DragAndDropDemo"
xmlns:drag="clr-namespace:VivendoByte.WPF.DragService;assembly=VivendoByte.WPF.DragService"

 

Innanzitutto, ho dichiarato i namespace che mi servono: “l” è l’assembly locale, mentre “drag” fa riferimento all’assembly VivendoByte.WPF.DragService. Il ViewModel viene inserito fra le risorse della Window corrente; il DataContext della Grid prende questo ViewModel e fa in modo di governare la view.

<Window.Resources>
    <l:MainViewModel x:Key="vm" />
</Window.Resources>
    
<Grid DataContext="{StaticResource vm}">
...
</Grid>

 

La prima parte interessante riguarda la ListBox posizionata a sinistra sulla finestra:

<ListBox Grid.Row="0" Margin="4" ItemsSource="{Binding Persons}"
            Background="LightGoldenrodYellow"
            SelectedItem="{Binding DraggingPerson}">
    <ListBox.ItemTemplate>
        <DataTemplate>
            <StackPanel Orientation="Horizontal">
                <TextBlock
                    Text="{Binding Fullname}" FontSize="24"
                    drag:DragService.SourceCommand="{Binding
                        RelativeSource={RelativeSource Mode=FindAncestor,AncestorType=Grid},
                        Path=DataContext.DragCommand}"
                    drag:DragService.SourceCommandParameter="{Binding}" />
            </StackPanel>
        </DataTemplate>
    </ListBox.ItemTemplate>
</ListBox>

 

La ListBox è bindata ad un elenco di persone (proprietà Persons del ViewModel); l’elemento selezionato è bindato sulla proprietà DraggingPerson. All’interno della ListBox ho definito un semplice DataTemplate, per permettere di mostrare ogni elemento in modo più furbo: usando la proprietà Fullname esposta da Person. E qui arriva il barbatrucco.

Sulla TextBlock ho bindato un Command, comprensivo di parametro. Siccome ci si trova all’interno di un DataTemplate, ho dovuto utilizzare RelativeSource per permettere al motore di WPF di navigare il logical tree della Window, raggiungere la Grid su cui è impostato il DataContext e – finalmente – prendere il Command interessato (nel nostro caso DragCommand). La cosa interessante è questa:

  • Nel momento in cui parte la nostra operazione di drag su un elemento della ListBox viene eseguita la CanExecute del Command associato; se la CanExecute fallisce (ritorna false) il drag non parte nemmeno
  • Se invece la CanExecute ritorna true, parte l’operazione di dragging

La stessa cosa bene o male accade per la TextBox, che è la destinazione del drag’n’drop. Invece di utilizzare SourceCommand e SourceCommandParameter, devono essere utilizzate TargetCommand e TargetCommandParameter.

<StackPanel Grid.Column="1" Margin="4">
    <TextBlock Text="Trascina una persona" FontSize="24" Height="40" />
    <DockPanel>
        <Button Content="-->" Margin="8" Width="32" Command="{Binding DropCommand}" />
        <TextBox Background="LightGoldenrodYellow" Text="{Binding SelectedPerson.Fullname,Mode=OneWay}"
                    FontSize="24" Height="40"
            drag:DragService.TargetCommand="{Binding DropCommand}" />
    </DockPanel>
    <Button Content="Rimuovi persona" Margin="8" Command="{Binding ClearPersonCommand}" FontSize="20" />
</StackPanel>

 

La TextBox è bindata al comando DropCommand. Questa volta il binding è diretto, dal momento che non ci si trova più dentro un DataTemplate, e quindi il riferimento al DataContext della Grid è diretto. Anche in questo caso:

  • Nel momento in cui passiamo sopra la TextBox durante il drag’n’drop, viene eseguita la CanExecute. Se ritorna false, l’icona del mouse diventa il classico divieto. Se ritorna true, tutto bene: sul controllo appare l’Adorner ed il drag’n’drop può concludersi
  • Se rilasciamo il tasto del mouse sopra il controllo, viene eseguito il command

Notare che il Button è bindato allo stesso comando DropCommand. Difatti, l’utente può draggare una persona dalla ListBox, o semplicemente selezionarla e poi cliccare il bottone.

Ultime piccole osservazioni

Provate a selezionare o a draggare l’elemento “Sabrina Pedretti”. E’ minorenne, e siccome la CanExecute del DragCommand impone che la persone sia maggiorenne, ecco che su questo elemento l’operazione di drag’n’drop non avviene. Davvero molto comodo: possiamo annullare il drag’n’drop nel momento in cui l’utente tenta di cominciarlo, sfruttando i Command e quindi rispettando appieno il paradigma M-V-VM. Il button “—>” anche in questo caso di spegne, per lo stesso motivo: lo stesso metodo CanExecute che impedisce il drag’n’drop fa spegnere il Button, perchè il command DragCommand non è disponibile.

I Command del ViewModel sono implementati attraverso la libreria Galasoft, di Laurent Bignon. Sono supportati sia il RelayCommand normale, che quello tipizzato. Nel progettino demo, il comando DragCommand è tipizzato sulla classe Person (nello XAML è stato specificato il SourceCommandParameter), mentre il comando DropCommand no (per capire quale elemento sto draggando utilizzo la proprietà DraggingPerson del ViewModel, impostata all’inizio del drag’n’drop).

Il Button indicato come “Rimuovi persona” svuota semplicemente la TextBox per ricominciare un’altra operazione di drag’n’drop. E lo fa agendo sulla proprietà SelectedPerson esposta dal ViewModel.

Disponibile attraverso NuGet!

Se utilizzate NuGet per la gestione degli assembly all’interno delle vostre solution VS2010, sappiate che potete trovare la libreria VivendoByte.WPF.DragService, scaricabile gratuitamennte e pronta all’uso. Ringrazio Alessandro Melchiori per la sua sessione presso l’ultimo workshop UGIdotNET, dove ha illustrato NuGet da diversi punti di vista, compreso quello del developer, ovvero come pubblicare proprie librerie sul repository pubblico.

Download del progetto demo

Se invece siete interessati al progetto demo, piccola applicazione WPF da cui è stato tratto lo screenshot sopra, è scaricabile dal mio account SkyDrive.

Send to Kindle

Igor Damiani

La sua passione per l'informatica nasce nella prima metà degli anni '80, quando suo padre acquistò un Texas Instruments TI-99. Da allora ha continuato a seguire l'evoluzione sia hardware che software avvenuta nel corso degli anni. E' un utente, un videogiocatore ed uno sviluppatore software a tempo pieno. Igor ha lavorato e lavora anche oggi con le più moderne tecnologie Microsoft per lo sviluppo di applicazioni: .NET Framework, XAML, Universal Windows Platform, su diverse piattaforme, tra cui spiccano Windows 10 piattaforme mobile. Numerose sono le app che Igor ha creato e pubblicato sul marketplace sotto il nome VivendoByte, suo personale marchio di fabbrica. Adora mantenere i contatti attraverso Twitter e soprattutto attraverso gli eventi delle community .NET.

Lascia un commento

Il tuo indirizzo email non sarà pubblicato. I campi obbligatori sono contrassegnati *

Questo sito usa Akismet per ridurre lo spam. Scopri come i tuoi dati vengono elaborati.