timerf#system.drawing.graphics

f# timer event graphics.LineDraw not updating form


Being new to F#, I'm trying to understand how to make graphic updates in a Form triggered by timer events. My expectation was that the below simple routine should continue drawing new "random" lines every second. Calling line() outside the timer event works seemingly without any problems, but I cannot get my head around why nothing gets displayed on the screen when the very same function is invoked via the timer event.

open System
open System.Drawing
open System.Windows.Forms

let form = new Form(Text="Simple Animation", Size=Size(400,500))
let pen = new Pen(Color.Red, 4.0f)
let random = new Random()

let line x = 
    let flexRight = random.Next(29,300)
    form.Paint.Add (fun e -> e.Graphics.DrawLine(pen, 30, 30, 350, flexRight))

let timer=new Timer(Interval=1000, Enabled=true)
timer.Tick.Add(fun time -> line())

form.Show()
Application.Run(form)

Any help very much appreciated, Thanks.


Solution

  • The major problem with you code is that on each timer tick just another brand new Paint event handler is added to your form instead of invoking a single registered OnPaint callback that would perform the drawing.

    You may get rid of your line function definition and register instead a single Paint callback as

    form.Paint.Add(fun e -> e.Graphics.DrawLine(pen, 30, 30, 350, random.Next(29,300)))
    

    Then on each timer tick Paint event may be fired, for example, by invalidating the form. This may be achieved by changing timer's callback code to

    timer.Tick.Add(fun _ -> form.Invalidate())
    

    The entire behaving as expected snippet is listed below:

    #r "System.Windows.Forms"
    
    open System
    open System.Drawing
    open System.Windows.Forms
    
    let form = new Form(Text="Simple Animation", Size=Size(400,500))
    let pen = new Pen(Color.Red, 4.0f)
    let random = new Random()
    
    form.Paint.Add(fun e -> e.Graphics.DrawLine(pen, 30, 30, 350, random.Next(29,300)))
    
    let timer=new System.Windows.Forms.Timer(Interval=1000, Enabled=true)
    timer.Tick.Add(fun _ -> form.Invalidate())
    
    form.Show()
    

    UPDATE: as it came out the original intent was showing on the form the superposition of all subsequent drawn lines, I provide one of possible ways to accommodate such behavior with the help of GraphicsPath. Utilizing it would require the following changes to the snippet above: