Thursday, February 26, 2009

F# Silverlight 2 and Isolated Storage

I recently picked up a copy of the book Silverlight 2 Recipes and wanted to try out some of the recipes by building F# version of them.   One of the first recipes that caught my eye was recipe 2.6 which talks about working with IsolatedStorage.  One of the main issues I had with implementing this recipe in F# is the fact that the public methods for IsolatedStorageFile is different between .NET Framework 3.5 and for Silverlight 2.  Therefore, I was unable to call the CreateFile method of IsolatedStorageFile .  But thankfully, this problem was already resolved by Michael Giagnocavo in his blog on F# 1.9.6.2 and Silverlight 2.  All I had to do is goto Project->[ProjectName] Properties –> Build and enter the following flag in “Other flags:”

--cliroot "C:\Program Files\Microsoft Silverlight\2.0.31005.0"

After configuring the ––cliroot flag, the source file compiled without problems.  Here’s the F# implementation of recipe 2.6 from the book:


#light
namespace SilverLightFSharp

open System.IO
open System.IO.IsolatedStorage
open System.Windows
open System.Windows.Controls
open System.Windows.Media
open System.ServiceModel
open List

// Recipe 2-6 from Silverlight 2 Recipes book
// Persisting Data on the Client

type MyPage() = class
inherit UserControl()

let settings = IsolatedStorageSettings.ApplicationSettings
let setting = "MySettings"
let filename = "FormFields.data"
let dataDirectory = "FormData"
let filepath = System.IO.Path.Combine(dataDirectory, filename)

//new () as this = {} then
do
let buildgrid color =
let grid = new Grid(Background = new SolidColorBrush(color))
let star = GridUnitType.Star

// Add column definitions
[0.06;0.455;0.485]
|> map (fun w -> new ColumnDefinition(Width=new GridLength(w,star)))
|> iter grid.ColumnDefinitions.Add

// Add row definitions
[0.08;0.217;0.61;0.093]
|> map (fun h -> new RowDefinition(Height=new GridLength(h,star)))
|> iter grid.RowDefinitions.Add

grid

let grid = buildgrid Colors.Gray

let gridadd (ui:#UIElement) row col =
grid.Children.Add(ui)
Grid.SetRow(ui,row)
Grid.SetColumn(ui,col)



let sp = new StackPanel(HorizontalAlignment = HorizontalAlignment.Stretch,
Margin=new Thickness(8.0,8.0,10.0,8.0))


new TextBlock(Text="Enter Setting Value",
TextWrapping=TextWrapping.Wrap,
Margin=new Thickness(4.0,4.0,4.0,4.0))
|> sp.Children.Add

let textdata = new TextBox(Height=126.0,
Text="",
TextWrapping=TextWrapping.Wrap,
Margin=new Thickness(4.0,4.0,4.0,4.0))
textdata |> sp.Children.Add

gridadd sp 2 1


// Build Form
let buildform () =
let brush = new LinearGradientBrush
(StartPoint = new Point(0.439999997615814,0.996999979019165),
EndPoint = new Point(0.560000002384186,0.00300000002607703))

[((255uy,58uy,108uy,87uy),0.0);
((255uy,163uy,189uy,163uy),0.536);
((255uy,58uy,108uy,87uy),0.968999981880188)]
|> map (fun (color,offset) -> new GradientStop(Color=(color |> Color.FromArgb), Offset=offset))
|> iter brush.GradientStops.Add

let form = new StackPanel(HorizontalAlignment = HorizontalAlignment.Stretch,
VerticalAlignment = VerticalAlignment.Stretch,
Margin = new Thickness(4.0,4.0,4.0,4.0))
let border = new Border
(HorizontalAlignment = HorizontalAlignment.Stretch,
Margin = new Thickness(8.0,8.0,10.0,8.0),
VerticalAlignment = VerticalAlignment.Stretch,
CornerRadius = new CornerRadius(10.0,10.0,10.0,10.0),
Child=form,
Background=brush)
gridadd border 2 2
Grid.SetRowSpan(border,2)

let createLabel label = new TextBlock(Text=label,
TextWrapping=TextWrapping.Wrap,
Margin = new Thickness(2.0,4.0,2.0,0.0))
let createTextbox () = new TextBox(TextWrapping=TextWrapping.Wrap,
Margin = new Thickness(2.0,0.0,2.0,2.0))


let formelements =
["First Name:";"Last Name:"; "Company:"; "Title:"]
|> map (fun label -> (createLabel label, createTextbox ()))


formelements |> iter (fun (label,tb) ->
form.Children.Add(label)
form.Children.Add(tb))

formelements


let createbtn thickness row col content fn =

let btn = new Button(HorizontalAlignment = HorizontalAlignment.Stretch,
Margin=thickness,
Content=content)
btn.Click.Add(fn)
btn.Click.Add(fun evt -> ())
gridadd btn row col


let form = buildform()
let fields = form |> map (fun (label,tb) -> tb)

// SaveFormData_Click
createbtn (new Thickness(8.0)) 1 1 "Save Form Data"
<| (fun evt ->
try
use store = IsolatedStorageFile.GetUserStoreForApplication()
store.CreateDirectory(dataDirectory)
use stream = store.CreateFile(filepath)
use writer = new StreamWriter(stream)
writer.AutoFlush <- true
let data = fields |> map (fun field -> field.Text + "|")
|> fold_left (+) ""
data |> writer.WriteLine
textdata.Text <- "Data saved to : " + filepath + "\n" + data
with
| :? IsolatedStorageException as ex -> textdata.Text <- "Error saving setting: " + ex.Message
| _ -> textdata.Text <- "Unable to open file... ")

// ReadFormData_Click
createbtn (new Thickness(8.0)) 1 2 "Load Form Data"
<| (fun evt ->
try
use store = IsolatedStorageFile.GetUserStoreForApplication()
use file = new IsolatedStorageFileStream(filepath,FileMode.Open, store)
use reader = new StreamReader(file)
let storedvalues = reader.ReadToEnd();
let data = storedvalues.Split([|'|'|])
|> Array.to_seq |> Seq.take (List.length fields)
|> Seq.to_list
zip fields data |> iter (fun (tb,data) -> tb.Text <- data)
// For checking...
textdata.Text <- storedvalues
with
| :? IsolatedStorageException as ex -> textdata.Text <- "Error saving setting: " + ex.Message
| _ -> textdata.Text <- "Unable to open file... ")

// ButtonUpdateSetting - repurposed for diagnostics
createbtn (new Thickness(4.0,4.0,14.0,4.0)) 3 1 "Update Setting"
<| (fun evt ->
use store = IsolatedStorageFile.GetUserStoreForApplication()
let dirs = store.GetDirectoryNames("*")
let files = store.GetFileNames("*")
let dirtext = "Directories = " + (dirs |> Array.to_list |> map (fun t -> "\n"+t) |>fold_left (+) "")
let filetext = "Filenames = " + (files |> Array.to_list |> map (fun t -> "\n"+t) |>fold_left (+) "")
textdata.Text <- dirtext + "\n" + filetext)

base.Width <- 400.0
base.Height <- 300.0
base.Content <- grid

// Loaded...
try
if settings.Keys.Count <> 0 then
textdata.Text <- settings.[setting].ToString()
with _ as ex -> textdata.Text <- "Error saving setting: " + ex.Message

end

type MyApp = class
inherit Application

new () as this = {} then
this.Startup.Add(fun _ -> this.RootVisual <- new MyPage())
//base.Exit.Add( fun _ -> ()) //this.Application_Exit)
//this.InitializeComponent()
end

2 comments:

Art said...

Hi John.
Thanks for your posts re: F# and SL. They are very helpful.

Anonymous said...

Well done, you seem to be very clever.