-
Notifications
You must be signed in to change notification settings - Fork 22
Reactive Library WhenPropertyChanged
WhenPropertyChanged is an extension method in the SciChart.UI.Reactive.Observability namespace which provides a reactive stream to one or more properties.
Given a viewmodel defined as follows:
public enum SearchOptions
{
AnOption,
AnotherOption,
}
public class WhenPropertyChangedTests : ObservableObjectBase
{
public string SearchText
{
get => GetDynamicValue<string>();
set => SetDynamicValue(value);
}
public SearchOptions SearchOptions
{
get => GetDynamicValue<SearchOptions>();
set => SetDynamicValue(value);
}
}
Properties may be observed using Reactive Extensions as follows
void Foo()
{
// Create the ViewModel
WhenPropertyChangedTests vm = new WhenPropertyChangedTests();
// Observe properties
var observable1 = vm.WhenPropertyChanged(x => x.SearchText);
var observable2 = vm.WhenPropertyChanged(x => x.SearchOptions);
// Subscribe
var disposable = observable1.Subscribe(t => Console.WriteLine($"Search Text = '{t}'"));
// Set properties
vm.SearchText = "Hello"; // -> Should output 'Search Text = 'Hello'' to console
vm.SearchText = "World"; // -> Should output 'Search Text = 'World'' to console
disposable.Dispose();
vm.SearchText = "Not observed"; // nothing happens
}
Multiple properties may be observed using Observable.CombineLatest
void Foo()
{
// Create the ViewModel
WhenPropertyChangedTests vm = new WhenPropertyChangedTests();
// Observe properties
var observable1 = vm.WhenPropertyChanged(x => x.SearchText);
var observable2 = vm.WhenPropertyChanged(x => x.SearchOptions);
// Subscribe
var disposable = Observable.CombineLatest(observable1, observable2, Tuple.Create)
.Subscribe(t => Console.WriteLine($"Search Text = '{t.Item1}', Options = '{t.Item2}'"));
// Set properties
//
// -> Should output 'Search Text = 'Hello', Options = 'AnOption'' to console
vm.SearchText = "Hello";
// -> Should output 'Search Text = 'Hello', Options = 'AnotherOption'' to console
vm.SearchOptions = SearchOptions.AnotherOption;
// -> Should output 'Search Text = 'World', Options = 'AnotherOption'' to console
vm.SearchText = "World";
disposable.Dispose();
vm.SearchText = "Not observed"; // nothing happens
}
The Extension method DisposeWith() ensures a reactive obsevable is disposed with a parent which implements ICompositeDisposable (including all viewmodel and trait types in SciChart.UI.Reactive)
void Foo()
{
// Create the ViewModel
WhenPropertyChangedTests vm = new WhenPropertyChangedTests();
// Observe properties
var observable1 = vm.WhenPropertyChanged(x => x.SearchText);
var observable2 = vm.WhenPropertyChanged(x => x.SearchOptions);
// Subscribe
var disposable = Observable.CombineLatest(observable1, observable2, Tuple.Create)
.Subscribe(t => Console.WriteLine($"Search Text = '{t.Item1}', Options = '{t.Item2}'"));
disposable.DisposeWith(vm);
// Dipose the parent, disposes subscription above
vm.Dispose();
vm.SearchText = "Not observed"; // nothing happens
}
Imagine you have a class which provides searches to a remote server. You want to call a method SearchForResults which is an async call with multiple parameters when the parameters change in a ViewModel, which are bound to user controls. You want to prevent too many searches to the server and want nice neat observable code to do it.
Starting off with the definition of the search service like this:
public interface ISearchService
{
Task<ResultViewModel> SearchForResults(string searchText, SearchOptions options);
}
public enum SearchOptions
{
AnOption,
AnotherOption,
}
public class ResultViewModel : ViewModelBase
{
public string ResultText
{
get => GetDynamicValue<string>();
set => SetDynamicValue(value);
}
}
We might create a ViewModel which observes properties like this:
public class WhenPropertyChangedTests : ObservableObjectBase
{
public WhenPropertyChangedTests(ISearchService searchProvider)
{
// TODO: The search
}
public string SearchText
{
get => GetDynamicValue<string>();
set => SetDynamicValue(value);
}
public SearchOptions SearchOptions
{
get => GetDynamicValue<SearchOptions>();
set => SetDynamicValue(value);
}
public IEnumerable<ResultViewModel> SearchResults
{
get => GetDynamicValue<IEnumerable<ResultViewModel>>();
set => SetDynamicValue(value);
}
}
Now, the next part is to observe the properties SearchOptions and SearchText to call the search service and get results.
Update the constructor of the ViewModel as follows:
public class WhenPropertyChangedTests : ObservableObjectBase
{
public WhenPropertyChangedTests(ISearchService searchProvider, ISchedulerContext scheduler)
{
// When either property changes (creates a Tuple)
this.WhenPropertiesChanged(x => x.SearchText, x => x.SearchOptions)
// Throttle events within 500ms
.Throttle(TimeSpan.FromMilliseconds(500), scheduler.Default)
// Does the search
.Select(args => searchProvider.SearchForResults(args.Item1, args.Item2).ToObservable())
.Switch()
// Sets results on the ViewModel
.ObserveOn(scheduler.Dispatcher)
.Subscribe(results => SearchResults = results)
.DisposeWith(this);
}
public string SearchText
{
get => GetDynamicValue<string>();
set => SetDynamicValue(value);
}
public SearchOptions SearchOptions
{
get => GetDynamicValue<SearchOptions>();
set => SetDynamicValue(value);
}
public IEnumerable<ResultViewModel> SearchResults
{
get => GetDynamicValue<IEnumerable<ResultViewModel>>();
set => SetDynamicValue(value);
}
}