How to convert a WPF Button.Click Event into Observable using Rx and F#
Asked Answered
I

2

15

I am trying to replicate some C# code which creates an IObservable from a Button.Click event. I want to port this code to F#.

Here is the original C# code which compiles without errors:

Observable.FromEvent<RoutedEventHandler, RoutedEventArgs>(
                                    h => new RoutedEventHandler(h),
                                    h => btn.Click += h,
                                    h => btn.Click -= h))

Here is my failing attempt to do the same in F#:

Observable.FromEvent<RoutedEventHandler, RoutedEventArgs>(
            Func<EventHandler<RoutedEventArgs>, RoutedEventHandler>(fun h -> RoutedEventHandler(h)),
            Action<RoutedEventHandler>(fun h -> btn.Click.AddHandler h),
            Action<RoutedEventHandler>(fun h -> btn.Click.RemoveHandler h))

Everything is happy except for the second line of the statement.

The F# compiler complains about fun h -> RoutedEventHandler(h) because it doesn't want to except h as a parameter to the RoutedEventHandler constructor.

On th other hand the C# compiler seems to have no problem accepting h => new RoutedEventHandler(h)

Interestingly enough, in both code samples (C# and F#) the type of h is EventHandler<RoutedEventArgs>.

The error message I am getting from the F# compiler is:

Error 2 This expression was expected to have type obj -> RoutedEventArgs -> unit but here has type EventHandler

The signature for RoutedEventHandler that I found inside PresentationCore is:

public delegate void RoutedEventHandler(object sender, RoutedEventArgs e);

As you can see it does take an object and RoutedEventArgs as parameters, so the F# compiler is actually correct.

Is there some magic that the C# compiler does behind the scenes to make this work that the F# compiler doesn't or am I just missing something here?

Either way, how can I make this work in F#?

Inosculate answered 27/2, 2011 at 4:1 Comment(0)
M
17

The easiest way I know of to make an IObservable<_> out of a WPF Button.Click event is to cast it:

open System
open System.Windows.Controls

let btn = new Button()
let obsClick = btn.Click :> IObservable<_>

Examining obsClick...

val obsClick : IObservable<Windows.RoutedEventArgs>

This is possible because the F# representation of standard .NET events is the type (in this case) IEvent<Windows.RoutedEventHandler,Windows.RoutedEventArgs>. As you can see from the documentation, IEvent implements IObservable. In other words, in F# every single event already is an IObservable.

Melquist answered 27/2, 2011 at 6:25 Comment(0)
A
6

Joel Mueller is spot on, so just for the record: a direct translation of the C# code would be

Observable.FromEvent(
    (fun h -> RoutedEventHandler(fun sender e -> h.Invoke(sender, e))),
    (fun h -> b.Click.AddHandler h),
    (fun h -> b.Click.RemoveHandler h)
)
Animated answered 27/2, 2011 at 14:31 Comment(1)
Thanks, looking at the C# code in Reflector made me realize that 'h' actually gets expanded to 'h.Invoke', which then gets treated as a method group that represents '(s,e) => h.Invoke(s,e)' and thus satisfies the RoutedEventHandler signature. Once that is clear, it is obvious that you are right on the money with your suggested F# implementation. I also agree though, that the less verbose conversion suggested by Joel Mueller is to be preferred unless you need to get a hold of the sender directly (e.g. not via args.Source), since it only returns an IObservable of the RoutedEventArgs.Inosculate

© 2022 - 2024 — McMap. All rights reserved.