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:
  1. Utilizzare gli eventi della ListView e fare da “codice” nella View… aaarghhh… addio M-V-VM ;-)
  2. 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