Jules
Jules

Reputation: 6346

WPF animation slow when the mouse is in the window

I'm trying create a moving rectangle (rect). This is the relevant code:

let timer = new Threading.DispatcherTimer();
timer.Tick.Add(fun _ -> Canvas.SetLeft(rect, Canvas.GetLeft(rect)+0.01) |> ignore)
timer.Start()

The problem is that the animation is slower if I move the mouse over the window. If I set the timer's interval:

timer.Interval <- TimeSpan.FromMilliSeconds(30.0)

Then the animation doesn't work at all. What am I doing wrong? Thanks.

Upvotes: 0

Views: 2240

Answers (1)

Robert
Robert

Reputation: 6437

I have two suggestions:

  1. if you are using f# interactive check you have installed a WPF event loop. F# interactive sets up a winforms eventloop by default, there is some compatibility between the winforms and WFP event loops, but not everything works to correctly. To install the event loop use the following script:

    #light

    // When running inside fsi, this will install a WPF event loop #if INTERACTIVE

    #I "c:/Program Files/Reference Assemblies/Microsoft/Framework/v3.0";; #I "C:/WINDOWS/Microsoft.NET/Framework/v3.0/WPF/";; #r "presentationcore.dll";; #r "presentationframework.dll";; #r "WindowsBase.dll";;

    module WPFEventLoop = open System open System.Windows open System.Windows.Threading open Microsoft.FSharp.Compiler.Interactive open Microsoft.FSharp.Compiler.Interactive.Settings type RunDelegate<'b> = delegate of unit -> 'b

     let Create() = 
         let app  = 
             try 
                 // Ensure the current application exists. This may fail, if it already does.
                 let app = new Application() in 
                 // Create a dummy window to act as the main window for the application.
                 // Because we're in FSI we never want to clean this up.
                 new Window() |> ignore; 
                 app 
              with :? InvalidOperationException -> Application.Current
         let disp = app.Dispatcher
         let restart = ref false
         { new IEventLoop with
              member x.Run() =   
                  app.Run() |> ignore
                  !restart
              member x.Invoke(f) = 
                  try disp.Invoke(DispatcherPriority.Send,new RunDelegate<_>(fun () -> box(f ()))) |> unbox
                  with e -> eprintf "\n\n ERROR: %O\n" e; rethrow()
              member x.ScheduleRestart() =   ()
                  //restart := true;
                  //app.Shutdown()
         } 
     let Install() = fsi.EventLoop <-  Create()
    

    WPFEventLoop.Install();;

    #endif

  2. I haven't tested this too well, but I think using an animation story board will give smother more consistent results, rather than using a timer. I think this would look something like (changing the width property haven't worked out how to change the position):

    #light

    open System open System.Windows open System.Windows.Controls open System.Windows.Shapes open System.Windows.Media open System.Windows.Media.Animation

    let rect = new Rectangle(Fill = new SolidColorBrush(Colors.Red), Height = 20., Width = 20., Name = "myRectangle")

    let canvas = let c = new Canvas() c.Children.Add(rect) |> ignore c

    NameScope.SetNameScope(canvas, new NameScope()); canvas.RegisterName(rect.Name, rect)

    let window = new Window(Content = canvas)

    let nfloat f = new Nullable(f)

    let myDoubleAnimation = new DoubleAnimation(From = nfloat 20.0, To = nfloat 50.0, Duration = new Duration(TimeSpan.FromSeconds(5.)), RepeatBehavior = RepeatBehavior.Forever) let myStoryboard = let sb = new Storyboard() sb.Children.Add(myDoubleAnimation) sb

    Storyboard.SetTargetName(myDoubleAnimation, rect.Name); Storyboard.SetTargetProperty(myDoubleAnimation, new PropertyPath(Rectangle.WidthProperty))

    rect.Loaded.Add(fun _ -> myStoryboard.Begin canvas)

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

    [] do main()

Upvotes: 4

Related Questions