2012-06-21 11 views
25

Sto creando un'applicazione WPF che è navigabile tramite i pulsanti e i comandi "Avanti" e "Indietro" personalizzati (ad esempio non utilizzando uno NavigationWindow). Su uno schermo, ho un ListBox che deve supportare più selezioni (usando la modalità Extended). Dispongo di un modello di visualizzazione per questa schermata e memorizzo gli elementi selezionati come proprietà, poiché devono essere mantenuti.Come supportare ListBox SelectedItems vincolante con MVVM in un'applicazione navigabile

Tuttavia, sono a conoscenza del fatto che la proprietà SelectedItems di un ListBox è di sola lettura. Ho cercato di risolvere il problema utilizzando this solution here, ma non sono stato in grado di adottarlo nella mia implementazione. Ho scoperto che non posso distinguere tra quando uno o più elementi sono deselezionati e quando navigo tra le schermate (NotifyCollectionChangedAction.Remove viene generato in entrambi i casi, poiché tecnicamente tutti gli elementi selezionati vengono deselezionati quando si naviga lontano dallo schermo). I miei comandi di navigazione si trovano in un modello di vista separato che gestisce i modelli di visualizzazione per ogni schermata, quindi non posso inserire alcuna implementazione relativa al modello di vista con lo ListBox.

Ho trovato molte altre soluzioni meno eleganti, ma nessuna di queste sembra imporre un legame bidirezionale tra il modello di vista e la vista.

Qualsiasi aiuto sarebbe molto apprezzato. Posso fornire un po 'del mio codice sorgente se possa aiutare a capire il mio problema.

+0

ah, capisco, già provi a usare un comportamento. utilizzare un BindableCollection per gli elementi selezionati, dovrebbe funzionare. Se hai più problemi, fammelo sapere. Descrivili e daremo un'occhiata. –

+1

si prega di mostrare qualche codice, in particolare il SelectedItems e il XAML. SelectedItems è una proprietà? Sospetti questo comportamento quando SelectedItems era solo un membro pubblico di BindableCollection, non una proprietà. –

+1

Ah, non mi ero reso conto che la proprietà doveva essere chiamata esplicitamente "SelectedItems" (la mia era chiamata "SelectedLanguages"). Ora ottengo un 'InvalidOperationException' lanciato nel costruttore' BindableCollection' quando faccio clic sul pulsante "Indietro" nella riga in cui il dispatcher è invocato con 'RaisePropertyChangedEventHandler'. Ho provato a inserire un blocco try/catch con 'Dispatcher.BeginInvoke' nel blocco catch, ma poi gli elementi dell'elenco non vengono riselezionati quando si torna alla pagina. – Casey

risposta

41

Prova a creare una proprietà IsSelected su ciascuno dei vostri elementi di dati e vincolante ListBoxItem.IsSelected a quella proprietà

<Style TargetType="{x:Type ListBoxItem}"> 
    <Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}" /> 
</Style> 
+0

Il mio 'ListBox' contiene elementi' CultureInfo', quindi sarebbe possibile usare la soluzione senza creare una classe personalizzata che eredita da 'CultureInfo' e ha la proprietà' IsSelected'? O sto interpretando la tua soluzione in modo errato? – Casey

+0

@ user1463765 No, dovresti avere una proprietà 'IsSelected' sul tuo oggetto affinché funzioni correttamente – Rachel

+0

Ok, grazie. Cercherò di risolvere il mio problema usando prima un comportamento, poiché preferirei evitare di definire una classe personalizzata. Proverò sicuramente la tua soluzione se non riesco a far funzionare il comportamento. – Casey

16

soluzioni di Rachel funziona benissimo! Ma c'è un problema che ho riscontrato: se si ridefinisce lo stile di ListBoxItem, si perde lo stile originale applicato (nel mio caso è responsabile per evidenziare l'elemento selezionato ecc.). È possibile evitare questo ereditando dalla stile originale:

<Style TargetType="{x:Type ListBoxItem}" BasedOn="{StaticResource {x:Type ListBoxItem}}"> 
    <Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}" /> 
</Style> 

impostazione BasedOn Nota (vedi this answer) .

8

Non ho potuto ottenere la soluzione di Rachel per funzionare come lo volevo, ma ho trovato la risposta Sandesh's di creare un custom dependency property per funzionare perfettamente per me. Ho appena dovuto scrivere un codice simile per un ListBox:

public class ListBoxCustom : ListBox 
{ 
    public ListBoxCustom() 
    { 
     SelectionChanged += ListBoxCustom_SelectionChanged; 
    } 

    void ListBoxCustom_SelectionChanged(object sender, SelectionChangedEventArgs e) 
    { 
     SelectedItemsList = SelectedItems; 
    } 

    public IList SelectedItemsList 
    { 
     get { return (IList)GetValue(SelectedItemsListProperty); } 
     set { SetValue(SelectedItemsListProperty, value); } 
    } 

    public static readonly DependencyProperty SelectedItemsListProperty = 
     DependencyProperty.Register("SelectedItemsList", typeof(IList), typeof(ListBoxCustom), new PropertyMetadata(null)); 

} 

Nel mio modello di vista ho appena fatto riferimento a tale proprietà per ottenere la mia lista selezionata.

+0

Mi piace questa risposta, ma probabilmente modificherò il codice in quanto tale: https://pastebin.com/YTccwmxG – maxp

-1

Elimina l'associazione di una casella di controllo alla proprietà IsSelected e inserendo il blocco di testo e la casella di controllo all'interno di un pannello di stack, il trucco!

1

Ho cercato una soluzione semplice ma senza fortuna.

La soluzione che Rachel ha è buona se si dispone già della proprietà Selected sull'oggetto all'interno di ItemsSource. In caso contrario, è necessario creare un modello per quel modello aziendale.

Sono andato a un'altra strada. Uno veloce, ma non perfetto.

Sulla ListBox creare un evento per SelectionChanged.

<ListBox ItemsSource="{Binding SomeItemsSource}" 
     SelectionMode="Multiple" 
     SelectionChanged="lstBox_OnSelectionChanged" /> 

Ora implementare l'evento sul codice sottostante della pagina XAML.

private void lstBox_OnSelectionChanged(object sender, SelectionChangedEventArgs e) 
{ 
    var listSelectedItems = ((ListBox) sender).SelectedItems; 
    ViewModel.YourListThatNeedsBinding = listSelectedItems.Cast<ObjectType>().ToList(); 
} 

Tada. Fatto.

Questo è stato fatto con l'aiuto di converting SelectedItemCollection to a List.

0

Non soddisfatto delle risposte date stavo cercando di trovarne uno da solo ... Beh, risulta essere più come un hack che una soluzione ma per me funziona bene. Questa soluzione utilizza MultiBindings in un modo speciale. Per prima cosa può sembrare una tonnellata di codice ma puoi riutilizzarlo con pochissimo sforzo.

In primo luogo ho implementato un 'IMultiValueConverter'

public class SelectedItemsMerger : IMultiValueConverter 
{ 
    public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture) 
    { 
     SelectedItemsContainer sic = values[1] as SelectedItemsContainer; 

     if (sic != null) 
      sic.SelectedItems = values[0]; 

     return values[0]; 
    } 

    public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture) 
    { 
     return new[] { value }; 
    } 
} 

E un SelectedItems contenitore/Wrapper:

public class SelectedItemsContainer 
{ 
    /// Nothing special here... 
    public object SelectedItems { get; set; } 
} 

Ora creiamo l'associazione per la nostra ListBox.SelectedItem (singolare). Nota: è necessario creare una risorsa statica per il "convertitore". Questo può essere fatto una volta per applicazione ed essere riutilizzato per tutti i ListBox che necessitano del convertitore.

Nel ViewModel ho creato il contenitore in cui posso eseguire il binding. È importante inizializzarlo con new() per riempirlo con i valori.

SelectedItemsContainer selectionContainer = new SelectedItemsContainer(); 
    public SelectedItemsContainer SelectionContainer 
    { 
     get { return this.selectionContainer; } 
     set 
     { 
      if (this.selectionContainer != value) 
      { 
       this.selectionContainer = value; 
       this.OnPropertyChanged("SelectionContainer"); 
      } 
     } 
    } 

E questo è tutto. Forse qualcuno vede dei miglioramenti? Cosa ne pensi?

Problemi correlati