I have two observables. One is from Observable.fromEvent(..), where the underlying event is the user checking a Winforms checkbox. The other is Observable.Interval(..) which I subscribe to in order to do some IO, and I would like to prevent this observable from doing IO, whenever the checkbox is not checked.
I could do it like this:
var gui = new GUI();
var booleans = Observable
.FromEvent<GUI.NewAllowHandler, bool>(
h => gui.NewAllow += h,
h => gui.NewAllow -= h)
Observable.Interval(TimeSpan.FromSeconds(10))
.CombineLatest(booleans, Tuple.Create)
.Where(t => t.Item2)
.Select(t => t.Item1)
.Subscribe(l => DoStuff(l));
but this has the overhead of mixing the booleans in and out of the stream. A nicer way of doing this would be, if I could generate a time-varying value from the booleans variable, which at all times had the value of the last event. Then I could do something like this:
var gui = new GUI();
var booleanState = Observable // typeof(booleanState) == ???
.FromEvent<GUI.NewAllowHandler, bool>(
h => gui.NewAllow += h,
h => gui.NewAllow -= h)
.TimeValue() // hypothetical syntax
Observable.Interval(TimeSpan.FromSeconds(10))
.Where(_ => booleanState)
.Subscribe(l => DoStuff(l));
, which to me seems much closer to the problem statement. Is there anything like this in Rx, or is there anything else, that could make such problems easier to handle?
The Where statement in your interval should work with a properly scoped normal bool:
var booleans = Observable
.FromEvent<GUI.NewAllowHandler, bool>(
h => gui.NewAllow += h,
h => gui.NewAllow -= h)
var isBoxChecked = false;
booleans.Subscribe(t => isBoxChecked = t);
Observable.Interval(TimeSpan.FromSeconds(10))
.Where(_ => isBoxChecked)
.Subscribe(l => DoStuff(l))
Edit: Per your comment, another way of doing it:
intervals = Observable.Interval(TimeSpan.FromSeconds(10));
booleans
.Where(t => t)
.SelectMany(_ => intervals.TakeUntil(booleans))
.Subscribe(l => DoStuff(l))
You need to model the checkbox checked state as Behavior and not as Event stream (because behavior has always a value and this value changes over a period of time - which fits with checkbox checked state). So you can do something like:
var booleans = new BehaviorSubject<bool>(chk.Checked)
var chkEvents = ... //generate boolean observable from checkbox check event
chkEvents.Subscribe(booleans);
Observable.Interval(TimeSpan.FromSeconds(10))
.Where(i => booleans.First())
.Subscribe(i => DoIO());
I'm going to give you two solutions. The first is a very simple and hopefully obvious one using only one observable. The second is a uses both observables.
Since you want to allow the IO only when the box is checked then this is the simplest approach:
Observable
.Interval(TimeSpan.FromSeconds(10))
.Where(_ => gui.IsChecked)
.Subscribe(l => DoStuff(l));
No need at all for the other observable.
But if you really need to use it then the Switch() extension method is your best bet. Try this:
booleans
.Select(b => b == true
? Observable.Interval(TimeSpan.FromSeconds(10))
: Observable.Empty<long>())
.Switch()
.Subscribe(l => DoStuff(l));
It's pretty clean and helps to show that there are empty periods if the checkbox is not ticked.
I hope this helps.
Related
I often have the situation where I want a UI element to "watch" an underlying value - supposing I am displaying an int - I want an an IObservable that I can subscribe to.
I have been using a Subject underneath, so I can just set it. That works really well... except for the first time. If I setup the subject - then later subscribe to it by opening a new UI element - it doesn't trigger an onnext until the next change.
I basically want something that works like a subject - but always and immediately does an onNext of the latest value to any new subscribers.
I know I can write such a construct myself - but it seems a common use case - is there something standard I'm missing?
You want either BehaviorSubject<T> or ReplaySubject<T>.
BehaviorSubject<T> replays the single most recent value and requires that you give an initial value in the constructor.
You use it like this:
var bs = new BehaviorSubject<int>(0);
bs.Subscribe(x => Console.WriteLine(x));
bs.OnNext(42);
bs.Subscribe(x => Console.WriteLine(x));
That would output 0, then 42, then 42 to the console.
ReplaySubject<T> is a little more general. It allows you to specify the number of values to replay, but doesn't enforce that you provide an initial value.
var rs = new ReplaySubject<int>(1);
rs.Subscribe(x => Console.WriteLine(x));
rs.OnNext(42);
rs.Subscribe(x => Console.WriteLine(x));
This produces 42, and then 42 to the console.
Compare this to a standard Subject<T>.
var s = new Subject<int>();
s.Subscribe(x => Console.WriteLine(x));
s.OnNext(42);
s.Subscribe(x => Console.WriteLine(x));
That just produces 42.
If you are using Rx in UI, you should take a look at ReactiveUI.
It has handy extension .ToProperty and more:
_isValid = this.WhenAnyValue(x => x.Username, x => x.Password, (user, password) => !string.IsNullOrEmpty(user) && !string.IsNullOrEmpty(password)).ToProperty(this, x => x.IsValid);
public bool IsValid => _isValid.Value;
This is basically what you are doing:
this.WhenAnyValue(x => x.Property) => property as observable
obs.ToProperty() => observable to property that you can bind to UI
both work with INotifyPropertyChanged
I currently have a feature that users Timer() to fire an observable immediately and then every x millseconds after.
HoldPayloads = Observable.Merge(
EnumeratedSymbolKeys.Select(
o => Observable.FromEventPattern<MouseButtonEventHandler, MouseButtonEventArgs>(
h => o.PreviewMouseLeftButtonDown += h,
h => o.PreviewMouseLeftButtonDown -= h)
.Select(_ => Observable.Timer(DateTimeOffset.Now, TimeSpan.FromMilliseconds(o.Frequency))
.TakeUntil(Observable.FromEventPattern<MouseButtonEventHandler, MouseButtonEventArgs>(
h => o.PreviewMouseLeftButtonUp += h,
h => o.PreviewMouseLeftButtonUp -= h)))
.Switch()
.Select(_ => o.Payload)));
What I'd like to have is The observable fire immediately when a button is clicked, then after initial longer delay start to repeat at either a faster interval or a decreasing interval down to a limit, something like this:
--x------x--x--x--x--x--x-->
or
--x------x----x---x--x-x-x->
I attempted to use Delay() combined with Scan() to generate exponentially lower values to delay by but couldn't get it to work. Was I on the right track? Any better ways to do something like this?
Revised code using the answer from Shlomo:
HoldPayloads = Observable.Merge(
EnumeratedSymbolKeys.Select(
o => Observable.FromEventPattern<MouseButtonEventHandler, MouseButtonEventArgs>(
h => o.PreviewMouseLeftButtonDown += h,
h => o.PreviewMouseLeftButtonDown -= h)
.Select(_ => Observable.Generate(
1,
q => true,
i => i+1,
i => i,
i => i==1
? TimeSpan.FromMilliseconds(0)
: i > 10
? TimeSpan.FromMilliseconds(50)
: TimeSpan.FromMilliseconds(500/i))
.TakeUntil(Observable.FromEventPattern<MouseButtonEventHandler, MouseButtonEventArgs>(
h => o.PreviewMouseLeftButtonUp += h,
h => o.PreviewMouseLeftButtonUp -= h)))
.Switch()
.Select(_ => o.Payload)));
Ended up modifying the conditional so that the first item was immediate.
Observable.Generate is your friend (discussed well on this page).
For increasingly frequent values, replace .Select(_ => Observable.Timer(DateTimeOffset.Now, TimeSpan.FromMilliseconds(o.Frequency)) with something like this:
Observable.Generate(
1,
_ => true,
i => i + 1,
i => i,
i => i > 10
? TimeSpan.FromMilliseconds(50)
: TimeSpan.FromMilliseconds(500 / i)
)
Whatever your pattern is, fit it into that last parameter. Think of Generate as like a reactive for loop with all the dials available to you.
EDIT:
With the code above, the first item from Generate will be delayed. If you want the first item immediately, then the second item delayed, you can do as follows:
Observable.Generate(
1,
_ => true,
i => i + 1,
i => i,
i => i > 10
? TimeSpan.FromMilliseconds(50)
: TimeSpan.FromMilliseconds(500 / i)
)
.StartWith(0)
So the original .Select(_ => Observable.Timer(DateTimeOffset.Now, TimeSpan.FromMilliseconds(o.Frequency) is now .Select(_ => Observable.Generate(...).StartWith(0))
I just thought I'd just add an answer to help to make the final query more readable. I haven't changed the answer in any way and I'm not looking to have this answer accepted or even up-voted. I just wanted to help add some clarity in how to understand the query.
I've just done a bit of substitution refactoring and have changed the query to be:
HoldPayloads =
EnumeratedSymbolKeys
.Select(o =>
MouseDowns(o)
.Select(_ => Generate().TakeUntil(MouseUps(o)))
.Switch()
.Select(_ => o.Payload))
.Merge();
To me this is much easier to read and the intent of the query is very clear.
It just leaves the definition of MouseDowns, MouseUps, and Generate. These are:
IObservable<Unit> MouseDowns(EnumeratedSymbolKey o) =>
Observable
.FromEventPattern<MouseButtonEventHandler, MouseButtonEventArgs>(
h => o.PreviewMouseLeftButtonDown += h, h => o.PreviewMouseLeftButtonDown -= h)
.Select(EncodingProvider => Unit.Default);
IObservable<Unit> MouseUps(EnumeratedSymbolKey o) =>
Observable
.FromEventPattern<MouseButtonEventHandler, MouseButtonEventArgs>(
h => o.PreviewMouseLeftButtonUp += h, h => o.PreviewMouseLeftButtonUp -= h)
.Select(EncodingProvider => Unit.Default);
IObservable<int> Generate() =>
Observable
.Generate(1, i => true, i => i + 1, i => i,
i => i == 1
? TimeSpan.FromMilliseconds(0)
: (i > 10 ? TimeSpan.FromMilliseconds(50) : TimeSpan.FromMilliseconds(500 / i)));
Now that these are separated out it's easier to confirm that each are correct and hopefully that the whole code is correct.
To generate a "measure" every 5 sec I'm doing something like :
var Events = Observable.
Interval(TimeSpan.FromSeconds(5)).
Select(i => factory.GenerateRandomMeasure())
I would like to do the same but based on an existing Measure collection.
I assume I have to do something like :
var Events = existingList.ToObservable();
But is It possible to do add an interval notion in order to get each list item with a interval? (one item every 5 sec for example)
You can do either of these which work just fine:
(1)
var Events =
Observable
.Interval(TimeSpan.FromSeconds(5))
.Zip(existingList, (i, x) => x)
.Select(i => factory.GenerateRandomMeasure());
(2)
var Events2 =
Observable
.Generate(
0,
x => x < existingList.Count,
x => x + 1,
x => existingList[x],
x => TimeSpan.FromSeconds(5))
.Select(i => factory.GenerateRandomMeasure());
The first is probably more sensible and easier to write. The second is very much worth learning if you don't know it already as .Generate is very powerful and can be used in a lot of places.
I want to use Sample to reduce the frequency of items coming out of my observable, but I want to immediately see the first event go through without being held up for the sample duration. After that I want the Sample to only give me an item on the sample interval.
The code I have for the simple Sample is:
var sampler = Observable
.Interval(TimeSpan.FromSeconds(2))
.Select(_ => Unit.Default);
var seq = Observable.FromEventPattern<IntEventArgs>(h => _eventSource.Happened += h, h => _eventSource.Happened -= h)
.Sample(sampler);
So I tried to use this to make it produce an item immediately, however that stops the observable working altogether:
var seq = Observable.FromEventPattern<IntEventArgs>(h => _eventSource.Happened += h, h => _eventSource.Happened -= h)
.Sample(Observable.Return(Unit.Default).Concat(sampler));
Then I thought maybe the problem is the Unit.Default part of the sampler so I tried getting rid of that but now that gives a compiler error:
var sampler = Observable
.Interval(TimeSpan.FromSeconds(2));
var seq = Observable.FromEventPattern<IntEventArgs>(h => _eventSource.Happened += h, h => _eventSource.Happened -= h)
.Observable.Return(Unit.Default).Concat(sampler);
I've tried googling for things like "c# immediate observable sample" but nothing shows up, I guess I'm using the wrong terminology but not sure what I do need...
Any ideas please?
Does this work for you?
var observable = Observable.Merge<IntEventArgs>(h => _eventSource.Happened += h,
h => _eventSource.Happened -= h)
.Publish()
.RefCount();
var seq = Observable.Merge<IntEventArgs>(observable.FirstAsync(),
observable.Skip(1).Sample(sampler));
The Publish() method makes sure that you register only once to your event.
I'm trying to tame a keyboard triggered event that without throttling would fire with a very high frequency. The code below works but it also delays the first invocation of GlobalCopy by the throttling timespan. Is there a way to get the first sample without delay?
observableGlobalCopy = Observable
.FromEventPattern<EventHandler, EventArgs>(h => this.GlobalCopy += h, h => this.GlobalCopy -= h);
observableGlobalCopy
.SubscribeOnDispatcher()
.Throttle(GlobalEventThottle)
.Subscribe(x => GlobalCopyHandler());
You could try something like this:
var observableGlobalCopy2 = observableGlobalCopy
.Window(() => Observable.Timer(TimeSpan.FromSeconds(1.0)))
.Select(_ => _.Take(1))
.Merge();
If I understand your requirements correctly, how about this?
observableGlobalCopy.Take(1)
.Concat(
observableGlobalCopy.Throttle(GlobalEventThottle))
.SubscribeOnDispatcher()
.Subscribe(x => GlobalCopyHandler());