Colonel Panic
Colonel Panic

Reputation: 137594

IObservable of keys pressed

So I can experiment with Reactive Extensions, I'd like to create an IObservable of keys pressed by the user. How can I do this?

This is for a C# console application

Upvotes: 3

Views: 777

Answers (4)

Asti
Asti

Reputation: 12667

The blocking versions of ReadKey() have a problem, in that if you dispose the subscription, it still prompts you to press a key.

If you want to have a clean unsubscription, i.e, be able to cancel the prompt, it's (unfortunately) necessary to go with a polling approach.

Observable.Interval(TimeSpan.FromMilliseconds(100))
          .Where(_ => Console.KeyAvailable)
          .Select(_ => (char)Console.ReadKey(false).Key)

You can now do cool things like Amb this stream with Observable.Timer to set up a timeout for keypresses.

Upvotes: 3

yamen
yamen

Reputation: 15618

An alternative to the method from @svick is to have the ReadKey loop as an Enumerable, and convert to an Observable. This puts it in the background.

    static IEnumerable<ConsoleKeyInfo> KeyPresses()
    {
        ConsoleKeyInfo key;
        do
        {
            key = Console.ReadKey();
            yield return key;
        } while (key.Key != ConsoleKey.Escape);
    }

We can generate the observables on the thread pool:

        var keys = KeyPresses().ToObservable(System.Reactive.Concurrency.Scheduler.ThreadPool);
        keys.Subscribe(key => Console.WriteLine("Pressed: {0}", key.Key));  

And wait on the main thread for the Escape key:

        keys.Where(key => key.Key == ConsoleKey.Escape).First();

Upvotes: 0

Enigmativity
Enigmativity

Reputation: 117064

Try this to get an observable sequence of read keys:

        IObservable<System.ConsoleKeyInfo> keys =
            Observable
                .Defer(() =>
                    Observable
                        .Start(() =>
                            Console.ReadKey()))
                .Repeat();

I tested this and it worked like a treat.

Upvotes: 4

svick
svick

Reputation: 244827

I don't see any way how to read key presses asynchronously, so I think you have to use the synchronous Console.ReadKey() on a separate thread, along with Subject<T>. Something like:

IObservable<ConsoleKeyInfo> ObserveKeysUntilEscape()
{
    var keys = new Subject<ConsoleKeyInfo>();

    Task.Run(
        () =>
        {
            ConsoleKeyInfo key;
            do
            {
                key = Console.ReadKey();
                keys.OnNext(key);
            } while (key.Key != ConsoleKey.Escape);
            keys.OnCompleted();
        });

    return keys;
}

Upvotes: 0

Related Questions