2

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.

INW
  • 23
  • 3

1 Answers1

3

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:

  • before the line adding the form Paint event handler add the line creating the instance of GraphicsPath

    let gp = new System.Drawing.Drawing2D.GraphicsPath()

  • change the Paint event handler to

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

Gene Belitski
  • 10,270
  • 1
  • 34
  • 54
  • Thanks @gene-belitski for your kind answer. I do see your valuable point and thus my basic mistake. My question though had a small extra caveat, as the intention was to add more lines to the existing drawing, whereas your kindly suggested changes to the code redraws the form and hence loses all previously added graphics. – INW Jan 15 '18 at 21:55
  • @INW While addressing your code _major_ problem I did not heed to those that may be considered Win forms framework ill-uses. Continuing this hackish approach I've updated my answer allowing the form to superpose all subsequent `draws`. – Gene Belitski Jan 16 '18 at 01:52
  • The `GraphicsPath` was exactly what I was looking for. Thanks again! – INW Jan 16 '18 at 14:33