ListView perchè sei tu, ListView… (part 1)
Ma torniamo a noi, abbiamo la nostra bella ListView e abbiamo anche deciso, che causa cattive frequentazioni ;-), non possiamo vivere senza Model-View-ViewModel… la simpaticona ci frega in curva perchè mancano alcune cosette non da poco, quelle che ho, a mio malgrado, trovato sino ad ora sono:
- L’assenza di un ItemDoubleClickCommand per poter agganciare un ICommand all’atto del doppio click su un elemento della lista;
- L’impossibilità di mettere in binding la collection SelectedItems (al plurale) e gestire automaticamente una lista che abbia il supporto per la selezione multipla;
- Utilizzare gli eventi della ListView e fare da “codice” nella View… aaarghhh… addio M-V-VM ;-)
- Dato che usiamo M-V-VM introdurre, nella collection che mettiamo in binding con la ListView, il concetto si “IsSelected”, che poi con un bel DataTemplate visualizziamo come CheckBox, consentendo all’utente di selezionare più elementi… funzionare funziona e lo uso pure, ma se l’utente è abituato alla selezione classica di Windows (quella con il CTRL per intenderci), e di tutto il software dalla notte dei tempi, direi che non ci siamo;
Il nostro primo (in realtà è il secondo punto della lista di cui sopra…) obiettivo è quindi poter scrivere:
Sfruttando le attached properties è veramente un giochetto:<ListView local:ListViewManager.SelectedItems="{Binding Path=SelectedItems}" ... />
La prima cosa che facciamo, in una classe statica ListViewManager, è registrare una nuova attached property, già questo ci permette di compilare lo snippet xaml che abbiamo visto prima (ricordatevi di dichiarare il namespace xml), a questo punto non ci resta che gestire il cambio di selezione della ListView. Abbiamo però un inghippo: da un lato abbiamo n ListView a cui il nostro, unico, behavior statico può essere agganciato; abbiamo quindi bisogno di un sistema, una sorta di dictionary, per tenere traccia di cosa stiamo facendo e con chi: la soluzione più semplice è sfruttare lo stesso DependencyObject a cui la attached property è collegata aggiungendo un’altra attached property, privata:public static readonly DependencyProperty SelectedItemsProperty = DependencyProperty.RegisterAttached( "SelectedItems", typeof( IList ), typeof( ListViewManager ), new FrameworkPropertyMetadata( null, OnSelectedItemsChanged ) ); public static IList GetSelectedItems( ListView owner ) { return ( IList )owner.GetValue( SelectedItemsProperty ); } public static void SetSelectedItems( ListView owner, IList value ) { owner.SetValue( SelectedItemsProperty, value ); }private static void OnSelectedItemsChanged( DependencyObject d, DependencyPropertyChangedEventArgs e ) {
}
La classe SelecionHandler è colei che si occupa di sincronizzare la propretà ListView.SelectedItems con la nostra collection nel ViewModel:static readonly DependencyProperty selectionHandlerProperty = DependencyProperty.RegisterAttached( "selectionHandler", typeof( SelectionHandler ), typeof( ListViewManager ), new FrameworkPropertyMetadata( null ) );
Nulla di trascendentale, una semplice classe che aggancia l’evento SelectionChanged della ListView e sincronizza l’attuale selezione con la lista con cui siamo in binding. A questo punto ci resta l’ultimo passaggio:private sealed class SelectionHandler { ListView owner; IList selectedItems; SelectionChangedEventHandler h; internal SelectionHandler() { h = ( s, e ) => { e.RemovedItems.Enumerate( obj => { if( this.selectedItems.Contains( obj ) ) { this.selectedItems.Remove( obj ); } } ); e.AddedItems.Enumerate( obj => { this.selectedItems.Add( obj ); } ); }; }
public void SartSync( ListView owner, IList selectedItems ) { this.owner = owner; this.selectedItems = selectedItems; this.owner.SelectionChanged += h; } public void StopSync() { this.owner.SelectionChanged -= h; this.owner = null; this.selectedItems = null; } }
Quando il valore della nostra attached property SelectedItems cambia procediamo con:static void OnSelectedItemsChanged( DependencyObject d, DependencyPropertyChangedEventArgs e ) { if( e.OldValue != null ) { var handler = d.GetValue( selectionHandlerProperty ) as SelectionHandler; if( handler != null ) { handler.StopSync(); d.ClearValue( selectionHandlerProperty ); } } if( e.NewValue != null ) { var handler = new SelectionHandler(); handler.SartSync( d.CastTo<ListView>(), e.NewValue.CastTo<IList>() ); d.SetValue( selectionHandlerProperty, handler ); } }
- verificare se avevamo un SelectionHandler agganciato, e nel caso lo rimuoviamo;
- creare un nuovo SelectionHandler, agganciarlo e “avviarlo”;
.m