I am trying to create an IObservable<string> from the following code, but i can't seem to find a way of how to properly unwrap the value of the event handler.
What is happening is that the PasswordBox might change, so whenever it does i want to observe on that, and provide a string resource whenever the password changed event is raised. It works fine if i do it with ordinary events but i am curious on how this would work using System.Reactive.
var passwordChanged = WhenPropertyChanged
.Where(name => nameof(PasswordBox) == name)
.Select(d => PasswordBox)
.Where(d => d != null)
.Select(box =>
{
return Observable.FromEvent<RoutedEventHandler, RoutedEventArgs>(
handler => box.PasswordChanged += handler,
handler => box.PasswordChanged -= handler);
}).Select(d => nameof(Password));
to me it seems like there has to be some way within the Select(box => ... part where i can return a different object (other than IObservable<IObservable<RoutedEventArgs>>), which can be used to subscribe to properly.
Doing it like the following works. But i think reactive does a better job of avoiding event handler memory leaks if you use it end to end.
var passwordHasChanged = new Subject<string>();
// listen for changes of the password
WhenPropertyChanged
.Where(name => nameof(PasswordBox) == name)
.Select(d => PasswordBox)
.Where(d => d != null)
.Subscribe(box =>
{
box.PasswordChanged += (sender, args) => passwordHasChanged.OnNext(nameof(Password));
});
passwordHasChanged.Subscribe(d => Log.Debug("Password changed"));
Avoid using Subjects wherever possible. Subjects are like the mutable variables of Rx, they don't compose and read imperatively rather than declaratively.
If you want events from only the last password input, use Switch.
Switch works on an IObservable<IObservable<T>>, and unsubscribes from the previous observable when it gets a newer observable.
var passwordChanged = WhenPropertyChanged
.Where(name => nameof(PasswordBox) == name)
.Select(d => PasswordBox)
.Where(d => d != null)
.Select(box =>
Observable.FromEvent<RoutedEventHandler, RoutedEventArgs>(
handler => box.PasswordChanged += handler,
handler => box.PasswordChanged -= handler);
)
.Switch()
.Select(d => nameof(Password));
Related
I have a class ClassWithProperty that contains a property, SomeProperty, that is an instance of another class, ClassWithEvent, with an event, SomeEvent. The ClassWithEvent object will regularly change. I need ClassWithProperty to keep a single updated subscription to the event in ClassWithEvent.
I've come up with one way to do it so far:
public ClassWithProperty()
{
IDisposable subscription = null;
this
.WhenAnyValue(x => x.SomeProperty)
.Do(x => subscription?.Dispose())
.Where(x => x != null)
.Subscribe(x => subscription =
Observable.FromEventPattern(x, nameof(x.SomeEvent))
.Subscribe(y => SomeMethod()));
}
This solution does work but it somehow does not feel like idiomatic Reactive code, and it also seems somewhat verbose for what I think should be a fairly typical problem to solve. Is there a better way to solve this sort of issue in Reactive?
It's very simple with a Switch operator that switches to the latest produced inner observable of a IObservable<IObservable<T>>. It disposes of the previous subscriptions and only gives you values from the latest observable.
IDisposable subscription =
this
.WhenAnyValue(x => x.SomeClass)
.Select(x => Observable.FromEventPattern(x, nameof(x.SomeEvent)))
.Switch()
.Subscribe(x => SomeMethod());
If SomeClass can be null (which your original code suggests) then you can do this:
IDisposable subscription =
this
.WhenAnyValue(x => x.SomeClass)
.Select(x =>
x == null
? Observable.Never<EventPattern<object>>()
: Observable.FromEventPattern(x, nameof(x.SomeEvent)))
.Switch()
.Subscribe(x => SomeMethod());
I'm trying to use Rx in my Kafka consumer.
public static event EventHandler<ConsumeResult<string, string>> GenericEvent;
then I have the following code
var observable = Observable.FromEventPattern<ConsumeResult<string, string>>(
x => GenericEvent += x,
x => GenericEvent -= x)
.Select(x => x.EventArgs);
while (!cancellationToken.IsCancellationRequested)
{
ConsumeResult<string, string> consumeResult = _consumer.Consume(cancellationToken);
GenericEvent(consumeResult.Topic, consumeResult);
}
then somewhere I use it like
var subscription = observable.Subscribe(message =>
{
Console.WriteLine($"{Thread.CurrentThread.ManagedThreadId} ** {message.Topic}/{message.Partition} #{message.Offset}: '{message.Value}'");
//_kafkaConsumer.Commit(messages);
});
Are the any possibility to run separated thread by topic name (consumeResult.Topic)? When consumer receive a message, it should redirect it to the corresponding thread by topic
Give this a go:
Observable
.Interval(TimeSpan.FromSeconds(0.1))
.Take(20)
.GroupBy(x => x % 3)
.SelectMany(xs => Observable.Using(() => new EventLoopScheduler(), els => xs.ObserveOn(els)))
.Subscribe(x => Console.WriteLine($"{x} {Thread.CurrentThread.ManagedThreadId}"));
This ensures that a thread is created in the new EventLoopScheduler() scheduler for each inner observable created by the GroupBy operator. The SelectMany flattens out the group, but keeps the EventLoopScheduler associated for each group.
In your case you GroupBy the consumeResult.Topic property.
Do make sure that your source observable ends as the threads live forever until they do. Calling Dispose() on the subscription is sufficient to end the observable.
I have a ViewModel in which I would like to listen for changes to items in two ReactiveLists, Payments and AccountPayments. The Lists are instantiated and the ChangeTrackingEnabled is set to true:
this.Payments = new ReactiveList<transaction.PaymentViewModel>();
this.Payments.ChangeTrackingEnabled = true;`
this.AccountPayments = new ReactiveList<AccountPaymentViewModel>();
this.AccountPayments.ChangeTrackingEnabled = true;`
Then, defined in my ViewModel, I have an ObservableAsPropertyHelper readonly property:
readonly ObservableAsPropertyHelper<decimal> _totalPayments;
public decimal TotalPayments { get { return _totalPayments.Value; } }
My intent is to set TotalPayments whenever any of these items change in both lists. I tried using WhenAny:
this.WhenAny(vm => vm.Payments.ItemChanged,
vm => vm.AccountPayments.ItemChanged,
(a, b) => a.Sender.Payments
.Where(x => x.Amount.HasValue).Sum(x => x.Amount.Value)
+ b.Sender.AccountPayments
.Where(x => x.Amount.HasValue).Sum(x => x.Amount.Value))
.ToProperty(this, vm => vm.TotalPayments, out _totalPayments);
While this compiles fine, it doesn't seem to catch the changes.
I also tried using WhenAnyObservable:
this.WhenAnyObservable(
vm => vm.Payments.ItemChanged,
vm => vm.AccountPayments.ItemChanged)
.Select(_ =>
this.Payments.Where(x => x.Amount.HasValue)
.Sum(x => x.Amount.Value)
+ this.AccountPayments.Where(x => x.Amount.HasValue)
.Sum(x => x.Amount.Value))
.ToProperty(this, vm => vm.TotalPayments, out _totalPayments);
But this won't compile.
Is there a way to accomplish what I am trying to do? Any help would be greatly appreciated.
The first won't work as it's observing property changes and ItemChanged itself won't change, it's an observable.
The second is pretty much correct, but requires a bit of a modification. WhenAnyObservable requires that all the observables are the same type. As you're uninterested in the actual result, you can select Unit and merge the two:
this.WhenAnyObservable(a => a.Payments.ItemChanged).Select(_ => Unit.Default)
.Merge(this.WhenAnyObservable(a => a.AccountPayments.ItemChanged).Select(_ => Unit.Default));
You can't select Unit.Default within WhenAnyObservable as it re-writes this expression to observe the property changes to make sure it has the latest observable. If neither Payments nor AccountPayments will change (i.e. they're read only), you can omit the WhenAnyObservable altogether:
Payments.ItemChanged.Select(_ => Unit.Default)
.Merge(AccountPayments.ItemChanged.Select(_ => Unit.Default));
We are using Rx to monitor activity within our silverlight application so that we can display a message to the user after a period of inactivity.
We are turning events (mouse moves etc.) into observables and then merging the observables together to create a single (allActivity) observable. We then throttle the allActivity observable using a timespan and something subscribes to be notified when the system has been inactive for a period of time.
How can I add a new observable/ sequence to this after the subscription (so that the subscription picks this up without unsubscribing and resubscribing).
e.g. merge several sequences together, throttle, subscribe. Now add an additional sequence to the observable that has been subscribed to.
Example code:
private IObservable<DateTime> allActivity;
public void CreateActivityObservables(UIElement uiElement)
{
// Create IObservables of event types we are interested in and project them as DateTimes
// These are our observables sequences that can push data to subscribers/ observers
// NB: These are like IQueryables in the sense that they do not iterate over the sequence just provide an IObservable type
var mouseMoveActivity = Observable.FromEventPattern<MouseEventHandler, MouseEventArgs>(h => uiElement.MouseMove += h, h => uiElement.MouseMove -= h)
.Select(o => DateTime.Now);
var mouseLeftButtonActivity = Observable.FromEventPattern<MouseButtonEventHandler, MouseButtonEventArgs>(h => uiElement.MouseLeftButtonDown += h, h => uiElement.MouseLeftButtonDown -= h)
.Select(o => DateTime.Now);
var mouseRightButtonActivity = Observable.FromEventPattern<MouseButtonEventHandler, MouseButtonEventArgs>(h => uiElement.MouseRightButtonDown += h, h => uiElement.MouseRightButtonDown -= h)
.Select(o => DateTime.Now);
var mouseWheelActivity = Observable.FromEventPattern<MouseWheelEventHandler, MouseWheelEventArgs>(h => uiElement.MouseWheel += h, h => uiElement.MouseWheel -= h)
.Select(o => DateTime.Now);
var keyboardActivity = Observable.FromEventPattern<KeyEventHandler, KeyEventArgs>(h => uiElement.KeyDown += h, h => uiElement.KeyDown -= h)
.Select(o => DateTime.Now);
var streetViewContainer = HtmlPage.Document.GetElementById("streetViewContainer");
var mouseMoveHandler = new EventHandler<HtmlEventArgs>(this.Moo);
bool b = streetViewContainer.AttachEvent("mousemove", mouseMoveHandler);
var browserActivity = Observable.FromEventPattern<Landmark.QDesk.ApplicationServices.IdleTimeoutService.MouseMoveHandler, HtmlEventArgs>(h => this.MyMouseMove += h, h => this.MyMouseMove -= h).Select(o => DateTime.Now);
// Merge the IObservables<DateTime> together into one stream/ sequence
this.allActivity = mouseMoveActivity.Merge(mouseLeftButtonActivity)
.Merge(mouseRightButtonActivity)
.Merge(mouseWheelActivity)
.Merge(keyboardActivity)
.Merge(browserActivity);
}
public IDisposable Subscribe(TimeSpan timeSpan, Action<DateTime> timeoutAction)
{
IObservable<DateTime> timeoutNotification = this.allActivity.Merge (IdleTimeoutService.GetDateTimeNowObservable())
.Throttle(timeSpan)
.ObserveOn(Scheduler.ThreadPool);
return timeoutNotification.Subscribe(timeoutAction);
}
There's an overload to Merge that takes in an IObservable<IObservable<TSource>>. Make the outer sequence a Subject<IObservable<TSource>> and call OnNext to it when you want to add another source to the bunch. The Merge operator will receive the source and subscribe to it:
var xss = new Subject<IObservable<int>>();
xss.Merge().Subscribe(x => Console.WriteLine(x));
xss.OnNext(Observable.Interval(TimeSpan.FromSeconds(1.0)).Select(x => 23 + 8 * (int)x));
xss.OnNext(Observable.Interval(TimeSpan.FromSeconds(0.8)).Select(x => 17 + 3 * (int)x));
xss.OnNext(Observable.Interval(TimeSpan.FromSeconds(1.3)).Select(x => 31 + 2 * (int)x));
...
The easiest way to do this would be to use an intermediate subject in place of the Merge calls.
Subject<DateTime> allActivities = new Subject<DateTime>();
var activitySubscriptions = new CompositeDisposable();
activitySubscriptions.Add(mouseMoveActivity.Subscribe(allActivities));
activitySubscriptions.Add(mouseLeftButtonActivity.Subscribe(allActivities));
//etc ...
//subscribe to activities
allActivities.Throttle(timeSpan)
.Subscribe(timeoutAction);
//later add another
activitySubscriptions.Add(newActivity.Subscribe(allActivities));
The Subject class will stop passing OnNext (and further OnError and OnCompleted) events from any of the observables it is subscribed to if it receives any OnError or OnCompleted.
The main difference between this approach and your sample is that it subscribes to all the events when the subject is created, rather than when you subscribe to the merged observable. Since all of the observables in your example are hot, the difference should not be noticeable.
I have a ReactiveUI-like view model. It has several properties of different types which fire NotifyPropertyChanged events and I want to subscribe a method that will be called when any is fired, but I am not interested in actual values.
My current code is a bit ugly (due to opaque true selects). Is there a way to express this which shows the intention of just caring when the event occurs?
this.ObservableForProperty(m => m.PropertyOne)
.Select(_ => true)
.Merge(this.ObservableForProperty(m => m.PropertyTwo).Select(_ => true))
.Subscribe(...)
I am merging about 8 properties, so it's more ugly than shown.
Since this looks like ReactiveUI, how about using the WhenAny operator:
this.WhenAny(x => x.PropertyOne, x => x.PropertyTwo, (p1, p2) => Unit.Default)
.Subscribe(x => /* ... */);
In general though, if you were combining arbitrary Observables, you could also write this a bit more clearly using the non-extension method:
Observable.Merge(
this.ObservableForProperty(x => x.PropertyOne).Select(_ => Unit.Default),
this.ObservableForProperty(x => x.PropertyTwo).Select(_ => Unit.Default),
this.ObservableForProperty(x => x.PropertyThree).Select(_ => Unit.Default)
).Subscribe(x => /* ... */);
Also, if you're subscribing to every property of a ReactiveObject, it's probably better to just use:
this.Changed.Subscribe(x => /* ... */);
You could make it an extension method to make the intent clearer:
public static IObservable<bool> IgnoreValue<T>(this IObservable<T> source)
{
return source.Select(_ => true);
}
...
this.ObservableForProperty(m => m.PropertyOne).IgnoreValue()
.Merge(this.ObservableForProperty(m => m.PropertyTwo).IgnoreValue())
.Subscribe(..);