Benjol
Benjol

Reputation: 66531

Combining events in WPF & F# (or, am I going crazy?)

Playing with a spike, I'm trying to drag a WPF label around. I translated Don Syme's mkMouseTracker code from here, and it looks like it should work, but for some reason, there is never any difference between the two args returned from mouseEvent, so my label never moves.

printfing seems to show that the last parameter gets swallowed/replaced with the values from moveArgs. Is this something to do with the use of ref?

Sorry the code block is a bit long, but it is self-contained.

#r "WindowsBase"
#r "PresentationCore"
#r "PresentationFramework"
#r "System.Xaml"

open System
open System.Windows
open System.Windows.Media
open System.Windows.Controls

let window = new Window(Name="Test",Width=500.0,Height=500.0)
window.Visibility <- Visibility.Visible
window.Show()

let l = new System.Windows.Controls.Label(Height = 30., Width = 150., 
            BorderBrush = Brushes.Black, BorderThickness = Thickness(3.))
window.Content <- l

let mkMouseTracker (c : #UIElement) =
  let event = new Event<_>()
  let lastArgs = ref None
  c.MouseDown.Add(fun args -> lastArgs := Some (args :> Input.MouseEventArgs ))
  c.MouseUp  .Add(fun args -> lastArgs := None)
  c.MouseMove.Add(fun moveArgs ->
    match !lastArgs with
    | Some last -> event.Trigger(last,moveArgs); lastArgs := Some moveArgs
    | None -> ())
  event.Publish

let mouseEvent = mkMouseTracker l
mouseEvent.Add(fun (args1, args2) ->
    let pos1, pos2 = args1.GetPosition(l), args2.GetPosition(l)
    l.RenderTransform <- TranslateTransform(pos2.X - pos1.X, pos2.Y - pos1.Y)
    l.Content <- sprintf "%A: %A -> %A" args1.Timestamp pos1 pos2)

Upvotes: 3

Views: 266

Answers (1)

Tomas Petricek
Tomas Petricek

Reputation: 243051

I think you need to get the position of the event immediately after the event is triggered. It is quite possible that WPF is re-using the mutable MouseEventArgs object for all the events (or something like that), so calling GetPosition later gives you the same point.

The following code does something. It doesn't work completely - the label is moved to a wrong place, so it still needs to be fixed, but it should get you started :-).

let mkMouseTracker (e : #UIElement) =
  let event = new Event<_>()
  let lastArgs = ref None
  e.MouseDown.Add(fun args -> lastArgs := Some(args.GetPosition(e)))
  e.MouseUp  .Add(fun args -> lastArgs := None)
  e.MouseMove.Add(fun moveArgs ->
    match !lastArgs with
    | Some last -> 
        let pos = moveArgs.GetPosition(e)
        event.Trigger(last,pos)
        lastArgs := Some pos
    | None -> ())
  event.Publish

let mouseEvent = mkMouseTracker l
mouseEvent.Add(fun (pos1, pos2) ->
    l.RenderTransform <- TranslateTransform(pos2.X - pos1.X, pos2.Y - pos1.Y)
    l.Content <- sprintf "%A" (pos2.X - pos1.X, pos2.Y - pos1.Y))

Upvotes: 3

Related Questions