user974608
user974608

Reputation: 242

RX - Run code only if subject meets criteria

I'm using observables to handle keydown events. I'd like to publish an observable, that will always set the key to handled iff the consumer has actually processed it. Is this possible?

Here's an example how I set keys handled now:

KeyDown.Where(e => e.Key == Keys.W)
.Subscribe(e => { Console.WriteLine(e.Key); e.Handled = true; });

Here's an example how I'd like to do it (if the handled property was automatically set afterwards, I could just publish Keys instead of KeyDownEventArgs too):

HandledKeyDown.Where(k => k == Keys.W).Subscribe(k => Console.WriteLine(k));

Here's what I tried:

HandledKeyDown = Observable.Create<Key>(observer => {
    var keys = KeyDown.Subscribe(e => { e.Handled = true; observer.OnNext(e.Key); });
    return () => keys.Dispose();
});

The issue with this code is that the key will always be set as handled, regardless whether the consumer actually processed it.

Is there a way to know if the code has "reached" the subscribe method?

Upvotes: 0

Views: 214

Answers (2)

yamen
yamen

Reputation: 15618

What you'd like is to overload subscription on KeyEventArgs so that it makes an event as handled after the subscription. Sounds like an extension:

public static IDisposable SubscribeAndHandle(this IObservable<KeyEventArgs> input, Action<Keys> action)
{
    return input.Subscribe(e => { action(e.KeyCode); e.Handled = true; });
}

Then you just use SubscribeAndHandle instead of Subscribe, which will set Handled to true only after the action has been run, and the action can use Keys not KeyEventArgs:

var sub = KeyDown
          .Where(e => e.KeyCode = Keys.W) // and so on
          .SubscribeAndHandle(k => Console.WriteLine(k));

Note that you could use Do instead in the extension method if you don't mind that Handled is set to true before the action is performed. This is a cleaner way of running side effects:

public static IDisposable SubscribeAndHandle(this IObservable<KeyEventArgs> input, Action<Keys> action)
{
    return input.Do(e => e.Handled = true)
                .Select(e => e.KeyCode)
                .Subscribe(e => action(e));
}

A helpful comment from @Enigmativity suggested that perhaps you might like to decide whether Handled is set to true or not from within your subscription. This is simple enough, just change the extension method to accept a Func<Keys,bool> rather than an Action<Keys>:

public static IDisposable SubscribeAndHandle(this IObservable<KeyEventArgs> input, Func<Keys,bool> action)
{
    return input.Subscribe(e => e.Handled = action(e.KeyCode));
}

Upvotes: 1

Enigmativity
Enigmativity

Reputation: 117029

I would look at doing something like this:

var HandledKeyDown =
    KeyDown
        .Do(e => { /* Handling Code Here */ })
        .Where(e => e.Handled)
        .Where(e => e.Key == Keys.W)
        .Select(e => e.Key)
        .Subscribe(k => Console.WriteLine(k));

The Do extension method lets you "intercept" the value of an observable inline.

Let me know if this helps.

Upvotes: 0

Related Questions