2012-03-08 25 views
5

Tengo un control ListView que muestra elementos de una colección observable. Estos artículos necesitan ser filtrados. Puedo hacer eso con un CollectionViewSource, pero el filtro debe actualizarse cada vez que cambia un elemento.Filtrar una colección observable

Mis artículos se parece a esto:

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

entonces tengo este ListView cuya ItemSource se une a la colección.

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

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

Esto no filtra nada. Así que tengo estas casillas de verificación y deben indicar qué elementos (dependiendo del estado) se deben mostrar. He utilizado entonces una CollectionViewSource:

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

El método del filtro se ve así:

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

Esto tiene la desventaja de que captura los valores de las casillas de verificación, por lo que tiene que llamar a este método (UpdateView) cada vez que se marca una casilla de verificación. Pero funciona.

Sin embargo, el estado del elemento cambia y si "hecho" no está marcado, por ejemplo, cuando un elemento entra en "hecho", debe eliminarse de la vista. Obviamente eso no cambia a menos que vuelva a llamar al UpdateView. Así que necesito llamar a este método cada vez que algo cambia. Eso me parece feo e ineficiente.

Entonces mi pregunta es, ¿se puede hacer esto de una manera más agradable?

+0

Publiqué otro enfoque como respuesta, pero recuerdo que los filtros funcionan sin una llamada a Update(). Intente implementar NotifyPropertyChanged en Project: el enlace no tiene conocimiento de un cambio sin este. – Paparazzi

Respuesta

13

vincular a su ListView directamente a la colección filtrada en lugar de la ObservableCollection mediante la creación de una propiedad -

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

lo tanto, ahora sólo necesita llamar a Refresh() en su colección en su estado casillas de verificación cambiada evento como este -

YourFilteredCollection.Refresh(); 

Para actualizar la colección basada en cualquier cambio de estado en la clase de artículo, usted puede generalizar enganchando el evento PropertyChanged de la clase de material (para esta clase necesita implementar INotifyPropertyChanged) y desde allí se puede llamar de refresco como esto -

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

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

Por lo tanto, cada vez que cualquier cambio de propiedad en su clase de artículo, serán filtrados su colección .

+1

pero igual tendría que llamar a Refresh cada vez que cambie el estado de un elemento o las casillas de verificación. –

+0

He actualizado mi respuesta con respecto a su preocupación por el cambio de estado.Así que de esta manera solo necesita llamar a la actualización desde dos lugares ahora. Espero que esto ayude. Además, puede subclasificar el ObservableCollection y mover esta lógica allí y puede usar sus subclases en lugar de su ObservableCollection. –

+1

Esto podría ser de ayuda para subclase de ObservableCollection - http://msdn.microsoft.com/en-us/library/ee696421.aspx –

4

Me gusta usar DataTriggers para eso. Para su lógica necesitaría usar un convertidor multivalente.

<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

Utilice una herramienta como ContinuousLinq. Vincula su vista de lista a una consulta que se reevaluará cuando cambie un elemento de la lista (o la propia lista).

Cuestiones relacionadas