2012-03-08 22 views
5

Ho un controllo ListView che visualizza elementi da una raccolta osservabile. Questi articoli devono essere filtrati. Sono in grado di farlo con un CollectionViewSource, ma il filtro deve essere aggiornato ogni volta che un elemento cambia.Filtra una raccolta osservabile

miei articoli assomiglia a questo:

enum Status {Done, Failed, Skipped, ...} 

class Project { 
    public string Name {get;set;} 
    public Status Status {get;set;} 
    // etc. etc. 
} 

class ProjectViewModel : INotifyPropertyChanged { 
    private Project project; 

    public ProjectBuildInfoViewModel(ProjectBuildInfo project) 
    { 
    this.project = project; 
    } 

    public string Name 
    { 
    get { return project.Name; } 
    set { project.Name = value; OnPropertyChanged("Name"); } 
    } 

    // etc. etc. 
} 

class CollectionViewModel { 
    private ObservableCollection<ProjectViewModel> projects = 
      new ObservableCollection<ProjectViewModel>(); 

    public ObservableCollection<ProjectViewModel> Collection 
    { 
    get { return projects; } 
    private set {projects = value; } 
    } 
} 

Poi ho questa ListView cui ItemSource è legato alla raccolta.

// member of the user control class 
private CollectionViewModel collection = new CollectionViewModel(); 

// in the constructor 
listView.ItemSource = collection.Collection. 

Questo non filtra nulla. Quindi ho queste caselle di controllo e dovrebbero indicare quali elementi (a seconda dello stato) dovrebbero essere visualizzati. Ho usato poi un CollectionViewSource:

private void UpdateView() 
{ 
    var source = CollectionViewSource.GetDefaultView(collection.Collection); 
    source.Filter = p => Filter((ProjectViewModel)p); 
    listStatus.ItemsSource = source; 
} 

Il metodo del filtro si presenta così:

private bool Filter(ProjectViewModel project) 
{ 
    return (ckFilterDone.IsChecked.HasValue && ckFilterDone.IsChecked.Value && project.Status == Status.Done) || 
      (ckFilterFailed.IsChecked.HasValue && ckFilterFailed.IsChecked.Value && project.Status == Status.Failed) || 
      (ckFilterSkipped.IsChecked.HasValue && ckFilterSkipped.IsChecked.Value && project.Status == Status.Skipped); 
} 

Questo ha lo svantaggio che cattura i valori delle caselle di controllo, quindi devo chiamare questo metodo (UpdateView) ogni volta che viene selezionata una casella di controllo. Ma funziona.

Tuttavia, lo stato dell'elemento cambia e se "done" non viene controllato, ad esempio, quando un elemento va in "done" dovrebbe essere rimosso dalla vista. Ovviamente ciò non cambia a meno che non chiami ancora UpdateView. Quindi ho bisogno di chiamare questo metodo ogni volta che qualcosa cambia. Mi sembra brutto e inefficiente.

Quindi la mia domanda è: è possibile farlo in un modo più bello?

+0

Ho inviato un altro approccio come risposta, ma ricordo che i filtri funzionano senza una chiamata ad Update(). Prova ad implementare NotifyPropertyChanged in Project - l'associazione non è a conoscenza di una modifica senza di essa. – Paparazzi

risposta

13

vincolare il proprio ListView direttamente alla raccolta filtrato al posto del ObservableCollection con la creazione di una proprietà -

public ICollectionView YourFilteredCollection 
{ 
    get 
    {  
     var source = CollectionViewSource.GetDefaultView(collection.Collection); 
     source.Filter = p => Filter((ProjectViewModel)p); 
     return source; 
    } 
} 

Così, ora semplicemente è necessario chiamare Refresh() sulla vostra collezione sul vostro stato caselle di controllo evento modificato come questo -

YourFilteredCollection.Refresh(); 

per aggiornare la collezione in base a qualsiasi cambiamento di stato della classe oggetto, è possibile generalizzare agganciando l'evento PropertyChanged della classe oggetto (per questo la classe ha bisogno di implementare INotifyPropertyChanged) e da lì si può chiamare aggiornamento come questo -

foreach (YourClass item in collection.Collection) 
{ 
    item.PropertyChanged += new PropertyChangedEventHandler(item_PropertyChanged); 
} 

void item_PropertyChanged(object sender, PropertyChangedEventArgs e) 
{ 
    YourFilteredCollection.Refresh(); 
} 

Così, ogni volta eventuali modifiche alle proprietà nella tua classe voce, la vostra collezione verranno filtrati .

+1

ma dovrei comunque chiamare Aggiorna ogni volta che lo stato di un articolo o le caselle di controllo cambiano. –

+0

Ho aggiornato la mia risposta in merito alla sua preoccupazione per il cambiamento di stato.Quindi in questo modo è necessario richiamare l'aggiornamento solo da due posizioni ora. Spero che questo ti aiuti. Inoltre, puoi creare sottoclasse di ObservableCollection e spostare questa logica lì e puoi utilizzare le sottoclassi al posto di ObservableCollection. –

+1

Questo potrebbe essere di aiuto per sottoclasse ObservableCollection - http://msdn.microsoft.com/en-us/library/ee696421.aspx –

4

Mi piace utilizzare DataTriggers per questo. Per la tua logica sarebbe necessario utilizzare un convertitore multivalore.

<ListView Grid.Row="3" Grid.Column="2" ItemsSource="{Binding Path=GabeLib.DocFieldsAll}"> 
     <ListView.ItemContainerStyle> 
      <Style TargetType="{x:Type ListViewItem}" > 
       <Style.Triggers> 
        <DataTrigger Binding="{Binding Path=Active}" Value="False"> 
         <Setter Property="Visibility" Value="Collapsed"/> 
        </DataTrigger> 
        <DataTrigger Binding="{Binding Path=FieldDef.ID}" Value="0"> 
         <Setter Property="Visibility" Value="Collapsed"/> 
        </DataTrigger> 
       </Style.Triggers> 
      </Style> 
     </ListView.ItemContainerStyle> 
1

Utilizzare uno strumento come ContinuousLinq. Si lega la listview a una query che rivaluterà quando un elemento nell'elenco (o l'elenco stesso) cambia.

Problemi correlati