2016-01-16 24 views
7

Sto sviluppando un'app UWP, con l'SDK di Light and Behaviors di Mvvm. Ho definito un multi ListView selezionabile:ListView SelectedItems binding: perché l'elenco è sempre nullo

<ListView 
    x:Name="MembersToInviteList" 
    IsMultiSelectCheckBoxEnabled="True" 
    SelectionMode="Multiple" 
    ItemsSource="{Binding Contacts}" 
    ItemTemplate="{StaticResource MemberTemplate}"> 

</ListView> 

mi piacerebbe, con un pulsante rilegata ad un MVVM-LightRelayCommand, per ottenere un elenco con gli elementi selezionati:

<Button 
    Command="{Binding AddMembersToEvent}" 
    CommandParameter="{Binding ElementName=MembersToInviteList, Path=SelectedItems}" 
    Content="Ok"/> 

La RelayCommand (di MVVM- quadro chiaro):

private RelayCommand<object> _addMembersToEvent; 
public RelayCommand<object> AddMembersToEvent 
{ 
    get 
    { 
     return _addMembersToEvent 
      ?? (_addMembersToEvent = new RelayCommand<object>(
       (selectedMembers) => 
       { 
        // Test 
        // selectedMembers is always null! 
       })); 
    } 
} 

ho messo un punto di interruzione all'interno del comando, e ho notato che è sempre selectedMembers null, anche se io seleziona vari oggetti. Dall'output della console non vedo alcun errore di binding o qualcos'altro.

Inoltre, se si passa come CommandParameter all'intero elenco e si inserisce un punto di interruzione all'interno della definizione del comando, noto che non è possibile accedere al valore SelectedItems né SelecteRanges.

<DataTemplate x:Name="MemberTemplate"> 

    <Viewbox MaxWidth="250"> 
     <Grid Width="250" 
       Margin="5, 5, 5, 5" 
       Background="{StaticResource MyLightGray}" 
       BorderBrush="{StaticResource ShadowColor}" 
       BorderThickness="0, 0, 0, 1" 
       CornerRadius="4" 
       Padding="5"> 

      <Grid.ColumnDefinitions> 
       <ColumnDefinition Width="Auto" /> 
       <ColumnDefinition Width="1*" /> 
      </Grid.ColumnDefinitions> 

      <Grid Grid.Column="0" 
        Width="45" 
        Height="45" 
        Margin="5,0,5,0" 
        VerticalAlignment="Center" 
        CornerRadius="50"> 

       <Grid.Background> 
        <ImageBrush AlignmentX="Center" 
           AlignmentY="Center" 
           ImageSource="{Binding Image.Url, 
                 Converter={StaticResource NullGroupImagePlaceholderConverter}}" 
           Stretch="UniformToFill" /> 
       </Grid.Background> 

      </Grid> 

      <TextBlock Grid.Column="1" 
         Margin="3" 
         VerticalAlignment="Center" 
         Foreground="{StaticResource ForegroundTextOverBodyColor}" 
         Style="{StaticResource LightText}" 
         Text="{Binding Alias}" /> 

     </Grid> 
    </Viewbox> 

</DataTemplate> 

Qual è il motivo? Come posso ottenere tale lista?

+0

Il RelayCommand è un'implementazione 'ICommand' scritta da te o proviene dal framework MVVM-Light? – Tomtom

+0

@Tomtom Mvvm MVVM-Light framework –

+0

Puoi mostrarci il tuo ItemTemplate? – Tomtom

risposta

6

Una delle soluzioni per passare SelectedItem da ListView in ViewModel (con RelayCommands) è descritta nel blog di igralli.

Pass ListView SelectedItems to ViewModel in Universal apps

provare il seguente codice per ottenere gli oggetti selezionati dal parametro.

private RelayCommand<IList<object>> _addMembersToEvent; 
    public RelayCommand<IList<object>> AddMembersToEvent 
    { 
     get 
     { 
      return _addMembersToEvent 
        ?? (_addMembersToEvent = new RelayCommand<IList<object>>(
         selectedMembers => 
         { 
          List<object> membersList = selectedMembers.ToList(); 
         })); 
     } 
    } 
+0

La tua risposta mi ha aiutato molto a risolvere il problema. Il codice che hai postato non è abbastanza per farlo funzionare; anche il link che hai postato vede il problema in un altro modo: lega l'elenco di elementi selezionati ogni volta che selezioni un elemento, il mio problema era quello di legarlo una volta quando un utente fa clic sul pulsante. Inoltre non ho usato i comportamenti sdk. –

0

non riesco a trovare una ragione, ma si può facilmente glissare del tutto avendo una proprietà "IsSelected" sul vostro Contatto e mettendo una casella di controllo sul modello:

<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"> 

    <Grid.RowDefinitions> 
     <RowDefinition Height="*"></RowDefinition> 
     <RowDefinition Height="Auto"></RowDefinition> 
     <RowDefinition Height="Auto"></RowDefinition> 
    </Grid.RowDefinitions> 

    <ListView ItemsSource="{Binding Contacts}"> 
     <ListView.ItemTemplate> 
      <DataTemplate> 
       <Grid> 
        <CheckBox Content="{Binding Name}" IsChecked="{Binding IsSelected, Mode=TwoWay}"></CheckBox> 
       </Grid> 
      </DataTemplate> 
     </ListView.ItemTemplate> 
    </ListView> 

    <TextBlock Grid.Row="1" Text="{Binding SelectedItemsOutput}"></TextBlock> 
    <Button Grid.Row="2" Content="What is checked?" Command="{Binding GoCommand}"></Button> 
</Grid> 

e macchine virtuali, ecc:

public class MainViewModel : INotifyPropertyChanged 
{ 
    private string _selectedItemsOutput; 

    public ObservableCollection<Contact> Contacts { get; set; } = new ObservableCollection<Contact> { new Contact() { Id = 1, Name = "Foo" }, new Contact() { Id = 2, Name = "Bar" } }; 

    public ICommand GoCommand => new RelayCommand(Go); 

    public string SelectedItemsOutput 
    { 
     get { return _selectedItemsOutput; } 
     set 
     { 
      if (value == _selectedItemsOutput) return; 
      _selectedItemsOutput = value; 
      OnPropertyChanged(); 
     } 
    } 

    void Go() 
    { 
     SelectedItemsOutput = string.Join(", ", Contacts.Where(x => x.IsSelected).Select(x => x.Name)); 
    } 

    public event PropertyChangedEventHandler PropertyChanged; 
} 

public class Contact : INotifyPropertyChanged 
{ 
    private bool _isSelected; 

    public int Id { get; set; } 
    public string Name { get; set; } 

    public bool IsSelected 
    { 
     get { return _isSelected; } 
     set 
     { 
      if (value == _isSelected) return; 
      _isSelected = value; 
      OnPropertyChanged(); 
     } 
    } 

    public override string ToString() 
    { 
     return Name; 
    } 

    public event PropertyChangedEventHandler PropertyChanged; 
} 

public sealed partial class MainPage : Page 
{ 
    public MainPage() 
    { 
     this.InitializeComponent(); 
     DataContext = new MainViewModel(); 
    } 
} 
+0

Hai ragione, ma non posso mettere una proprietà "IsSelected" al mio modello di contatto perché non ho realmente accesso a quella classe. –

1

Ho fatto un piccolo esempio per il tuo caso senza MVVM-Light e funziona perfettamente.

Forse il problema si trova nella classe RelayCommand. Ho scritto i seguenti:

public class RelayCommand<T> : ICommand 
{ 
    private readonly Action<T> execute; 
    private readonly Predicate<T> canExecute; 

    public RelayCommand(Action<T> execute, Predicate<T> canExecute = null) 
    { 
     if (execute == null) 
      throw new ArgumentNullException("execute"); 
     this.execute = execute; 
     this.canExecute = canExecute; 
    } 

    public bool CanExecute(object parameter) 
    { 
     if (canExecute == null) 
      return true; 
     return canExecute((T) parameter); 
    } 

    public void Execute(object parameter) 
    { 
     execute((T) parameter); 
    } 

    public event EventHandler CanExecuteChanged 
    { 
     add { CommandManager.RequerySuggested += value; } 
     remove { CommandManager.RequerySuggested -= value; } 
    } 
} 
+0

Ho provato l'altra implementazione del comando, ma non funziona. Stesso problema. Ho appena cambiato un tag per essere più specifico: UWP. (Prima non riuscivo a metterlo a causa dei tag max) –

0

Solo i miei cinque centesimi e potrebbe essere un colpo assolutamente a lungo, ma si dovrebbe verificare this:

Se l'ItemsSource implementa IItemsRangeInfo, la collezione SelectedItems non viene aggiornato in base alla selezione nell'elenco. Utilizzare invece la proprietà SelectedRanges.

Sto solo indovinando sulla base della risposta di Tomtom, dal momento che dice che ha fatto quasi esattamente come te e ottenuto una soluzione di lavoro. Forse la differenza sta in questo piccolo dettaglio. Non ho ancora controllato me stesso.

+0

Appena provato, ma è nullo comunque. –

0

Grazie alla risposta di Romano ho capito come risolvere il problema:

Prima di tutto, come Romano suggerito:

private RelayCommand<IList<object>> _addMembersToEvent; 
public RelayCommand<IList<object>> AddMembersToEvent 
{ 
    get 
    { 
     return _addMembersToEvent 
       ?? (_addMembersToEvent = new RelayCommand<IList<object>>(
        selectedMembers => 
        { 
         List<object> membersList = selectedMembers.ToList(); 
        })); 
    } 
} 

Poi, il codice XAML:

<Button 
    Command="{Binding AddMembersToEvent}" 
    CommandParameter="{Binding ElementName=MembersToInviteList, Converter={StaticResource ListViewSelectedItemsConverter}}" 
    Content="Ok"/> 

La differenza qui è che ho passato l'intera lista come parametro, non è la proprietà SelectedItems. Quindi, utilizzando un IValueConverter ho convertito dall'oggetto ListView-IList<object> di SelectedMember:

public class ListViewSelectedItemsConverter : IValueConverter 
{ 
    public object Convert(object value, Type targetType, object parameter, string language) 
    { 
     var listView = value as ListView; 
     return listView.SelectedItems; 
    } 

    public object ConvertBack(object value, Type targetType, object parameter, string language) 
    { 
     throw new NotImplementedException(); 
    } 
} 

In questo modo il RelayCommand<IList<object>> ottenuto la lista di destra e non un valore nullo.

+1

potresti aver trovato la tua soluzione ma ho eseguito il tuo codice dato sopra senza cambiare nulla (con la mia classe di comando relay) e ha funzionato bene. selectedMembers ha sempre valori. Penso che il problema riguardasse la tua classe RelayCommand. –

+0

Nella soluzione sopra RelayCommand è ancora MVVM-Light (come nella domanda). –

+0

puoi aggiungere anche questa classe alla domanda? visto che non ho MVVM-Light e anche altri che possono leggere questo post potrebbero volerlo. –