C
C#2mo ago
Rodonies

WPF CollectionView Filter on Property of Model?

in a MVVM WPF app, how would I go about filtering a ListBox that is bound to a property of a model?
//Model
class Y {
string Name;
List<X> ListOfX;
}

class X {
string Name;
}
//Model
class Y {
string Name;
List<X> ListOfX;
}

class X {
string Name;
}
//ViewModel
public ObservableCollection<Y> ObservableListOfY { get; set; }


private Y _SelectedY;
public Y SelectedY;
{
get { return _SelectedY; }
set
{
_SelectedY = value;
OnPropertyChanged();
}
}

private string _SearchBoxText = string.Empty;
public string SearchBoxText
{
get { return SearchBoxText; }
set
{
_SearchBoxText = value;
FilterData();
OnPropertyChanged();
}
}

public ctor()
{
ObservableListOfY = ...
}

public void FilterData()
{
//This filter works
CollectionViewSource.GetDefaultView(ObservableListOfY ).Filter = o => (o as Y).ListOfX.Any(x => x.Name.Contains(SearchBoxText));

//This one doesn't, probably because ListOfZ is not an ObservableCollection
CollectionViewSource.GetDefaultView(SelectedY.ListOfZ).Filter = o => (o as X).Name.Contains(SearchBoxText);
}
//ViewModel
public ObservableCollection<Y> ObservableListOfY { get; set; }


private Y _SelectedY;
public Y SelectedY;
{
get { return _SelectedY; }
set
{
_SelectedY = value;
OnPropertyChanged();
}
}

private string _SearchBoxText = string.Empty;
public string SearchBoxText
{
get { return SearchBoxText; }
set
{
_SearchBoxText = value;
FilterData();
OnPropertyChanged();
}
}

public ctor()
{
ObservableListOfY = ...
}

public void FilterData()
{
//This filter works
CollectionViewSource.GetDefaultView(ObservableListOfY ).Filter = o => (o as Y).ListOfX.Any(x => x.Name.Contains(SearchBoxText));

//This one doesn't, probably because ListOfZ is not an ObservableCollection
CollectionViewSource.GetDefaultView(SelectedY.ListOfZ).Filter = o => (o as X).Name.Contains(SearchBoxText);
}
<!--View-->
<TextBox Text="{Binding SearchBoxText}" />
<ListBox ItemsSource="{Binding ObservableListOfY}" SelectedItem="{Binding SelectedY}" />
<ListBox ItemsSource="{Binding SelectedY.ListOfX}" />
<!--View-->
<TextBox Text="{Binding SearchBoxText}" />
<ListBox ItemsSource="{Binding ObservableListOfY}" SelectedItem="{Binding SelectedY}" />
<ListBox ItemsSource="{Binding SelectedY.ListOfX}" />
I want to filter out all the Y's that don't have a single X that matches the SearchText, I achieve this by setting the Filter property on YCollectionView in the ViewModel. The problem is that even though that works, if you select a Y in the listbox it'll show every X, even if they don't match the text. I can't set the Filter property on a CollectionView because ListOfX is a property of the Y model and I don't think I should use a CollectionView in the model, how would you normally achieve this behaviour? is creating a ObservableListOfX and updating it in the setter of SelectedY a valid solution?
10 Replies
Rodonies
Rodonies2mo ago
@Klarth so you are telling me to introduce a new observable list for the ListOfX from whatever Y that is selected in the viewmodel right? My question still remains as to where I should set that observablelist you told me it shouldn't happen in the SelectedY setter but I wouldn't know where else to put it, besides in some SelectionChanged event from the listbox sorry for tagging you here but you seem to be at least somewhat engaged with my question haha :p, I posted a more complete code mockup above I'm trying hard to find a proper solution, because this problem is actually nested one level more, as in: X has a list of Z, and I want to filter based on the name of Z and it should not show any Y's NOR X's that don't have a single X that don't have a single Z
Klarth
Klarth2mo ago
Y is the classic "if a tree falls in the forest and nobody is around, does it make a sound?". There are no notifications, no domain events, etc to announce changes.
Rodonies
Rodonies2mo ago
you mean ObservableListOfY? yeah I haven't implemented propertychange on there because that collection never changes I could but it would be unused so I left it out of the code mockup
Klarth
Klarth2mo ago
class Y {
string Name;
List<X> ListOfX;
}
class Y {
string Name;
List<X> ListOfX;
}
No, this part. Does it ever change?
Rodonies
Rodonies2mo ago
no but that's the model right? I don't think it should notify anything even if it did
Klarth
Klarth2mo ago
If they're immutable, then just map them (ie. copy) to a new ViewModel type with an observable collection, use CollectionViewSource, and move on. If you have an in-memory domain, you're either: 1. notifying of changes (not INPC, but events/messages) or 2. leaking logic of when and what to synchronize to the ViewModel. Otherwise, you call a service to do some domain work...and you have no idea what happens if you don't do one of those two steps.
Rodonies
Rodonies2mo ago
yeah you're right on that actually, but it just gets read in at the start and it will never change
Klarth
Klarth2mo ago
Then what's the big deal about mapping it to something View-friendly?
public static class YViewModelMapper
{
public static YViewModel ToViewModel(this Y model)
{
return new YViewModel
{
Name = model.Name,
ListOfX = new(model.ListOfX)
};
}
}

public partial class YViewModel : ObservableObject {
[ObservableProperty] private string _name;
[ObservableProperty] ObservableCollection<X> _listOfX;
}
public static class YViewModelMapper
{
public static YViewModel ToViewModel(this Y model)
{
return new YViewModel
{
Name = model.Name,
ListOfX = new(model.ListOfX)
};
}
}

public partial class YViewModel : ObservableObject {
[ObservableProperty] private string _name;
[ObservableProperty] ObservableCollection<X> _listOfX;
}
🤷‍♂️
Rodonies
Rodonies2mo ago
so this is just mirroring the model but in the viewmodel space? I have never seen that before
Klarth
Klarth2mo ago
Like I said elsewhere, the VM state is just a temporary, editable copy of Model(s) state. You have to copy because you aren't binding to directly to the Model. If you wrap and automatically sync VM and Model state...then you're going to run into issues.
Want results from more Discord servers?
Add your server