2010-11-14

Create F# Silverlight Apps from Visual Studio 2010 Shell

It is possible to create and debug Silverlight applications using the free Visual Studio 2010 Shell (integrated mode) + F# 2.0 Free Tools + Silverlight 4 Tools. To demonstrate this, I translated the C# and XAML in my previous post  into F#:

namespace AreaConverter2
 
open System
open System.Windows
open System.Windows.Controls
open System.Windows.Media
open System.Windows.Shapes
open System.Windows.Data
 
open MediaExt // defines SolidColorBrush.FromArgb
 
type public AreaConverter() =
 
  /// calculates the area of a polygon
  let area2D (polygon:PointCollection) = 
    let n = polygon.Count
    let divideBy2 f = f * 0.5
    let step i = polygon.[i].X * polygon.[(i+1)%n].Y - polygon.[i].Y * polygon.[(i+1)%n].X
    {0..n-1}
    |> Seq.fold(fun acc i -> acc + step i ) 0.0
    |> divideBy2
 
  interface IValueConverter with
 
    member x.Convert(value, targetType, parameter, culture) =
      match value with
      | :? PointCollection as polygon ->
        let area = area2D polygon
        if targetType = typeof<double> then
          area |> box
        else if targetType = typeof<string> then
          match parameter with
          | :? string as p -> area.ToString(p, culture) |> box
          | _ -> area.ToString culture |> box
        else
          raise (NotImplementedException())
      | _ -> raise (NotImplementedException())
 
    member x.ConvertBack(value, targetType, parameter, culture) =
      raise (NotImplementedException())
 
type Euclidean() as this =
  inherit UserControl()
  do
    let layoutRoot = Grid()
    this.Content <- layoutRoot
    layoutRoot.Background <- SolidColorBrush.FromArgb "#efefef"
    layoutRoot.SetValue(ToolTipService.ToolTipProperty, "click to add point")
 
    let border = Border()
    layoutRoot.Children.Add border
    border.CornerRadius <- CornerRadius 3.
    border.Padding <- Thickness 4.
    border.Height <- 32.
    border.VerticalAlignment <- VerticalAlignment.Top
    border.Background <- SolidColorBrush.FromArgb "#FFC0C0C0" // Silver
 
    let stackPanel = StackPanel()
    border.Child <- stackPanel
    stackPanel.Orientation <- Orientation.Horizontal
    stackPanel.Children.Add(TextBlock(Text="area (units):", VerticalAlignment=VerticalAlignment.Center))
    let txt = TextBlock()
    stackPanel.Children.Add txt
    txt.FontWeight <- FontWeights.Bold
    txt.Foreground <- SolidColorBrush Colors.Black
    txt.VerticalAlignment <- VerticalAlignment.Center
    txt.Margin <- Thickness(5., 0., 5., 0.)
    let btnClear = Button(Content="clear shape")
    stackPanel.Children.Add btnClear
 
    let cnv2 = Canvas(HorizontalAlignment=HorizontalAlignment.Center, VerticalAlignment=VerticalAlignment.Center)
    layoutRoot.Children.Add cnv2
 
    let poly = Polygon()
    cnv2.Children.Add poly
    poly.Fill <- SolidColorBrush Colors.Red
    poly.Stroke <- SolidColorBrush Colors.Black
    poly.StrokeThickness <- 1.
 
    // start off with a square
    let points = PointCollection()
    points.Add(Point(0.,0.))
    points.Add(Point(100.,0.))
    points.Add(Point(100.,100.))
    points.Add(Point(0.,100.))
    poly.Points <- points
 
    let cnv = Canvas(HorizontalAlignment=HorizontalAlignment.Center, VerticalAlignment=VerticalAlignment.Center)
    layoutRoot.Children.Add cnv
 
    let binding = Binding()
    binding.Mode <- BindingMode.OneWay
    binding.Source <- poly
    binding.Path <- PropertyPath "Points"
    binding.Converter <- AreaConverter()
    txt.SetBinding(TextBlock.TextProperty, binding) |> ignore
 
    layoutRoot.MouseLeftButtonUp.Add(fun args ->
      let points = new PointCollection()
      poly.Points |> Seq.iter points.Add
      let newPoint = args.GetPosition poly
      points.Add newPoint
      poly.Points <- points
 
      let placeholder = Ellipse()
      placeholder.Fill <- SolidColorBrush Colors.Black
      placeholder.Width <- 4.
      placeholder.Height <- 4.
      let t = TranslateTransform()
      t.X <- newPoint.X - 2.
      t.Y <- newPoint.Y - 2.
      placeholder.RenderTransform <- t
      cnv.Children.Add placeholder
 
      let tb = TextBlock()
      tb.Foreground <- SolidColorBrush Colors.Gray
      tb.FontSize <- 8.
      tb.Text <- newPoint.ToString()
      tb.RenderTransform <- t
      cnv.Children.Add tb
    )
 
    btnClear.Click.Add(fun _ ->
      poly.Points <- PointCollection()
      cnv.Children.Clear()
    )
    ()
 
type App() as app =
  inherit Application()
  do
    app.Startup.Add(fun _ ->
      app.RootVisual <- Euclidean()
    )

All the source code for the solution is available in a Mercurial repository here. Once again, credit goes to Cristian Merighi for all original code. I just translated what wasn’t in F# already, into F#.

There are three caveats to be aware of:

  1. Add > New Item… – doesn’t seem to work, so create the files and use Add > Existing Item…
  2. XAML support – The XAML editor works, but compiling any XAML in the project leads to something like this:
    C:\Users\ctaggart\blog\examples100\AreaConverter2\button.xaml(1,1): error : The method or operation is not implemented.
  3. F5 – I mentioned the problem in my 2010-11-11 blog post and logged some more details in a Silverlight forum post. F5 is ignoring the OutputPath, so as a work-a-round in this project, I set the OutputPath to “Bin”.