F# in Silverlight

F# in Silverlight

  • Comments 9

Over the last couple years, there has been an explosion of interest in Silverlight.  As a .NET-based runtime, it is possible to compile Silverlight applications with any .NET language, and we’ve seen a lot of F# developers using F# in Silverlight.  However, until recently this involved building an application using the desktop version of the F# runtime, which could result in some pitfalls and mixed levels of success.

With the recent F# May CTP though, we now provide a Silverlight version of the F# runtime, FSharp.Core.dll, along with the F# release.  This enables building truly first-class Silverlight components using F#.

To make this easier, I’ve posted some Silverlight F# project templates and samples on Code Gallery. 

Download

F# Templates and Samples for Silverlight

Templates

image

Samples

L-Systems

Lindenmayer Systems are an interesting way of generating a variety of fractals using a simple set of rewrite rules.  Check out the fascinating book The Algorithmic Beauty of Plants for details.  The Silverlight application below uses an L-System rewriter and rendered written in F#.

  • F, G, A, B are drawn as a move forward.
  • + is a turn right.
  • - is a turn right.
  • X, Y and anything else is skipped during rendering.

     

    open System.Windows
    open System.Windows.Shapes
    
    let rec internal applyRulesInOrder rules c =
        match rules with
        | [] -> string c
        | rule::rules' ->
            match rule c with
            | None -> applyRulesInOrder rules' c
            | Some result -> result
    
    let internal step rules current =
         current
         |> String.collect (applyRulesInOrder rules)
    
    let internal rotate (x,y) theta =
        let x' = x * cos theta - y * sin theta
        let y' = x * sin theta + y * cos theta
        (x',y')
    
    let rec internal render (x,y) (dx,dy) angle points system =
        match system with
        | [] -> (x,-y)::points
        | 'A'::system' | 'B'::system' | 'F'::system' | 'G'::system' ->
            let x',y' = x+dx,y+dy
            render (x',y') (dx,dy) angle ((x,-y)::points)  system'
        | '+'::system' ->
            let (dx',dy') = rotate (dx,dy) angle
            render (x,y) (dx',dy') angle points system'
        | '-'::system' ->
            let (dx',dy') = rotate (dx,dy) (-angle)
            render (x,y) (dx',dy') angle points system'
        | _::system' ->
            render (x,y) (dx,dy) angle points system'
    
    let rec internal applyN f n x =
        if n = 0 then x
        else f (applyN f (n-1) x)
    
    let internal normalize points =
        let minX = points |> Seq.map (fun (x,_) -> x) |> Seq.min
        let minY = points |> Seq.map (fun (_,y) -> y) |> Seq.min
        points |> List.map (fun (x,y) -> new Point(x-minX, y-minY))
    
    type LSystem(rulesString:string, start:string, angle:int, stepSize:int, n:int) =
        let expanded,isError =
            try
                let rules =
                    rulesString.Split([|"\r";"\n"|], System.StringSplitOptions.RemoveEmptyEntries)
                    |> Array.map (fun line -> line.Split([|"->"|], System.StringSplitOptions.RemoveEmptyEntries))
                    |> Array.map (fun fromAndTo -> (fromAndTo.[0].[0], fromAndTo.[1]))
                let ruleFunctions = [ for (c, s) in rules -> fun x -> if x = c then Some s else None]
                applyN (step ruleFunctions) n start, false
            with
            | e -> "", true
    
        member this.Render(polyline : Polyline) =
            let points = render (0.0,0.0) (float stepSize,0.0) (float angle * System.Math.PI / 180.0) [] (List.ofSeq expanded)
            for pt in normalize points do polyline.Points.Add(pt)
            isError
    
            

    Console Control

    A resuable Silverlight control providing a console emulation. The control exposes input and ouput streams akin to those on the System.Console class. Could be used to provide console input and output as part of a Silverlight application, or as a way to convert Windows Console apps to Silverlight apps.

    This samples hooks the Console up to a simple echo loop.

     

    namespace System.Windows.Controls
    
    open System
    open System.IO
    open System.Windows
    open System.Windows.Controls
    open System.Windows.Input
    open SilverlightContrib.Utilities.ClipboardHelper
    open System.Text
    
    // A shared base implementation of Stream for 
    // use by the console input and output streams
    [<AbstractClass>]
    type private ConsoleStream(isRead) = 
        inherit Stream()
        override this.CanRead = isRead
        override this.CanWrite = not isRead
        override this.CanSeek = false
        override this.Position
            with get() = raise (new NotSupportedException("Console stream does not have a position"))
            and  set(v) = raise (new NotSupportedException("Console stream does not have a position"))
        override this.Length = raise (new NotSupportedException("Console stream does not have a length"))
        override this.Flush() = ()
        override this.Seek(offset, origin) = raise (new NotSupportedException("Console stream cannot seek"))
        override this.SetLength(v) = raise (new NotSupportedException("Console stream does not have a length")) 
    
    /// A control representing a Console window
    /// Provides an InputStream and OutputStream
    /// for reading an writing character input.
    /// Also supports copy/paste on some browsers
    type SilverlightConsole() as self = 
        inherit TextBox()
    
        // The queue of user input which has been collected by the
        // console, but not yet read from the input stream
        let readQueue = new System.Collections.Generic.Queue<int>()
    
        // A stream that reads characters from user input
        let inputStream = 
            { new ConsoleStream(true) with
                override this.Write(buffer,offset,count) = 
                    raise (new NotSupportedException("Cannot write from Console input stream"))
                override this.Read(buffer,offset,count) = 
                    do System.Diagnostics.Debug.WriteLine("Starting to read {0} bytes", count)
                    let rec waitForAtLeastOneByte() =
                        let shouldSleep = ref true
                        let ret = ref [||]
                        lock readQueue  (fun () ->
                            shouldSleep := readQueue.Count < 1
                            if not !shouldSleep then 
                                let lengthToRead = min readQueue.Count count
                                ret := Array.init lengthToRead (fun i -> byte (readQueue.Dequeue())))
                        if !shouldSleep
                        then System.Threading.Thread.Sleep(100); waitForAtLeastOneByte()
                        else !ret
                    let bytes = waitForAtLeastOneByte()
                    System.Array.Copy(bytes, 0, buffer, offset, bytes.Length)
                    do System.Diagnostics.Debug.WriteLine("Finished reading {0} bytes", bytes.Length)
                    bytes.Length
            }
    
        // A stream that sends character output onto the console screen
        let outputStream =
            { new ConsoleStream(false) with
                override this.Read(buffer,offset,count) = 
                    raise (new NotSupportedException("Cannot read from Console output stream"))
                override this.Write(buffer,offset,count) = 
                    let isDelete = offset < 0
                    let newText =
                         if isDelete
                         then ""
                        else UnicodeEncoding.UTF8.GetString(buffer, offset, count)
                    let _ = self.Dispatcher.BeginInvoke(fun () ->
                        if isDelete then
                             if self.Text.Length >= count then
                                 self.Text <- self.Text.Substring(0, self.Text.Length - count)
                        else
                            do self.Text <- self.Text + newText
                        do self.SelectionStart <- self.Text.Length
                        do self.SelectionLength <- 0)
                    ()
            }
    
        let shiftNumbers = [|')';'!';'@';'#';'$';'%';'^';'&';'*';'('|]
        let currentInputLine = new System.Collections.Generic.List<int>()
    
        // Handles key down events
        // Processes the pressed key and turns it into console input
        // Also echos the pressed key to the console
        let keyDownHandler(keyArgs : KeyEventArgs) =
            try 
                do keyArgs.Handled <- true
                let shiftDown = Keyboard.Modifiers &&& ModifierKeys.Shift <> (enum 0)
                let ctrlDown = Keyboard.Modifiers &&& ModifierKeys.Control <> (enum 0)
                let p = keyArgs.PlatformKeyCode
                if ctrlDown || keyArgs.Key = Key.Ctrl then
                    if keyArgs.Key = Key.V then
                        lock currentInputLine (fun () ->
                            let clipboard = new ClipboardHelper()
                            let fromClipboard = clipboard.GetData()
                            for c in fromClipboard do
                                do currentInputLine.Add(int c)
                                outputStream.WriteByte(byte c)
                                if c = '\n' then
                                    for i in currentInputLine do
                                        do System.Diagnostics.Debug.WriteLine("Enqueued {0}", char i)
                                        do readQueue.Enqueue(i)
                                    do currentInputLine.Clear()
                        )
                    elif keyArgs.Key = Key.C then
                        let text = self.SelectedText
                        let clipboard = new ClipboardHelper()
                        clipboard.SetData(text)
                else
                    System.Diagnostics.Debug.WriteLine("Got key {0} {1} {2}", p, char p, keyArgs.Key)
                    let ascii =
                        match p with
                        | n when n >= 65 && n <= 90 -> if shiftDown then p else p+32
                        | n when n >= 48 && n <= 57 -> if shiftDown then int shiftNumbers.[p-48] else p
                        | 8 -> 8 // backspace
                        | 13 -> int '\n'
                        | 32 -> int ' '
                        | 186 -> if shiftDown then int ':' else int ';'
                        | 187 -> if shiftDown then int '+' else int '='
                        | 188 -> if shiftDown then int '<' else int ','
                        | 189 -> if shiftDown then int '_' else int '-'
                        | 190 -> if shiftDown then int '>' else int '.'
                        | 191 -> if shiftDown then int '?' else int '/'
                        | 192 -> if shiftDown then int '~' else int '`'
                        | 219 -> if shiftDown then int '{' else int '['
                        | 220 -> if shiftDown then int '|' else int '\\'
                        | 221 -> if shiftDown then int '}' else int ']'
                        | 222 -> if shiftDown then int '\"' else int '\''
                        | _ -> -1
                    if ascii = 8 then
                        lock currentInputLine (fun () ->
                            if currentInputLine.Count > 0 then currentInputLine.RemoveAt(currentInputLine.Count - 1)
                            outputStream.Write([||], -1, 1)
                        )
                    elif ascii > 0 then
                        lock currentInputLine (fun () ->
                            do currentInputLine.Add(ascii)
                            outputStream.WriteByte(byte ascii)
                        )
                    if ascii = int '\n' then
                         lock currentInputLine (fun () ->
                            for i in currentInputLine do
                                 do System.Diagnostics.Debug.WriteLine("Enqueued {0}", char i)
                                if i = 10 then
                                    do readQueue.Enqueue(13)
                                do readQueue.Enqueue(i)
                            do currentInputLine.Clear())
                    do self.SelectionStart <- self.Text.Length
                    do self.SelectionLength <- 0
            with
            | e -> System.Diagnostics.Debug.WriteLine(e)
    
        // Lazily initialized StreamReader/StreamWriter
        let outReader = lazy (new System.IO.StreamWriter(outputStream, Encoding.UTF8, 256, AutoFlush=true))
        let inReader = lazy (new System.IO.StreamReader(inputStream, Encoding.UTF8, false, 256))
    
        // Manually handle the Return key so we can accept newlines
        do self.AcceptsReturn <- true
        // Make sure a vertical scrollbar appears when needed
        do self.VerticalScrollBarVisibility <- ScrollBarVisibility.Auto
        // Make the control read-only so that users cannot move the cusor or change the contents
        // Unfortunatley, this also greys it out - ideally we could seperate theese two.
        do self.IsReadOnly <- true
        // Hookup the keyDownHandler
        do self.KeyDown.Add(keyDownHandler)
    
        /// The raw input stream for the Console
        member this.InputStream = inputStream :> Stream
        /// The raw ouput stream for the Console
        member this.OutputStream = outputStream :> Stream
    
        /// A StreamWriter for writing to the Console
        member this.Out = outReader.Value
        /// A StreamReader for reading from the Console
        member this.In = inReader.Value
    

    Summary

    Try out F# with Silverlight using the F# May CTP and the F# for Silverlight templates.

  • Leave a Comment
    • Please add 3 and 4 and type the answer here:
    • Post
    • That's a splendid idea! What about a LOGO implementation? I wished I had the time to do it.

      Anyway, well done and keep going.

    • Great FUN, F# and SL!

      Thanks

      Art

    • Great article, Luke!

      Looking forward to part 2:

      Hosting F# Interactive in Silverlight.

      Danny

    • This is just so COOL!!! I was waiting for a long time to use SL in F#, Thanks a lot!!!! :)

    • Too bad silverlight didn't give anything back to the browser or the OS while it was thinking and thrashing -- it had to power-cycle my machine when trying a complex hex rendering. No broswer action, no ctrl-shift-esc, no ctrl-alt-del...

    • Hi,

      Nice examples.  Do you have any idea when we might see project templates for F# Silverlight libraries for Visual Studio 2010 beta 1?

      thanks,

      Roly

    • Roly - We expect to include F# templates for Silverlight development along with Visual Studio 2010 Beta2.  For Beta1 though, we unfortunatley don't have a version of FSharp.Core.dll for Silverlight which ships with the release, so we can't easily provide an interim set of templates here, aside from those for VS2008, linked above.

    • No probs.  I'll wait for for beta 2!  Thanks again for the great examples - love the L-systems one.

    • This article's translated into Russian.

    Page 1 of 1 (9 items)