Mar 112010

Hi folks,

I have recently been experimenting with combining Reactive X, WPF, and F# and have found the combination to be very palatable. I chose drag and drop as the test case because it is both non trivial and generally deeply stateful. The resulting Rx turns out to be one fifth the code of my original C#, much easier to read and has fewer errors.

Drag and Drop code:

    leftDown
        |> map (fun e1 -> (e1, mouseMove |> takeUntil leftUp |> takeUntil mouseLeave))
        |> filter (fun (e1, e2s) -> e1.Source.Equals(canvas) |> not)
        |> map (fun (e1, e2s) ->
                        let control = e1.Source :?> UIElement
                        let leftTop = (Canvas.GetLeft(control), Canvas.GetTop(control))
                        let pt1 = e1.GetPosition(canvas)
                        e2s |> map (fun e2 ->
                                        let pt2 = e2.GetPosition(canvas)
                                        (control, leftTop, pt2 - pt1)))
        |> mergeAll
        |> subscribe (fun (control, (left,top), delta) -> Canvas.SetLeft(control, left + delta.X); Canvas.SetTop(control, top + delta.Y))

As always, I am interested in feedback.

Cheers,

Matt

Full Code:

open System
open System.Linq
open System.Windows
open System.Windows.Input
open System.Windows.Controls
open System.Windows.Shapes
open System.Windows.Media
open System.Xaml
open System.IO
open System.Text
open System.Windows.Markup

let parseXAML (xaml : string) =
    use ms = new MemoryStream(Encoding.ASCII.GetBytes(xaml))
    ms.Position <- int64 0
    XamlReader.Load(ms)

// helper functions from Steffen Forkmann (http://www.navision-blog.de)
let asAction f = new System.Action(f)

let doNothing = asAction (fun () -> ())

let create f =
    Observable.Create<_>(fun x ->
        f x
        doNothing)

let fromEvent (event:IEvent<_,_>) = create (fun x -> event.Add x.OnNext)

let mergeAll (observables:IObservable<IObservable<'a>>) = Observable.Merge observables

let map f observable = Observable.Select(observable, Func<_,_>(f))

let takeUntil other source = Observable.TakeUntil(source,other)

let filter f observable = Observable.Where(observable, Func<_,_>(f))

let subscribeComplete next error completed (obs: IObservable<'a>) =
    obs.Subscribe(
        (fun x -> next x),
        (fun e -> error e),
        (fun () -> completed()))

let subscribeWithError next error obs =
    subscribeComplete next error (fun () -> ()) obs

let subscribe next obs =
    subscribeWithError next ignore obs

let window =
    let window =
        "<Window
            xmlns=\"http://schemas.microsoft.com/winfx/2006/xaml/presentation\"
            xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"
            Title=\"Event Sample\" Height=\"200\" Width=\"200\">
        <Canvas x:Name=\"canvas\" Background=\"LightBlue\">
            <Rectangle Canvas.Left=\"50\" Canvas.Top=\"50\"  Fill=\"Red\" Height=\"50\" Width=\"50\" />
            <TextBlock Canvas.Left=\"50\" Canvas.Top=\"120\"   Text=\"Hello World!\" />
        </Canvas>
    </Window>" |> parseXAML :?> Window
    let canvas = window.FindName("canvas") :?> Canvas

    let leftDown = canvas.MouseLeftButtonDown  |> fromEvent
    let leftUp   = canvas.MouseLeftButtonUp    |> fromEvent
    let mouseMove = canvas.MouseMove           |> fromEvent
    let mouseLeave = canvas.MouseLeave         |> fromEvent

    leftDown
        |> map (fun e1 -> (e1, mouseMove |> takeUntil leftUp |> takeUntil mouseLeave))
        |> filter (fun (e1, e2s) -> e1.Source.Equals(canvas) |> not)
        |> map (fun (e1, e2s) ->
                        let control = e1.Source :?> UIElement
                        let leftTop = (Canvas.GetLeft(control), Canvas.GetTop(control))
                        let pt1 = e1.GetPosition(canvas)
                        e2s |> map (fun e2 ->
                                        let pt2 = e2.GetPosition(canvas)
                                        (control, leftTop, pt2 - pt1)))
        |> mergeAll
        |> subscribe (fun (control, (left,top), delta) -> Canvas.SetLeft(control, left + delta.X); Canvas.SetTop(control, top + delta.Y))
        |> ignore

    window

let main() =
    let app = new Application()
    app.Run(window) |> ignore

[<STAThread>]
    do main()
Share and Enjoy:
  • Print
  • Digg
  • del.icio.us
  • Facebook
  • Google Bookmarks
  • RSS
  • StumbleUpon
  • Twitter
Posted by Matt Tagged with: , ,

3 Comments to “Drag and Drop using Rx and WPF in F#”

  1. [...] Matt Moloney’s Drag and Drop using Rx and WPF in F# [...]

  2. That’s some pretty neat stuff! However, the interop with xaml doesn’t look to handy – do you actually use it like this, or do you have some other trick to work more easily together with the xaml world?

  3. Matt says:

    Thanks. I’m in the habit of using XAML like this because it is easier to specify more advanced behavior. Code works fine *except for DataTemplates which have to be defined in XAML.

Leave a Reply

(required)

(required)