Finally got something I'm satisfied with. I have to say, I really just absolutely love the whole idea of event composition in F#. It takes some time to adjust your thinking, but it's very elegant when you get a good solution.

This is what I finally came up with:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
let track_selection_rect (c:#Control) = 
    let fire_begin,event_begin = Event.create()
    let fire_update,event_update = Event.create()
    let fire_end,event_end = Event.create()
    let orig = ref None
    c.MouseDown.Add(fun args -> orig := Some args.Location; fire_begin(args))
    c.MouseMove.Add(fun args -> 
        match !orig with 
        | Some p -> fire_update (p, args.Location)
        | None -> ())
    c.MouseUp.Add(fun args -> 
        match !orig with
        | Some p -> fire_end(p, args.Location); orig := None
        | _ -> ())
    (event_begin, event_update, event_end)

let board_paint_event (c:#Control) = 
    let fire,event = Event.create()
    let selection = ref None

    let (_,drag_select,end_select) = Events.track_selection_rect c

    drag_select.Add(fun (orig,cur) -> selection_rect := Some (rect_of orig cur); c.Invalidate())
    end_select.Add(fun (down,up) -> selection_rect := None; c.Invalidate())

    c.Paint.Add(fun args -> fire(!selection_rect, args))

let paint_board rect args = 
    match rect with
    | None -> () (*do nothing, there's no selection rect to draw*)
    | Some r -> (*draw rect r*)

let x = new Panel()
(board_paint_event x).Add(paint_board)
By on 2/17/2009 6:02 PM ()

Hi divisortheory.
Thanks for your mouse posts, 2 years on, yet relavent.
Merry Winter Solstice 2011! Happy New Year 2012!!

I've been postpoining F# interactive manipulation until I read
Jon Harrop's F# .NET Journal article on Quickhull which got my attention -
"A key aspect of the GUI is the ability to drag and drop points in the point set and watch the convex hull as it is recomputed in real time. The following drag value is the current state of the outstanding drag operation, if any, represented by the index of the point being dragged:

1
2
    > let drag : int option ref = ref None;;
    val drag : int option ref = {contents = null;}

This design follows the mantra "make illegal states unrepresentable". Alternative designs in languages where this cannot be so easily expressed tend to use one boolean variable to indicate whether or not a drag is in progress and another integer variable to convey the index when meaningful. Using data structures such as the option type in F# precludes the possibility of non-sensical values being created and encountered at run-time, reducing the scope for errors and the number of run-time tests."

I'm trying to comprehend the lesson.
PLH OOE Art Scott twitter @semasiographic

By on 12/17/2011 10:03 AM ()

Hi Art --

What Jon is saying there is that well-written F# code will take advantage of F#'s powerful type system to guarantee the correctness of the code -- that is, the code cannot fail unexpectedly at run-time because you've explicitly handled all possible scenarios.

Here's an example: you're writing a function which takes a string representing an integer (e.g., "123") and parses it, returning the value (123).

Now, what happens if I pass the string "mwahaha!!!" to your function? Let's assume your function doesn't just crash when given an invalid string.

In C, your function might return the value -1 to indicate an invalid or "not set" state. While there's nothing wrong with this, it does mean that any developer who writes some code calling your function needs to explicitly check the return value to see if the string they passed was parsed correctly; also, they'll need to remember which value indicates that state. If they forget to do this and naively use the value returned by your function, everything will compile and work just fine -- until they accidentally pass an invalid string to your function and their program crashes at run-time.

In F#, this is handled in a much more elegant way; you'd define the return type of your function as "int option", and whenever it's passed an invalid string, you return None. Now, any code which calls your function is forced to explicitly specify what it's going to do if your function returns a value (None) indicating that it's input was invalid. And by "forced", I mean the F# compiler won't allow any code to call your string-parsing function unless it handles *both* the "valid" and "invalid" outputs of the function -- otherwise, it'll fail to compile and you'll know right away where a bug could have *potentially* occurred.

By on 12/17/2011 12:41 PM ()
IntelliFactory Offices Copyright (c) 2011-2012 IntelliFactory. All rights reserved.
Home | Products | Consulting | Trainings | Blogs | Jobs | Contact Us | Terms of Use | Privacy Policy | Cookie Policy
Built with WebSharper