Wednesday, November 28, 2007

Learning WPF with F# - Canvas

Examples from Chapter 7 of Petzold's book "Applications = Code + Markup: A Guide to the Microsoft Windows Presentation Foundation"


PaintTheButton

#light
#I @"C:\Program Files\Reference Assemblies\Microsoft\Framework\v3.0"
#r @"WindowsBase.dll"
#r @"PresentationCore.dll"
#r @"PresentationFramework.dll"

open System
open System.Windows
open System.Windows.Controls
open System.Windows.Input
open System.Windows.Media
open System.Windows.Shapes

(* From Chap 7 - PaintTheButton.cs *)
type PaintTheButton = class
inherit Window as base

new () as this = {} then
this.Title <- "Paint the Button"

// Create the Button as content of the window
let btn = new Button()
btn.HorizontalAlignment <- HorizontalAlignment.Center
btn.VerticalAlignment <- VerticalAlignment.Center
this.Content <- btn

// Create the Canvas as content of the button
let canv = new Canvas()
canv.Width <- 144.0
canv.Height <- 144.0
btn.Content <- canv

// Create Rectangle as child of canvas
let rect = new Rectangle()
rect.Width <- canv.Width
rect.Height <- canv.Height
rect.RadiusX <- 24.0
rect.RadiusY <- 24.0
rect.Fill <- Brushes.Blue;
canv.Children.Add(rect) |>ignore
Canvas.SetLeft(rect, 0.0)
Canvas.SetRight(rect, 0.0)

// Create Polygon as child of canvas
let poly = new Polygon()
poly.Fill <- Brushes.Yellow
poly.Points <- new PointCollection()

for i in [0..4] do
let angle = Int32.to_float(i)*4.0*Math.PI/5.0
let pt = new Point(48.0*Math.Sin(angle),-48.0*Math.Cos(angle))
poly.Points.Add(pt)

canv.Children.Add(poly) |>ignore
Canvas.SetLeft(poly, canv.Width / 2.0)
Canvas.SetTop(poly, canv.Height / 2.0)

end

#if COMPILED
[<STAThread()>]
do
let app = new Application() in
app.Run(new PaintTheButton()) |> ignore
#endif

PlayJeuDeTacquin

#light
#I @"C:\Program Files\Reference Assemblies\Microsoft\Framework\v3.0"
#r @"WindowsBase.dll"
#r @"PresentationCore.dll"
#r @"PresentationFramework.dll"

open System
open System.Windows
open System.Windows.Controls
open System.Windows.Controls.Primitives
open System.Windows.Input
open System.Windows.Media
open System.Windows.Shapes
open System.Windows.Threading
//
// From Chap 7 - PlayJeuDeTacquin example
//
(* From Chap 7 - Tile.cs *)
type Tile = class
inherit Canvas as base

static member SIZE = 64.0
static member BORD = 6.0
val mutable txtblk : TextBlock

new () as this = {txtblk=null} then
this.Width <- Tile.SIZE
this.Height <- Tile.SIZE

// Upper-left shadowed border
let poly = new Polygon()
poly.Points <- new PointCollection
([|new Point(0.0,0.0)
new Point(Tile.SIZE,0.0)
new Point(Tile.SIZE-Tile.BORD,Tile.BORD)
new Point(Tile.BORD,Tile.BORD)
new Point(Tile.BORD,Tile.SIZE-Tile.BORD)
new Point(0.0,Tile.SIZE)|])
poly.Fill <- SystemColors.ControlLightLightBrush
this.Children.Add(poly)|>ignore

// Lower-right shadowed border
let poly = new Polygon()
poly.Points <- new PointCollection
([|new Point(Tile.SIZE,Tile.SIZE)
new Point(Tile.SIZE,0.0)
new Point(Tile.SIZE-Tile.BORD,Tile.BORD)
new Point(Tile.SIZE-Tile.BORD,Tile.SIZE-Tile.BORD)
new Point(Tile.BORD,Tile.SIZE-Tile.BORD)
new Point(0.0,Tile.SIZE)|])
poly.Fill <- SystemColors.ControlDarkBrush
this.Children.Add(poly)|>ignore

// Host for centered text
let bord = new Border()
bord.Width <- Tile.SIZE - 2.0 * Tile.BORD
bord.Height <- Tile.SIZE - 2.0 * Tile.BORD
bord.Background <- SystemColors.ControlBrush;
this.Children.Add(bord) |>ignore
Canvas.SetLeft(bord, Tile.BORD)
Canvas.SetTop(bord, Tile.BORD)

// Display of text
this.txtblk <- new TextBlock()
this.txtblk.FontSize <- 32.0
this.txtblk.Foreground <- SystemColors.ControlTextBrush
this.txtblk.HorizontalAlignment <- HorizontalAlignment.Center
this.txtblk.VerticalAlignment <- VerticalAlignment.Center
bord.Child <- this.txtblk

// Public property to set text
member this.Text
with get () = this.txtblk.Text
and set value = this.txtblk.Text <- value

end

(* From Chap 7 - Empty.cs *)
type Empty = class
inherit System.Windows.FrameworkElement as base

new () as this = {}

end

(* From Chap 7 - PlayJeuDeTacquin.cs *)
type PlayJeuDeTacquin = class
inherit Window as base

// Define constant member variables
member this.NumberRows = 4
member this.NumberCols = 4

val mutable unigrid : UniformGrid
val mutable xEmpty : int
val mutable yEmpty : int
val mutable iCounter : int
val mutable keys : Key array
val mutable elEmptySpare : UIElement


new () as this = {
unigrid=null
xEmpty = 0
yEmpty = 0
iCounter = 0
keys = [|Key.Left;Key.Right;Key.Down|]
elEmptySpare = ((new Empty()) :> UIElement) } then
this.Title <- "Jeu de Tacquin";
this.SizeToContent <- SizeToContent.WidthAndHeight;
this.ResizeMode <- ResizeMode.CanMinimize;
this.Background <- SystemColors.ControlBrush;

// Create StackPanel as content of window
let stack = new StackPanel()
this.Content <- stack

// Create Button at top of window
let btn = new Button()
btn.Content <- "_Scramble"
btn.Margin <- new Thickness(10.0)
btn.HorizontalAlignment <- HorizontalAlignment.Center

// lambda expressions allow us to remove the need for member variable rand
btn.Click.Add
(fun _ ->
let rand = new Random()
this.iCounter <- 16 * this.NumberCols * this.NumberRows
let tmr = new DispatcherTimer()
tmr.Interval <- TimeSpan.FromMilliseconds(10.0)
tmr.Tick.Add
(fun e ->
for i in [0..4] do
this.MoveTile this.xEmpty (this.yEmpty + rand.Next(3) - 1)
this.MoveTile (this.xEmpty + rand.Next(3) - 1) this.yEmpty
this.iCounter <- this.iCounter -1
if (this.iCounter = 0) then tmr.Stop())
tmr.Start())

stack.Children.Add(btn) |>ignore

// Create Border for aesthetic purposes.
let bord = new Border()
bord.BorderBrush <- SystemColors.ControlDarkDarkBrush
bord.BorderThickness <- new Thickness(1.0)
stack.Children.Add(bord) |>ignore

// Create Unigrid as Child of Border.
this.unigrid <- new UniformGrid()
this.unigrid.Rows <- this.NumberRows
this.unigrid.Columns <- this.NumberCols
bord.Child <- this.unigrid

for i in [1..(this.NumberRows*this.NumberCols-1)] do
let tile = new Tile()
tile.Text <- Int32.to_string(i)

tile.MouseLeftButtonDown.Add
(fun e ->
let iMove = this.unigrid.Children.IndexOf(tile)
let xMove = iMove % this.NumberCols
let yMove = iMove / this.NumberCols

if (xMove = this.xEmpty) then
while (yMove <> this.yEmpty) do
let yTarget = this.yEmpty +
(yMove - this.yEmpty)/Math.Abs(yMove -this.yEmpty)
this.MoveTile xMove yTarget

if (yMove = this.yEmpty) then
while (xMove <> this.xEmpty) do
let xTarget = this.xEmpty +
(xMove - this.xEmpty)/Math.Abs(xMove -this.xEmpty)
this.MoveTile xTarget yMove
())
this.unigrid.Children.Add(tile) |> ignore

this.unigrid.Children.Add(new Empty()) |>ignore
this.xEmpty <- this.NumberCols -1
this.yEmpty <- this.NumberRows -1

member this.MoveTile (xTile:int) (yTile:int) =
if ((xTile = this.xEmpty && yTile = this.yEmpty)
|| xTile < 0 || xTile >= this.NumberCols
|| yTile < 0 || yTile >= this.NumberRows) then
()
else
let iTile = this.NumberCols * yTile + xTile
let iEmpty = this.NumberCols * this.yEmpty + this.xEmpty
let elTile = this.unigrid.Children.[iTile]
let elEmpty = this.unigrid.Children.[iEmpty]

this.unigrid.Children.RemoveAt(iTile)
this.unigrid.Children.Insert(iTile, this.elEmptySpare)
this.unigrid.Children.RemoveAt(iEmpty)
this.unigrid.Children.Insert(iEmpty, elTile)
this.xEmpty <- xTile
this.yEmpty <- yTile
this.elEmptySpare <- elEmpty

end

#if COMPILED
[<STAThread()>]
do
let app = new Application() in
app.Run(new PlayJeuDeTacquin()) |> ignore
#endif


No comments: