Reactive Extensions - Properties Update Each Other

I have 2 DecimalUpDown controls, num_one and num_two, bound to the First and Second properties, respectively. When First changes, it will contact the server to calculate the Second value and vice versa. Dismissing the server causes the user interface to be released asynchronously, but when shooting fast (for example, the scroll wheel), the last request is not always the last to return, so the values ​​may stop synchronizing.

Using Reactive I am trying to disconnect calls only to only start a server call after the user has stopped making changes for a while. The problem is that when you make changes during an update, the start of property changes starts with each other and gets stuck back and forth depending on the TimeSpan of the throttle.

public MainWindow() { InitializeComponent(); DataContext = this; Observable.FromEventPattern<RoutedPropertyChangedEventHandler<object>, RoutedPropertyChangedEventArgs<object>>(h => num_one.ValueChanged += h, h => num_one.ValueChanged -= h) .Throttle(TimeSpan.FromMilliseconds(100), Scheduler.ThreadPool) .Subscribe(x => { Thread.Sleep(300); // simulate work Second = (decimal)x.EventArgs.NewValue / 3.0m; }); Observable.FromEventPattern<RoutedPropertyChangedEventHandler<object>, RoutedPropertyChangedEventArgs<object>>(h => num_two.ValueChanged += h, h => num_two.ValueChanged -= h) .Throttle(TimeSpan.FromMilliseconds(100), Scheduler.ThreadPool) .Subscribe(x => { Thread.Sleep(300); // simulate work First = (decimal)x.EventArgs.NewValue * 3.0m; }); } private decimal first; public decimal First { get { return first; } set { first = value; NotifyPropertyChanged("First"); } } private decimal second; public decimal Second { get { return second; } set { second = value; NotifyPropertyChanged("Second"); } } 
+4
source share
2 answers

There's a built-in Rx statement that can help you do exactly what you want without using Throttle and timeouts - it's a Switch statement.

The Switch statement does not work on IObservable<T> , so in most cases you will never see it in intellisense.

Instead, it runs on IObservable<IObservable<T>> - the stream of observables - and aligns the source to IObservable<T> , constantly switching to the last observable (and ignoring any values ​​from previous observables). It is completed only when the external observable is completed, and not internal.

This is exactly what you want - if a new change in value occurs, then ignore any previous results and return only the last.

Here's how to do it.

First, I removed the yucky event handling code for a couple of observables.

 var ones = Observable .FromEventPattern< RoutedPropertyChangedEventHandler<object>, RoutedPropertyChangedEventArgs<object>>( h => num_one.ValueChanged += h, h => num_one.ValueChanged -= h) .Select(ep => (decimal)ep.EventArgs.NewValue); var twos = Observable .FromEventPattern< RoutedPropertyChangedEventHandler<object>, RoutedPropertyChangedEventArgs<object>>( h => num_two.ValueChanged += h, h => num_two.ValueChanged -= h) .Select(ep => (decimal)ep.EventArgs.NewValue); 

Your code seems a bit confusing. I believe that the value of the DecimalUpDown is an input to a server function that returns a result. So, here are the functions that the server will call.

 Func<decimal, IObservable<decimal>> one2two = x => Observable.Start(() => { Thread.Sleep(300); // simulate work return x / 3.0m; }); Func<decimal, IObservable<decimal>> two2one = x => Observable.Start(() => { Thread.Sleep(300); // simulate work return x * 3.0m; }); 

Obviously, you are inserting your actual server code calls in these two functions.

Now it's almost trivial to plug in the final observables and subscriptions.

 ones .DistinctUntilChanged() .Select(x => one2two(x)) .Switch() .Subscribe(x => { Second = x; }); twos .DistinctUntilChanged() .Select(x => two2one(x)) .Switch() .Subscribe(x => { First = x; }); 

DistinctUntilChanged ensures that we will only make a call if the values ​​have really changed.

Then it is easy to call two server functions, execute Switch and return only the last result, which is then simply assigned to the property.

You may need to add a scheduler here or there and ObserveOn to get a subscription to the user interface thread, but otherwise this solution should work well.

+6
source

Modified property changes should not be triggered unless the property has changed. You need to add an if statement.

 public decimal First { get { return first; } set { if(first == value) return; first = value; NotifyPropertyChanged("First"); } } 
+1
source

All Articles