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.
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))
Related
I am making a practice simple app as I am learning swiftUI. My current problem seems to lie in this function:
variables
#State var cameras: [Camera] = []
#State var angle: Double = 0.0
func fetchAngle(){
COLLECTION_CAMERAS.whereField("active", isEqualTo: true).addSnapshotListener { snapshot, _ in
self.cameras = snapshot!.documents.compactMap({ try? $0.data(as: Camera.self)})
self.angle = cameras[0].angle
}
There can only be one active camera out of the three.
I know its bad practice but all the program is in the MainView() for simplicity, after I finish debugging I will use MVVM.
The angle variable is #Published, because I want to redraw the user interface when its updated.
The there is a set of 3 buttons and each time I press one of them, successfully update in firebase the active field to true or false.
As there are three cameras I fetch the collection and wish to only get the camera where the active field is true. So far so good, my problem starts (I think) with this code self.angle = cameras[0].angle. What I wish to achieve is to be able to set self.angle with the angle of the only camera that has active == true.
A form has been created. It can be seen, but 'Load' event doesn't fire. This is the simplest case:
type Program() as this =
let form_ = new Form(Visible=true, Text="Some Caption", Width=1024, Height=768)
do
form_.Load.Add(this.OnFormLoad)
form_.Show()
member x.OnFormLoad(e) =
Trace.WriteLine("OnFormLoad() entering...")
member x.form = form_
#if COMPILED
[<STAThread()>]
let program = new Program()
Application.Run(program.form)
#endif
Where am I wrong in this code?
Try removing Visible=true from the call that constructs the form.
I think that when you set Visible to true, the form gets immediately created and loaded, so the Load event is triggered during the form construction (before you setup the handler). I would also remove the call form_.Show(). I think the form will be opened by Application.Run.
I've prepared the following WinForms code to be as simple as possible to help answer my question. You can see I have a start button which sets up and runs 3 different async computations in parallel which each do some work and then update labels with a result. I have 3 cancel buttons corresponding to each async computation being run in parallel. How can I wire up these cancel buttons to cancel their corresponding async computations, while allowing the others to continue running in parallel? Thanks!
open System.Windows.Forms
type MyForm() as this =
inherit Form()
let lbl1 = new Label(AutoSize=true, Text="Press Start")
let lbl2 = new Label(AutoSize=true, Text="Press Start")
let lbl3 = new Label(AutoSize=true, Text="Press Start")
let cancelBtn1 = new Button(AutoSize=true,Enabled=false, Text="Cancel")
let cancelBtn2 = new Button(AutoSize=true,Enabled=false, Text="Cancel")
let cancelBtn3 = new Button(AutoSize=true,Enabled=false, Text="Cancel")
let startBtn = new Button(AutoSize=true,Text="Start")
let panel = new FlowLayoutPanel(AutoSize=true, Dock=DockStyle.Fill, FlowDirection=FlowDirection.TopDown)
do
panel.Controls.AddRange [|startBtn; lbl1; cancelBtn1; lbl2; cancelBtn2; lbl3; cancelBtn3; |]
this.Controls.Add(panel)
startBtn.Click.Add <| fun _ ->
startBtn.Enabled <- false
[lbl1;lbl2;lbl3] |> List.iter (fun lbl -> lbl.Text <- "Loading...")
[cancelBtn1;cancelBtn2;cancelBtn3] |> List.iter (fun cancelBtn -> cancelBtn.Enabled <- true)
let guiContext = System.Threading.SynchronizationContext.Current
let work (timeout:int) = //work is not aware it is being run within an async computation
System.Threading.Thread.Sleep(timeout)
System.DateTime.Now.Ticks |> string
let asyncUpdate (lbl:Label) (cancelBtn:Button) timeout =
async {
let result = work timeout //"cancelling" means forcibly aborting, since work may be stuck in an infinite loop
do! Async.SwitchToContext guiContext
cancelBtn.Enabled <- false
lbl.Text <- result
}
let parallelAsyncUpdates =
[|asyncUpdate lbl1 cancelBtn1 3000; asyncUpdate lbl2 cancelBtn2 6000; asyncUpdate lbl3 cancelBtn3 9000;|]
|> Async.Parallel
|> Async.Ignore
Async.StartWithContinuations(
parallelAsyncUpdates,
(fun _ -> startBtn.Enabled <- true),
(fun _ -> ()),
(fun _ -> ()))
Cancelling threads un-cooperatively is generally a bad practice, so I wouldn't recommend doing that. See for example this article. It can be done when you're programming with Thread directly (using Thread.Abort), but none of the modern parallel/asynchronous libraries for .NET (such as TPL or F# Async) use this. If that's really what you need, then you'll have to use threads explicitly.
A better option is to change the work function so that it can be cooperatively cancelled. In F#, this really just means wrapping it inside async and using let! or do!, because this automatically inserts support for cancellation. For example:
let work (timeout:int) = async {
do! Async.Sleep(timeout)
return System.DateTime.Now.Ticks |> string }
Without using async (e.g. if the function is written in C#), you could pass around a CancellationToken value and use it to check if cancellation was requested (by calling ThrowIfCancellationRequestsd). Then you can start the three computations using Async.Start (creating a new CancellationTokenSource for each of the computations).
To do something until they all complete, I would probably create a simple agent (that triggers some event until it receives a specifies number of messages). I don't think there is any more direct way to do that (because Async.Parallel uses the same cancellation token for all workflows).
So, I guess that the point of this answer is - if work is meant to be cancelled, then it should be aware of the situation, so that it can deal with it appropriately.
As Tomas mentioned, forcibly stopping a thread is a bad idea, and designing something that doesn't realize it is a thread to be able to stop raises flags, in my mind, but, if you are doing a long calculation, if you are using some data structure, such as a 2 or 3D array, then one option would be to be able to set that to null, but, this violates many concepts of F#, since what your function is working on should be not only immutable, but if there is some array that is going to be changed, then nothing else should be changing it.
For example, if you need to stop a thread that is processing a file (I had to do this before), then, since the file couldn't be deleted, as it was open, then I was able to open it in Notepad, then just delete all the content, and save it, and the thread crashed.
So, you may want to do something like this in order to accomplish your goal, but, I would suggest that you re-evaluate your design and see if there is a better way to do this.
I'm trying to write a game using F# and Silverlight and am struggling a bit with immutability.
I want to decouple the game from the view a little, so I put it in a module and made its update function return a new instance of the world-state, thus providing immutability.
The view (AppControl) is responsible for drawing the world.
However, I see no way around making the world a ref cell in the view.
Now, I think the mutable state is local enough to not cause any problems (please correct me, if I'm wrong), I am just curious, if someone can think of a way to avoid mutable state completely?
Here's the outline of the application, I tried to reduce the problem down to the essence:
open System
open System.Windows
open System.Windows.Controls
open System.Windows.Media
module Game =
type World = { State : int }
let init() =
{ State = 0 }
// immutable update loop
let updateWorld world =
{ State = world.State + 1 }
type AppControl() =
inherit UserControl()
let canvas = new Canvas()
let textBlock = new TextBlock()
let world = Game.init() |> ref // mutable world
let drawWorld (world : Game.World) =
textBlock.Text <- world.State.ToString()
// mutating game loop
let gameLoop world =
world := Game.updateWorld !world
drawWorld !world
()
do
canvas.Children.Add(textBlock)
base.Content <- canvas
CompositionTarget.Rendering.Add (fun _ -> gameLoop world)
type App() as this =
inherit Application()
let main = new AppControl()
do this.Startup.Add(fun _ -> this.RootVisual <- main)
The structure of your code looks fine - the mutable state is localized in the user interface (which is mutable anyway), so it is fine. You're not mutating the field from any closure, so you could use a mutable field (declared using let mutable world = ..) instead of ref cell.
To completely avoid the mutation, you can use asynchronous workflow (running on the GUI thread):
type AppControl() =
inherit UserControl()
let canvas = new Canvas()
let textBlock = new TextBlock()
let drawWorld (world : Game.World) =
textBlock.Text <- world.State.ToString()
// Asynchronous loop that waits for 'Rendering', updates
// the world & draws it and then continues waiting
let gameLoop world = async {
let! _ = Async.AwaitEvent CompositionTarget.Rendering
let newWorld = Game.updateWorld world
drawWorld newWorld
return! gameLoop newWorld }
do
canvas.Children.Add(textBlock)
base.Content <- canvas
gameLoop Game.init() |> Async.StartImmediate
The gameLoop function is asynchronous, so it doesn't block any thread. It is started using Async.StartImmediate, which means that it will run only on GUI thread (so accessing GUI elements & events from the body is safe). Inside the function, you can wait for event occurrence (using Async.AwaitEvent) and then do some action. The last line (return!) is a tail-call, so the function will continue running until the application is closed.
I'm trying to implement drag and drop in Silverlight using F# and asynchronous workflows.
I'm simply trying to drag around a rectangle on the canvas, using two loops for the the two states (waiting and dragging), an idea I got from Tomas Petricek's book "Real-world Functional Programming", but I ran into a problem:
Unlike WPF or WinForms, Silverlight's MouseEventArgs do not carry information about the button state, so I can't return from the drag-loop by checking if the left mouse button is no longer pressed. I only managed to solve this by introducing a mutable flag.
Would anyone have a solution for this, that does not involve mutable state?
Here's the relevant code part (please excuse the sloppy dragging code, which snaps the rectangle to the mouse pointer):
type MainPage() as this =
inherit UserControl()
do
Application.LoadComponent(this, new System.Uri("/SilverlightApplication1;component/Page.xaml", System.UriKind.Relative))
let layoutRoot : Canvas = downcast this.FindName("LayoutRoot")
let rectangle1 : Rectangle = downcast this.FindName("Rectangle1")
let mutable isDragged = false
do
rectangle1.MouseLeftButtonUp.Add(fun _ -> isDragged <- false)
let rec drag() = async {
let! args = layoutRoot.MouseMove |> Async.AwaitEvent
if (isDragged) then
Canvas.SetLeft(rectangle1, args.GetPosition(layoutRoot).X)
Canvas.SetTop(rectangle1, args.GetPosition(layoutRoot).Y)
return! drag()
else
return()
}
let wait() = async {
while true do
let! args = Async.AwaitEvent rectangle1.MouseLeftButtonDown
isDragged <- true
do! drag()
}
Async.StartImmediate(wait())
()
Thank you very much for your time!
The way to solve this issue is to use an overloaded AwaitEvent that allows you to wait for two events. Instead of just waiting for MouseMove, you can also wait for the MouseUp event - in the first case, you can continue moving and in the second case, you can return from the loop and stop drag&drop (this is actually discussed later in the book in section 16.4.5).
Here is the code - it actually uses AwaitObservable variant of the method (see below), which is a better choice in general, because it works with Observable.map and similar combinators (in case you wanted to use these).
let! args = Async.AwaitObservable(layoutRoot.MouseMove, layoutRoot.MouseUp)
match args with
| Choice1Of2(args) ->
// Handle the 'MouseMove' event with 'args' here
Canvas.SetLeft(rectangle1, args.GetPosition(layoutRoot).X)
Canvas.SetTop(rectangle1, args.GetPosition(layoutRoot).Y)
return! drag()
| Choice2Of2(_) ->
// Handle the 'MouseUp' event here
return()
As far as I know, the overloaded AwaitObservable method is not available in the F# libraries (yet), but you can get it from the book's web site, or you can use the following code:
// Adds 'AwaitObservable' that takes two observables and returns
// Choice<'a, 'b> containing either Choice1Of2 or Choice2Of2 depending
// on which of the observables occurred first
type Microsoft.FSharp.Control.Async with
static member AwaitObservable(ev1:IObservable<'a>, ev2:IObservable<'b>) =
Async.FromContinuations((fun (cont,econt,ccont) ->
let rec callback1 = (fun value ->
remover1.Dispose()
remover2.Dispose()
cont(Choice1Of2(value)) )
and callback2 = (fun value ->
remover1.Dispose()
remover2.Dispose()
cont(Choice2Of2(value)) )
// Attach handlers to both observables
and remover1 : IDisposable = ev1.Subscribe(callback1)
and remover2 : IDisposable = ev2.Subscribe(callback2)
() ))