Reputation: 47
I'm new to WPF, and just learning how things work. My endgoal is to have a datagrid for each heading which displays totals from a collection of items associated with each category.
But I'm seeing some unexpected behavior. I expect to see this output:
Heading1
Category1
Category2
Heading2
Category3
What I actually see is this:
Heading1
Category3
Heading2
Category3
I'm stumped as to why. Hoping somebody could shed a little light. Thanks for any help!
Here's my code:
MainWindow.xaml
<Window x:Class="TestApp.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:TestApp"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<StackPanel>
<ItemsControl ItemsSource="{Binding Headings}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding Description}" />
<DataGrid ItemsSource="{Binding Categories}" AutoGenerateColumns="False">
<DataGrid.Columns>
<DataGridTextColumn Header="" Binding="{Binding Description}" Width="*" />
</DataGrid.Columns>
</DataGrid>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
</Window>
MainWindow.xaml.cs
public partial class MainWindow : Window
{
public ObservableCollection<HeadingViewModel> Headings { get; }
public MainWindow()
{
InitializeComponent();
this.DataContext = this;
var category1 = new CategoryViewModel()
{
Id = 1,
Description = "Category 1",
HeadingId = 1,
};
var category2 = new CategoryViewModel()
{
Id = 2,
Description = "Category 2",
HeadingId = 1,
};
var category3 = new CategoryViewModel()
{
Id = 3,
Description = "Category 3",
HeadingId = 2,
};
var categories = new ObservableCollection<CategoryViewModel>();
categories.Add(category1);
categories.Add(category2);
categories.Add(category3);
var heading1 = new HeadingViewModel(categories)
{
Id = 1,
Description = "Heading 1",
};
var heading2 = new HeadingViewModel(categories)
{
Id = 2,
Description = "Heading 2",
};
this.Headings = new ObservableCollection<HeadingViewModel>();
this.Headings.Add(heading1);
this.Headings.Add(heading2);
}
HeadingViewModel.cs
public class HeadingViewModel : INotifyPropertyChanged
{
public ICollectionView Categories { get; }
public HeadingViewModel(ObservableCollection<CategoryViewModel> categories)
{
this.Categories = CollectionViewSource.GetDefaultView(categories);
this.Categories.Filter = CategoriesFilter;
}
public event PropertyChangedEventHandler PropertyChanged;
private bool CategoriesFilter(object item)
{
var category = item as CategoryViewModel;
return category.HeadingId == this.Id;
}
private int id;
public int Id
{
get
{
return id;
}
set
{
this.id = value;
OnPropertyChanged();
Categories.Refresh();
}
}
public string Description { get; set; }
protected void OnPropertyChanged([CallerMemberName] string name = null)
{
this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}
}
CategoryViewModel.cs
public class CategoryViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public CategoryViewModel() {}
public int Id { get; set; }
public string Description { get; set; }
private int? headingId;
public int? HeadingId
{
get
{
return headingId;
}
set
{
this.headingId = value;
OnPropertyChanged();
}
}
protected void OnPropertyChanged([CallerMemberName] string name = null)
{
this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}
}
Upvotes: 1
Views: 71
Reputation: 47
grek40 gets the gold star for telling me what the problem was - but here's the solution for anyone else that stumbles across this:
In HeadingViewModel.cs change this line:
this.Categories = CollectionViewSource.GetDefaultView(categories);
to this:
this.Categories = new CollectionViewSource { Source = categories }.View;
That returns a new instance of the viewsource for each instance of Heading which solves the issue.
Upvotes: 1
Reputation: 13448
The problem is, CollectionViewSource.GetDefaultView(categories)
will return the same ICollectionView
instance for the same input, no matter how often you call it.
Since you pass the same categories
to both HeadingViewModel
constructors, the second constructor this.Categories.Filter = CategoriesFilter
will override the filter for both headings.
Upvotes: 1