C
C#•2mo ago
stigzler

Filtering an Observable Collection in WPF MVVM via ICollectionView

I have an observabelCollection of a data object:
public ObservableCollection<GistViewModel> Gists { get => gists; set => SetProperty(ref gists, value); }
public ObservableCollection<GistViewModel> Gists { get => gists; set => SetProperty(ref gists, value); }
Each GistViewModel has the following:
public BindingList<GistFileViewModel> GistFiles { get => gistFiles; }
public BindingList<GistFileViewModel> GistFiles { get => gistFiles; }
In terms of what this represents, each Gist has a collection of GistFiles. I am mapping this onto a TreeViewControl (root being Gists, Child being GistFiles) via the following xaml (abridged):
<!-- GistLevel -->
<HierarchicalDataTemplate DataType="{x:Type viewmodels:GistViewModel}" ItemsSource="{Binding Path=GistsFiles}">

<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding GistFiles, Converter={StaticResource GistFilesToFirstFilenameConverter}}"></TextBlock>
</StackPanel>

</HierarchicalDataTemplate>

<!-- GIST FILE LEVEL -->

<DataTemplate DataType="{x:Type viewmodels:GistFileViewModel}">
<StackPanel Orientation="Horizontal">
<TextBlock VerticalAlignment="Center" Margin="2,0,2,0" Text="{Binding Filename}"></TextBlock>
</StackPanel>
</DataTemplate>

<TreeView x:Name="GistsTV" ItemsSource="{Binding Gists}"/>
<!-- GistLevel -->
<HierarchicalDataTemplate DataType="{x:Type viewmodels:GistViewModel}" ItemsSource="{Binding Path=GistsFiles}">

<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding GistFiles, Converter={StaticResource GistFilesToFirstFilenameConverter}}"></TextBlock>
</StackPanel>

</HierarchicalDataTemplate>

<!-- GIST FILE LEVEL -->

<DataTemplate DataType="{x:Type viewmodels:GistFileViewModel}">
<StackPanel Orientation="Horizontal">
<TextBlock VerticalAlignment="Center" Margin="2,0,2,0" Text="{Binding Filename}"></TextBlock>
</StackPanel>
</DataTemplate>

<TreeView x:Name="GistsTV" ItemsSource="{Binding Gists}"/>
This works successfully. Now I am trying to introduce a filter feature. I have followed a guide online somewhere which advises using ICollectionView. I implement this thus:
public ICollectionView GistsView { get => CollectionViewSource.GetDefaultView(Gists); }

private async Task GetAllGistsAsync()
{
Gists = await gistManager.LoadGistsAsync();
GistsView.Refresh();
}
public ICollectionView GistsView { get => CollectionViewSource.GetDefaultView(Gists); }

private async Task GetAllGistsAsync()
{
Gists = await gistManager.LoadGistsAsync();
GistsView.Refresh();
}
And changing my xaml to:
<TreeView x:Name="GistsTV" ItemsSource="{Binding GistsView}"/>
<TreeView x:Name="GistsTV" ItemsSource="{Binding GistsView}"/>
I'm just trying to get the default list presented first. However, this does not work. No list items are displayed. GistFilesToFirstFilenameConverter doesn't get called when Binding = GistsView The full code is here: https://github.com/stigzler/VisGist/blob/master/VisGist/ToolWindows/MainWindow.xaml What am I missing?
GitHub
VisGist/VisGist/ToolWindows/MainWindow.xaml at master · stigzler/Vi...
Contribute to stigzler/VisGist development by creating an account on GitHub.
2 Replies
devaness
devaness•2mo ago
I did not check your whole code, but I think the problem is this line:
Gists = await gistManager.LoadGistsAsync();
Gists = await gistManager.LoadGistsAsync();
This replaces the whole list object with a new one. Without the CollectionView this works, because the view was bound directly to this property and your setter called the PropertyChanged event via the SetProperty method. When using an ObservableCollection the class already handles the PropertyChanged events itself. I usually just instantiate it once and then add or remove items from it. Omitting the setter will also prevent you from accidentally replacing the object. Like this:
public ObservableCollection<GistViewModel> Gists { get; } = new();
public ObservableCollection<GistViewModel> Gists { get; } = new();
Your LoadGistsAsync() method can return a standard list and the viewmodel can add them to the observable collection. Unfortunately ObservableCollection does not have an AddRange() method by default (though some frameworks like Prism provide one as an extension method).
var newData = await gistManager.LoadGistsAsync();
Gists.Clear();

// Gists.AddRange(newData);
foreach (var item in newData)
{
Gists.Add(item);
}
var newData = await gistManager.LoadGistsAsync();
Gists.Clear();

// Gists.AddRange(newData);
foreach (var item in newData)
{
Gists.Add(item);
}
stigzler
stigzler•2mo ago
thanks for the answer. Sadly, it didn't work - same problem 😦