2012-03-08 12 views
5

Mam kontrolkę ListView, która wyświetla elementy z obserwowalnej kolekcji. Te elementy muszą być filtrowane. Mogę to zrobić z CollectionViewSource, ale filtr musi być aktualizowany za każdym razem, gdy zmienia się przedmiot.Filtrowanie obserwowalnej kolekcji

Moi pozycji wygląda następująco:

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; } 
    } 
} 

Wtedy mam ten ListView którego ItemSource jest związany z kolekcji.

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

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

To niczego nie filtruje. Tak więc mam te pola wyboru i powinny one wskazywać, które elementy (w zależności od stanu) powinny być wyświetlane. Użyłem wtedy CollectionViewSource:

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

Sposób filtr wygląda następująco:

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); 
} 

Ma to tę wadę, że rejestruje wartości pól, więc muszę wywołać tę metodę (UpdateView) za każdym razem, gdy zaznaczone jest pole wyboru. Ale działa.

Jednak stan elementu zmienia się, a jeśli "wykonano" nie jest sprawdzany na przykład, gdy element przechodzi do "zrobione", powinien zostać usunięty z widoku. Oczywiście to się nie zmieni, chyba że ponownie zadzwonię pod numer UpdateView. Tak więc muszę wywoływać tę metodę za każdym razem, gdy coś się zmienia. To dla mnie wygląda brzydko i nieefektywnie.

Moje pytanie brzmi: czy można to zrobić w przyjemniejszy sposób?

+0

Postawiłem inne podejście jako odpowiedź, ale pamiętam, że filtry działają bez wywołania funkcji Update(). Spróbuj wdrożyć program NotifyPropertyChanged w projekcie - powiązanie nie będzie świadome zmiany bez niego. – Paparazzi

Odpowiedz

13

powiązać ListView bezpośrednio do przefiltrowanej kolekcji zamiast ObservableCollection tworząc nieruchomości -

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

Więc teraz po prostu trzeba zadzwonić Refresh() w swojej kolekcji na stan pola wyboru Zmienione wydarzenie tak -

YourFilteredCollection.Refresh(); 

Aby odświeżyć kolekcję w oparciu o każdej zmianie stanu w klasie elementu, można uogólnić go zaczepiając zdarzenie PropertyChanged swojej klasie elementu (w tym klasa trzeba zaimplementować INotifyPropertyChanged), a stamtąd można wywołać odświeżania tak -

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

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

Tak więc, gdy wszelkie zmiany własności w swojej klasie elementu, kolekcja będą filtrowane .

+1

ale nadal musiałbym wywoływać Odśwież za każdym razem, gdy stan elementu lub pola wyboru się zmieniają. –

+0

Mam aktualizację moją odpowiedź dotyczącą twojego niepokoju o zmianę stanu.W ten sposób musisz teraz odświeżyć tylko z dwóch miejsc. Mam nadzieję że to pomoże. Co więcej, możesz podklasować ObservableCollection i przenieść tę logikę tam, gdzie możesz użyć swoich podklas w miejsce ObservableCollection. –

+1

Może to być pomocna w podklasie obiektu ObservableCollection - http://msdn.microsoft.com/en-us/library/ee696421.aspx –

4

Lubię używać DataTriggers do tego. Dla twojej logiki konieczne będzie użycie konwertera wielowartościowego.

<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

Użyj narzędzia takiego jak ContinuousLinq. Powiązanie listview do zapytania, które zostanie ponownie ocenione, gdy zmieni się element na liście (lub sama lista).