Sunday, December 28, 2008

F# CTP and Silverlight 2 and XAML

Following up from yesterday, the rudiments of the clock, to show XAML integration in the F# plus Silverlight combination. This should be enough of a template for starting with and mutating to taste.

The code is unchanged in the Feb 2010 (v 1.9.9.9) CTP.

astroclock.xaml:

<UserControl x:Class="astroclock.fs.Page"
xmlns="http://schemas.microsoft.com/client/2007"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Width="480" Height="400">
<Grid x:Name="LayoutRoot">
<Canvas Name="canvas1" Margin="0,0,0,129" Background="Black" Width="480" Height="240">
<TextBlock Height="30" FontSize="25" Canvas.Left="240" Canvas.Top="24" Name="hms" Foreground="White" TextAlignment="Center" VerticalAlignment="Center" HorizontalAlignment="Center" Width="240">::</TextBlock>
<TextBlock Height="30" FontSize="25" Canvas.Left="240" Canvas.Top="78" Name="day" Foreground="White" TextAlignment="Center" VerticalAlignment="Center" HorizontalAlignment="Center" Width="240">::</TextBlock>
<TextBlock Height="30" FontSize="25" Canvas.Left="240" Canvas.Top="132" Name="date" Foreground="White" TextAlignment="Center" VerticalAlignment="Center" HorizontalAlignment="Center" Width="240">::</TextBlock>
<TextBlock Height="30" FontSize="25" Canvas.Left="240" Canvas.Top="186" Name="sunup" Foreground="White" TextAlignment="Center" VerticalAlignment="Center" HorizontalAlignment="Center" Width="240">::</TextBlock>
<Line
Name="hour"
X1="120" Y1="120"
X2="120" Y2="60"
Stroke="White"
StrokeThickness="4">
<Line.RenderTransform>
<TransformGroup>
<RotateTransform Angle="0" CenterX="120" CenterY="120" />
</TransformGroup>
</Line.RenderTransform>
</Line>
<Line
Name="minute"
X1="120" Y1="120"
X2="120" Y2="30"
Stroke="White"
StrokeThickness="3">
<Line.RenderTransform>
<TransformGroup>
<RotateTransform Angle="0" CenterX="120" CenterY="120" />
</TransformGroup>
</Line.RenderTransform>
</Line>
<Line
Name="second"
X1="120" Y1="120"
X2="120" Y2="35"
Stroke="White"
StrokeThickness="1">
<Line.RenderTransform>
<TransformGroup>
<RotateTransform Angle="0" CenterX="120" CenterY="120" />
</TransformGroup>
</Line.RenderTransform>
</Line>
</Canvas>
<HyperlinkButton Height="28" HorizontalAlignment="Left" Margin="0,0,0,17" Name="hyperlink" VerticalAlignment="Bottom" Width="87" NavigateUri="astroclock.html" Content="Permalink" />
</Grid>
</UserControl>
view raw gistfile1.xml hosted with ❤ by GitHub

which we note has a UserControl as root, and astroclock.fs:

#light
namespace astroclock.fs
open System
open System.ComponentModel
open System.Globalization
open System.Windows
open System.Windows.Browser
open System.Windows.Controls
open System.Windows.Markup
open System.Windows.Media
open System.Windows.Shapes
type Page = class
inherit UserControl
val mutable page : string
val mutable query : System.Collections.Generic.IDictionary< string,string >
val animate : BackgroundWorker
val mutable culture : CultureInfo
val mutable hms : string
val mutable date : string
member this.everySecond() =
System.Threading.Thread.Sleep(1000)
this.animate.ReportProgress(0)
this.everySecond()
member this.moveHand name angle =
let line = this.FindName(name) :?> Line
let tg = line.RenderTransform :?> TransformGroup
let rotate = tg.Children.[0] :?> RotateTransform
rotate.Angle <- angle
member this.setText name value =
let block = this.FindName(name) :?> TextBlock
block.Text <- value
block
member this.ignore x =
()
member this.updateTick() =
let now = System.DateTime.Now
this.setText "hms" (now.ToString(this.hms, this.culture)) |> this.ignore
this.setText "day" (now.ToString("dddd", this.culture)) |> this.ignore
this.setText "date" (now.ToString(this.date, this.culture)) |> this.ignore
try
this.moveHand "second" (6.0 * float(now.Second))
this.moveHand "minute" (float(6*now.Minute)+(0.1*float(now.Second)))
this.moveHand "hour" (float(30*now.Hour)+(float(now.Minute)/2.0)+(float(now.Second)/300.0))
with x -> // debug technique
(this.setText "sunup" x.Message).FontSize <- 8.0
new () as this = {page = String.Empty;
query = null;
animate = new BackgroundWorker();
culture = null
hms = "HH:mm:ss";
date = "d-MMM-yyyy";
} then
System.Windows.Application.LoadComponent(this, new System.Uri("/astroclock.xaml", System.UriKind.Relative));
this.query <- System.Windows.Browser.HtmlPage.Document.QueryString
try
let culture_name = this.query.["locale"]
this.culture <- new CultureInfo(culture_name)
with _ ->
this.culture <- CultureInfo.CurrentCulture
try
this.hms <- this.query.["hms"]
with _ ->
this.hms <- this.culture.DateTimeFormat.LongTimePattern // HH:mm:ss
try
this.date <- this.query.["date"]
with _ ->
this.date <- this.culture.DateTimeFormat.ShortDatePattern // d-MMM-yyyy
let button = this.FindName("hyperlink") :?> HyperlinkButton
button.NavigateUri <- System.Windows.Browser.HtmlPage.Document.DocumentUri
this.page <- System.Windows.Browser.HtmlPage.Document.DocumentUri.GetComponents(
System.UriComponents.SchemeAndServer ||| System.UriComponents.Path,
System.UriFormat.SafeUnescaped)
this.animate.WorkerReportsProgress <- true
this.animate.DoWork.Add(fun _ -> this.everySecond())
this.animate.ProgressChanged.Add(fun _ -> this.updateTick())
this.animate.RunWorkerAsync()
this.updateTick ()
end
type MyApp = class
inherit Application
new () as this = {} then
this.Startup.Add(fun _ -> this.RootVisual <- new Page())
end
view raw gistfile1.fs hosted with ❤ by GitHub

with the obvious AppManifest.xaml:

<Deployment xmlns="http://schemas.microsoft.com/client/2007/deployment"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
RuntimeVersion="2.0.31005.0"
EntryPointAssembly="astroclock-fs"
EntryPointType="astroclock.fs.MyApp">
<Deployment.Parts>
<AssemblyPart x:Name="astroclock-fs" Source="astroclock-fs.dll" />
<AssemblyPart x:Name="FSharp.Core" Source="FSharp.Core.dll" />
</Deployment.Parts>
</Deployment>
view raw gistfile1.xml hosted with ❤ by GitHub

LATER: Data binding sort of works sometimes; I can get it to work when set at run-time to bind a string to a TextBlock via myTextBlock.DataContext <- this.StringProperty (but not yet through XAML), and Sliders are being totally recalcitrant...

I think there is some autogeneration magic just not happening for F# -- the best I can get seems to be to approximate a one-time binding -- so it's back to wiring up events by hand.

1 comment :

Art said...

Hi Steve.
I like what you're doing here.
I plan to try it.
Thanks,

Art Scott