La ListView di WPF è semplicemente la manna, unita al motore di templating, che ha potenzialità infinite, nel giro di pochissimo tempo vi fa dimenticare che Microsoft si è “dimenticata” di mettere una griglia tra i controlli builtin; e non ditemi che esite quella del WPF Toolkit perchè siamo lontani anni luce da qualcosa che rasenti la decenza… lasciamo perdere poi i controlli di terze parti, io ho provato quello di Xceed, carino, stiloso, ma un chiodo spaventoso… fa pure rima :-D
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;
Ad entrambi i problemi evidenziati ci sono degli workaround:
- 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;
AttachedProperties rulez!
Il nostro primo (in realtà è il secondo punto della lista di cui sopra…) obiettivo è quindi poter scrivere:
<ListView local:ListViewManager.SelectedItems="{Binding Path=SelectedItems}" ... />
Sfruttando le attached properties è veramente un giochetto:
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 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:
static readonly DependencyProperty selectionHandlerProperty = DependencyProperty.RegisterAttached(
"selectionHandler",
typeof( SelectionHandler ),
typeof( ListViewManager ),
new FrameworkPropertyMetadata( null ) );
La classe SelecionHandler è colei che si occupa di sincronizzare la propretà ListView.SelectedItems con la nostra collection nel ViewModel:
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;
}
}
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:
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 );
}
}
Quando il valore della nostra attached property SelectedItems cambia procediamo con:
- verificare se avevamo un SelectionHandler agganciato, e nel caso lo rimuoviamo;
- creare un nuovo SelectionHandler, agganciarlo e “avviarlo”;
Lascio per una seconda puntata l’implemtazione dell’ItemDoubleClickCommand.
.m