1

Recently I've been learning C# and WPF for work. I'm trying to use MVVM on a project I'm working on, just to keep the code organized and learn how it works.

In MVVM, controls on the View bind to properties on the ViewModel, which implements INotifyPropertyChanged. Pretty often, when a certain property is updated, I'll want a bunch of other properties to get updated as a result.

For example, I have a ListBox with a TextBox above it. You can type in the TextBox, and it filters the stuff in the ListBox. But I also need to be able to clear the TextBox from code in certain cases. The code ends up looking like this:

private Collection<string> _listOfStuff;
public Collection<string> FilteredList
{
    get
    {
        if (String.IsNullOrWhiteSpace(SearchText))
        {
            return _listOfStuff;
        }
        else
        {
            return new Collection<string>(_listOfStuff.Where(x => x.Contains(SearchText)));
        }
    }
    set
    {
        if (value != _listOfStuff)
        {
            _listOfStuff = value;
            OnPropertyChanged("FilteredList");
        }
    }
}

private string _searchText;
public string SearchText
{
    get { return _searchText; }
    set
    {
        if (value != _searchText)
        {
            _searchText = value;
            OnPropertyChanged("SearchText"); // Tells the view to change the value of the TextBox
            OnPropertyChanged("FilteredList"); // Tells the view to update the filtered list
        }
    }
}

As this project gets bigger, this is starting to feel sloppy. I have one setter with 6 calls to OnPropertyChanged and it's getting hard to keep track of stuff. Is there a better way to do this?

Patrick A.
  • 15
  • 1
  • 3
  • by sloppy you mean lots of OnPropertyChanged statements ? well that's exactly what you want isn't ? – Muds Dec 03 '15 at 17:19
  • You don't have to call `OnPropertyChanged("FilteredList");` here as it wasn't changed/assigned. When it gets changed (i.e. in an async command), then it the view will be notified when you do `FilteredList = await SomeAsyncMethod()`. What are you exactly trying to archive? – Tseng Dec 03 '15 at 17:28
  • @Tseng I have to call `OnPropertyChanged("FilteredList");` because it's value depends on the contents of SearchText – Patrick A. Dec 03 '15 at 17:39
  • @Muds It is, but it gets hard to maintain this way and I was wondering if there's a more organized way of doing it. If there's not, that's OK – Patrick A. Dec 03 '15 at 17:41
  • see my answer that might help u but its really not great – Muds Dec 03 '15 at 17:47
  • Lots of different ways. You'd have to write most of them. –  Dec 03 '15 at 18:35
  • If you not raise `PropertyChanged` event, then view doesn't know about changes. You need to raise 6 different event for this. If you don't want, then design simpler view or use some third part libraries which does it for you – Fabio Dec 04 '15 at 11:03

5 Answers5

1

First you shouldn't do potentially expensive operations in a command, then you'll be able to remove the OnPropertyChanged("FilteredList"); from your SearchText.

So you should move that code from the getter and into it's own command and bind it from XAML (either as Command on a button or using Blends Interactivity Trigger to call it when the text fields value changes).

public ICommand SearchCommand { get; protected set; }
// Constructor
public MyViewModel()
{
    // DelegateCommand.FromAsyncHandler is from Prism Framework, but you can use
    // whatever your MVVM framework offers for async commands
    SearchCommand = DelegateCommand.FromAsyncHandler(DoSearch);
}

public async Task DoSearch() 
{
    var result = await _listOfStuff.Where(x => x.Contains(SearchText)).ToListAsync();
    FilteredList = new Collection<string>(result);
}

private Collection<string> _listOfStuff;
private Collection<string> _filteredList;
public Collection<string> FilteredList
{
    get
    {
        return _filteredList;
    }
    set
    {
        if (value != _filteredList)
        {
            _filteredList = value;
            OnPropertyChanged("FilteredList");
        }
    }
}

private string _searchText;
public string SearchText
{
    get 
    { 
        return _searchText;
    }
    set
    {
        if (value != _searchText)
        {
            _searchText = value;
            OnPropertyChanged("SearchText");
        }
    }
}

On a side note: You can also use OnPropertyChanged(nameof(FilteredList)); to have a refactor friendly version, when you rename your property all of your OnPropertyChanged calls will be updated to. Requires C# 6.0 though, but it's compatible with older .NET Frameworks (back to 2.0), but requires Visual Studio 2015 or later

Tseng
  • 61,549
  • 15
  • 193
  • 205
1

I tried out Assisticant on a project about a year ago. It figures out which of your properties need to raise notifications and also which are related. There is a good course for it on Pluralsight and the examples on the website are pretty good. If nothing else you could check out the source code to see how he did it.

Also some good suggestions from Change Notification in MVVM Hierarchies.

They mentioned:
Use an attribute -> e.g. [DependsUpon(nameof(Size))]

and

Josh Smith's PropertyObserver

Could put the raise property change calls in a method if you just need to raise the same notifications every time.

Community
  • 1
  • 1
1

For anyone searching for a good solution to this type of problem: Check out ReactiveUI.

It is a framework based on Reactive Extensions (Rx), with the idea that you model this type of dependencies between properties explicitly, without a jungle of RaisePropertyChanged(..).

Specifically check out the ObservableAsPropertyHelper (sometimes called OAPH).

samirem
  • 125
  • 2
  • 9
0

You should only raise OnPropertyChanged in the setter of the property itself.

A cleaner implementation of your ViewModel can be:

private Collection<string> _listOfStuff;
private Collection<string> _filteredList;

public Collection<string> FilteredList
{
    get
    {         
            return _filteredList;
    }
    set
    {
        if (value != _filteredList)
        {
            _filteredList = value;
            OnPropertyChanged("FilteredList");
        }
    }
}

private string _searchText;
public string SearchText
{
    get { return _searchText; }
    set
    {
        if (value != _searchText)
        {
            _searchText = value;
            OnPropertyChanged("SearchText");

            FilteredList = new Collection<string>(_listOfStuff.Where(x => x.Contains(SearchText)));
        }
    }
}
LucaV
  • 245
  • 3
  • 11
-1

if you just don't wanna type only other option is to fire OnPropertyChanged for all properties which can be done by passing a null or string.Empty, although it will be sloppier code!

OnPropertyChanged(Null);

or

OnPropertyChanged(String.Empty);
Muds
  • 4,006
  • 5
  • 31
  • 53