Abbiamo parlato della possibilità di mettere in binding i SelectedItems della ListView di WPF con una nostra collection, altra cosa a cui abbiamo accennato è la mancanza, sulla ListView, di un ICommand per gestire il doppio click su un elemento della lista.
Siccome abbiamo scoperto, almeno io ;-), la potenza delle attached properties facciamocelo.
ItemDoubleClickCommand
Partiamo come al solito dal risultato che vogliamo ottenere:
<ListView local:ListViewManager.ItemDoubleClickCommand="{Binding Path=Select}" />
Molto semplice: poter dichiarare un nuovo Command e metterlo in binding con qualcosa che implementi l’interfaccia ICommand; per arrivare li, nell’ormai vecchio e familiare ListViewManager, cominciamo a dichiarare l’attached property:
public static DependencyProperty ItemDoubleClickCommandProperty = DependencyProperty.RegisterAttached(
    "ItemDoubleClickCommand",
    typeof( ICommand ),
    typeof( ListViewManager ),
    new UIPropertyMetadata( OnItemDoubleClickCommandChanged ) );


public static void SetItemDoubleClickCommand( DependencyObject target, ICommand value )
{
    target.SetValue( ItemDoubleClickCommandProperty, value );
}

static ICommand GetItemDoubleClickCommand( DependencyObject target )
{
    return ( ICommand )target.GetValue( ItemDoubleClickCommandProperty );
}
Riporto anche la parte Get/Set perchè faccio notare che Visual Studio sfrutta le informazioni sul “tipo” del parametro value di SetXxxx, e del valore di ritorno di GetXxxx, per capire in fase di compilazione se la dichiarazione nello xaml sia sensata o meno, questo in combinazione con il tipo che avete dichiarato nella attached property (il secondo parametro del metodo RegisterAttached).
Continuiamo poi l’handler che viene invocato in fase di modifica del valore della proprietà:
static void OnItemDoubleClickCommandChanged( DependencyObject target, DependencyPropertyChangedEventArgs e )
{
    Ensure.That( target.GetType() )
        .WithMessage( "This behavior can be attached to ListView(s) only." )
        .Is<ListView>();

    var lv = ( ListView )target;
    if( ( e.NewValue != null ) && ( e.OldValue == null ) )
    {
        lv.MouseDoubleClick += onListViewDblClick;

    }
    else if( ( e.NewValue == null ) && ( e.OldValue != null ) )
    {
        lv.MouseDoubleClick –= onListViewDblClick;
    }
}
Verifichiamo innanzitutto che siamo “attached” su una ListView dopodichè agganciamo o sganciamo l’handler dall’evento MouseDoubleClick in base alla situazione in cui siamo. L’event handler, dichiarato sempre nel costruttore statico della classe ListViewManager, altro non fa che gestire il doppio click:
onListViewDblClick += ( s, e ) => 
{
    if( IsInsideListViewItem( e.OriginalSource as DependencyObject ) )
    {
        var command = GetItemDoubleClickCommand( ( DependencyObject )s );
        if( command.CanExecute( null ) )
        {
            command.Execute( null );
        }
    }
};
Per prima cosa verifichiamo di essere su un ListViewItem, questa verifica è necessaria perchè l’evento MouseDoubleClick viene scatenato per il doppio click su qualsiasi parte della ListView ma a noi interessano solo quelli avvenuti in un ListViewItem, andando a recuperare l’elemento che ha originato il doppio click (OriginalSource) e risalendo il VisualTree finchè non troviamo quello che ci interessa:
static Boolean IsInsideListViewItem( DependencyObject obj )
{
    if( obj == null )
    {
        return false;
    }
    else if( obj is ListViewItem )
    {
        return true;
    }
    else
    {
        var parent = VisualTreeHelper.GetParent( obj );
        return IsInsideListViewItem( parent );
    }
}
Ripercorrere all’indietro il visual tree è necessario perchè associato a quel ListViewItem ci potrebbe essere un complesso DataTemplate e quindi la OriginalSource dell’evento essere un elemento nested nel template.
Risaliamo quindi il visual tree e se effettivamente siamo su un ListViewItem recuperiamo, dalla attached property, una reference all’ICommand con cui siamo in binding, verifichiamo che CanExecute() sia true e nel caso invochiamo l’esecuzione del command.
.m