Wednesday, March 26, 2008

Learning WPF with F# - Printing and Dialog Boxes

F# has a couple of basic generic collections including Array, List and Sequences. It was never clear in my mind when I should use what. My initial views are that I should use Array for mutable list, List for non-lazy and mutation free (aka immutable) list and Sequences for lazy, mutation-free lists. But these are conceptual understanding which is quite different from practical usage applications. I have almost no experience with lazy lists and bulk of my past programming experiences are with mutable lists. The only immutable list that I've dealt with on a consistent basis is String object and that's a pathological case in the sense that most of the time, I don't think of Strings as immutable lists.

So it was that imprecise view of lists that I tried to tackle Chapter 17 of Petzold's book Applications = Code + Markup: A Guide to the Microsoft Windows Presentation Foundation and ran into a wall while working with PrintWithMargins example. I built the margin TextBoxes as a sequence and tried to update the Text field of those TextBoxes and it would not work. Suddenly, it dawned on me that I should not be using Seq.find and instead should be using Array.find. It appears that Seq.find returns a copy of the TextBox while it is Array.find that returns the reference to the original TextBox. I suppose it's useful to maintain a distinction between these different collections as immutable lists are more amenable to be offloaded to different processors for processing while mutable lists must run on a single cpu.

Here are the examples from Chapter 17:


PrintOnClick

#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

let stack = new StackPanel()
let btn = new Button(Content = "_Print...",
HorizontalAlignment = HorizontalAlignment.Center,
Margin = new Thickness(24.0))

// PrintOnClick impelmentation
btn.Click.Add(fun _ ->
let dlg = new PrintDialog()

let vis = new DrawingVisual()

using (vis.RenderOpen()) (fun dc ->

(Brushes.LightGray,
new Pen(Brushes.Black, 3.0),
new Point(dlg.PrintableAreaWidth /2.0,
dlg.PrintableAreaHeight / 2.0),
dlg.PrintableAreaWidth / 2.0,
dlg.PrintableAreaHeight / 2.0) |> dc.DrawEllipse
)
(vis, "My first print job") |> dlg.PrintVisual
)


btn |> stack.Children.Add |> ignore

let window = new Window(Title="Print Ellipse",
FontSize=24.0,
Content=stack)

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

PrintWithMargins

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


open System
open System.Globalization
open System.Windows
open System.Windows.Controls
open System.Windows.Controls.Primitives
open System.Windows.Input
open System.Windows.Media
open System.Printing


//
// From Chapter 17 - PrintWithMargins
//
// PageMarginsDialog
type PageMarginsDialog() = class
inherit Window() as base

let sides = seq ["Left";"Right";"Top";"Bottom"]

let createTextBox label = new TextBox(Name=label,
MinWidth=48.0,
Text="1.40",
Margin = new Thickness(6.0))

let createMarginTextBoxes list = list |> Seq.map createTextBox

// I originally built this findTextBox with the following statement:
//
// let findTextBox (textboxes:TextBox array) side =
// textboxes |> Seq.find (fun textbox -> textbox.Name = side)
//
// and kept wondering why my PageMargins set function doesn't work.
// After fruitless flailing about with the debugger, it finally dawned on me
// that Seq.find returns a copy while Array.find returns the reference to
// the original TextBox, which is what I wanted.
let findTextBox (textboxes:TextBox array) side =
textboxes |> Array.find (fun textbox -> textbox.Name = side)

let mutable margins=null

do
margins <- Array.of_seq (createMarginTextBoxes sides)

base.Title <- "Page Setup"
base.ShowInTaskbar <- false
base.WindowStyle <- WindowStyle.ToolWindow
base.WindowStartupLocation <- WindowStartupLocation.CenterOwner
base.SizeToContent <- SizeToContent.WidthAndHeight
base.ResizeMode <- ResizeMode.NoResize

let stack = new StackPanel()
base.Content <- stack

let grid = new Grid(Margin = new Thickness(6.0))

let grpbox = new GroupBox(Header = "Margins (inches)",
Content = grid,
Margin = new Thickness(12.0))
grpbox |> stack.Children.Add |> ignore

[1..3] |> Seq.iter (fun _ ->
new RowDefinition(Height = GridLength.Auto)
|> grid.RowDefinitions.Add)

[1..4] |> Seq.iter (fun _ ->
new ColumnDefinition (Width = GridLength.Auto)
|> grid.ColumnDefinitions.Add)


// Use UniformGrid fo OK and Cancel Buttons
let unigrid = new UniformGrid(Rows = 1,Columns = 2)
unigrid |> stack.Children.Add |> ignore

let ok = new Button(Content="OK",
IsDefault = true,
IsEnabled = false,
MinWidth = 60.0,
Margin = new Thickness(12.0),
HorizontalAlignment = HorizontalAlignment.Center)
let dialog = base
ok.Click.Add(fun _ -> dialog.DialogResult <- new Nullable<Boolean>(true))
ok |> unigrid.Children.Add |> ignore

let cancel = new Button(Content="Cancel",
IsCancel = true,
IsEnabled = true,
MinWidth = 60.0,
Margin = new Thickness(12.0),
HorizontalAlignment = HorizontalAlignment.Center)
cancel |> unigrid.Children.Add |> ignore

sides |> Seq.iteri (fun i side ->
let lbl = new Label(Content="_" + side + ":",
Margin=new Thickness(6.0),
VerticalAlignment = VerticalAlignment.Center)
grid.Children.Add(lbl) |> ignore
Grid.SetRow(lbl,i/2)
Grid.SetColumn(lbl, 2*(i%2))

let txtbox = findTextBox margins side

txtbox.TextChanged.Add(fun _ ->
ok.IsEnabled <- margins
|> Seq.map (fun txtbox -> txtbox.Text)
|> Seq.map (fun x-> Double.TryParse(x) |> fst)
|> Seq.fold1 (fun x y -> x && y))

grid.Children.Add(txtbox) |> ignore
Grid.SetRow(txtbox,i/2)
Grid.SetColumn(txtbox,2*(i%2)+1)
)

member this.PageMargins
with get() =
Console.WriteLine("Getting Page Margins:")
margins |> Seq.iter (fun x -> Console.WriteLine(x.Text))
let findTextBoxSide = findTextBox margins
new Thickness(Double.Parse((findTextBoxSide "Left").Text)*96.0,
Double.Parse((findTextBoxSide "Right").Text)*96.0,
Double.Parse((findTextBoxSide "Top").Text)*96.0,
Double.Parse((findTextBoxSide "Bottom").Text)*96.0)
and set (value:Thickness) =
Console.WriteLine("Setting Page Margins:")
margins |> Seq.iter (fun x -> Console.WriteLine("{0} : {1}",x.Name,x.Text))
let findTextBoxSide = findTextBox margins
(findTextBoxSide "Left").Text <- (value.Left /96.0).ToString("F3")
(findTextBoxSide "Right").Text <- (value.Right /96.0).ToString("F3")
(findTextBoxSide "Top").Text <- (value.Top /96.0).ToString("F3")
(findTextBoxSide "Bottom").Text <- (value.Bottom /96.0).ToString("F3")
margins |> Seq.iter (fun x -> Console.WriteLine(x.Text))

end

let mutable prnqueue:PrintQueue = null
let mutable prntkt:PrintTicket = null

let mutable marginPage = new Thickness(96.0)
let stack = new StackPanel()
let window = new Window(Title="Print with Margins",
FontSize=24.0,
Content=stack)

let pagesetupBtn = new Button(Content = "Page Set_up...",
HorizontalAlignment = HorizontalAlignment.Center,
Margin = new Thickness(24.0))

pagesetupBtn |> stack.Children.Add |> ignore

let printBtn = new Button(Content = "_Print",
HorizontalAlignment = HorizontalAlignment.Center,
Margin = new Thickness(24.0))
printBtn |> stack.Children.Add |> ignore


pagesetupBtn.Click.Add(fun _ ->
let dlg = new PageMarginsDialog()
dlg.Owner <- window
dlg.PageMargins <- marginPage

let result = dlg.ShowDialog().GetValueOrDefault()
if result then
marginPage <- dlg.PageMargins
)

printBtn.Click.Add(fun _ ->
let dlg = new PrintDialog()

if prnqueue <> null then
dlg.PrintQueue <- prnqueue

if prntkt <> null then
dlg.PrintTicket <- prntkt

let vis = new DrawingVisual()
using (vis.RenderOpen()) (fun dc ->
let pen = new Pen(Brushes.Black,1.0)
let marginWidth = (marginPage.Left + marginPage.Top)
let marginHeight = (marginPage.Top + marginPage.Bottom)
let rectPage = new Rect(marginPage.Left,
marginPage.Top,
dlg.PrintableAreaWidth - marginWidth,
dlg.PrintableAreaHeight - marginHeight)
dc.DrawRectangle(null,pen,rectPage)

let formtxt = new FormattedText(String.Format("Hello, Printer {0} x {1}",
dlg.PrintableAreaWidth/96.0,
dlg.PrintableAreaHeight/96.0),
CultureInfo.CurrentCulture,
FlowDirection.LeftToRight,
new Typeface(new FontFamily("Times New Roman"),
FontStyles.Italic,
FontWeights.Normal,
FontStretches.Normal),
48.0,
Brushes.Black)

let ptText = new Point(rectPage.Left + (rectPage.Width - formtxt.Width) / 2.0,
rectPage.Top + (rectPage.Height - formtxt.Height) / 2.0)
let sizeText = new Size(formtxt.Width,formtxt.Height)

dc.DrawText(formtxt,ptText)
dc.DrawRectangle(null,pen,new Rect(ptText,sizeText))
)

dlg.PrintVisual(vis,window.Title)

()
)



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

PrintaBunchaButtons

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


open System
open System.Windows
open System.Windows.Controls
open System.Windows.Input
open System.Windows.Media
//
// From Chapter 17 - PrintaBunchaButtons
//

let btn = new Button(FontSize = 24.0,
Content = "Print ...",
Padding = new Thickness(12.0),
Margin = new Thickness(96.0))

let window = new Window(Title="Print a Bunch of Buttons",
SizeToContent = SizeToContent.WidthAndHeight,
ResizeMode = ResizeMode.CanMinimize,
Content=btn)


// PrintOnClick implementation
btn.Click.Add(fun _ ->
let dlg = new PrintDialog()
let retval = dlg.ShowDialog().GetValueOrDefault()
if retval then
let grid = new Grid(Background=new LinearGradientBrush(Colors.Gray,
Colors.White,
new Point(0.0, 0.0),
new Point(1.0, 1.0)))
[1..5] |> Seq.iter (fun _ ->
new ColumnDefinition(Width = GridLength.Auto)
|> grid.ColumnDefinitions.Add

new RowDefinition(Height = GridLength.Auto)
|> grid.RowDefinitions.Add)

let rand = new Random()

[0..24] |> Seq.iteri (fun i _ ->

let btn = new Button(FontSize=12.0 + (Int32.to_float (rand.Next(8))),
Content = "Button No. " + (Int32.to_string (i+1)),
HorizontalAlignment = HorizontalAlignment.Center,
VerticalAlignment = VerticalAlignment.Center,
Margin = new Thickness(6.0))
btn |> grid.Children.Add |> ignore
Grid.SetRow(btn, i%5)
Grid.SetColumn(btn,i/5))

// Size the grid
grid.Measure(new Size(Double.PositiveInfinity,
Double.PositiveInfinity))

let sizeGrid = grid.DesiredSize

// Determine point for centering Grid on page
let ptGrid = new Point((dlg.PrintableAreaWidth - sizeGrid.Width) / 2.0,
(dlg.PrintableAreaHeight - sizeGrid.Height) / 2.0)

// Layout pass
grid.Arrange(new Rect(ptGrid,sizeGrid))

// print it
dlg.PrintVisual(grid,window.Title))


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

PrintBanner

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

open System
open System.Globalization
open System.Printing
open System.Windows
open System.Windows.Controls
open System.Windows.Documents
open System.Windows.Input
open System.Windows.Media
//
// From Chapter 17 - PrintBanner
//
type BannerDocumentPaginator() = class
inherit DocumentPaginator() as base

let mutable txt = ""
let mutable face = new Typeface("")
let mutable sizeMax = new Size(0.0,0.0)
let mutable (sizePage:Size) = new Size(0.0,0.0)

let getFormattedText (ch:char) (face:Typeface) (em:double) =
new FormattedText(ch.ToString(),
CultureInfo.CurrentCulture,
FlowDirection.LeftToRight,
face,
em,
Brushes.Black)

member this.Text
with get() = txt
and set value = txt <-value

member this.Typeface
with get() = face
and set value = face <-value

override this.IsPageCountValid
with get() =
txt.ToCharArray() |> Seq.iter (fun ch ->
let formtxt = getFormattedText ch face 100.0
sizeMax.Width <- Math.Max(sizeMax.Width,formtxt.Width)
sizeMax.Height <- Math.Max(sizeMax.Height ,formtxt.Height )
)
true

override this.PageCount
with get() = if txt = null then 0 else txt.Length

override this.PageSize
with get() = sizePage
and set value = sizePage <- value

override this.Source
with get() = null

override this.GetPage (numPage) =
let vis = new DrawingVisual()
using (vis.RenderOpen()) (fun dc ->
let factor = Math.Min((this.PageSize.Width - 96.0) / sizeMax.Width,
(this.PageSize.Height - 96.0) / sizeMax.Height)
let formtxt = getFormattedText (txt.Chars numPage) face (factor*100.0)
let ptText = new Point((this.PageSize.Width - formtxt.Width) / 2.0,
(this.PageSize.Height - formtxt.Height) / 2.0)
dc.DrawText(formtxt,ptText))
//
new DocumentPage(vis)



end

// PrintBanner
let stack = new StackPanel()
let window = new Window(Title="Print Banner",
SizeToContent = SizeToContent.WidthAndHeight,
Content=stack)

let txtbox = new TextBox(Width=250.0,
Margin= new Thickness(12.0))
txtbox |> stack.Children.Add |> ignore

let btn = new Button(Content = "_Print...",
Margin = new Thickness(12.0),
HorizontalAlignment = HorizontalAlignment.Center)
btn |> stack.Children.Add |> ignore

btn.Click.Add(fun _ ->
let dlg = new PrintDialog()
let retval = dlg.ShowDialog().GetValueOrDefault()
if retval then
let prntkt = dlg.PrintTicket
prntkt.PageOrientation <- new Nullable<PageOrientation>(PageOrientation.Portrait)

// Create new BannerDocumentPaginator object.
let paginator = new BannerDocumentPaginator(Text=txtbox.Text)
paginator.PageSize <- new Size(dlg.PrintableAreaWidth,
dlg.PrintableAreaHeight)

dlg.PrintDocument(paginator, "Banner: " + txtbox.Text))


txtbox.Focus()



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

ChooseFont

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

open System
open System.Collections
open System.Printing
open System.Windows
open System.Windows.Controls
open System.Windows.Documents
open System.Windows.Input
open System.Windows.Media
//
// From Chapter 17 - ChooseFont
//
let minus1 x = x-1
let plus1 x = x+1

type Lister() = class
inherit ContentControl() as base

let list = new ArrayList()
let stack = new StackPanel()
let mutable indexSelected = -1
let scroll = new ScrollViewer()

let triggerSelectionChanged,selectionChangedEvent = IEvent.create()

let ScrollIntoView() =
if (list.Count > 0 &&
indexSelected <> -1 &&
scroll.ViewportHeight <= scroll.ExtentHeight) then

let heightPerItem = scroll.ExtentHeight / float list.Count
let offsetItemTop = float indexSelected * heightPerItem
let offsetItemBot = float (indexSelected+1) * heightPerItem
if offsetItemTop < scroll.VerticalOffset then
offsetItemTop |> scroll.ScrollToVerticalOffset
else if offsetItemBot > (scroll.VerticalOffset + scroll.ViewportHeight) then
(scroll.VerticalOffset +
offsetItemBot -
scroll.VerticalOffset -
scroll.ViewportHeight) |> scroll.ScrollToVerticalOffset

()

let highlightSelected () =
stack.Children
|> IEnumerable.untyped_to_typed
|> IEnumerable.iter (fun (txtblock:TextBlock) ->
txtblock.Background <- SystemColors.WindowBrush
txtblock.Foreground <- SystemColors.WindowTextBrush)

if indexSelected > -1 then
let txtblock = stack.Children.[indexSelected] :?> TextBlock
txtblock.Background <- SystemColors.HighlightBrush
txtblock.Foreground <- SystemColors.HighlightTextBrush

ScrollIntoView()

do
base.Focusable <- false

// Make border the content of ContentControl
let bord = new Border(BorderThickness = new Thickness(1.0),
BorderBrush = SystemColors.ActiveBorderBrush,
Background = SystemColors.WindowBrush)


scroll.Focusable <- false
scroll.Content <- stack
scroll.Padding <- new Thickness(2.0, 0.0, 0.0, 0.0)
bord.Child <- scroll

base.Content <- bord

let baseCtl = base

// Implement ScrollIntoView
baseCtl.Loaded.Add(fun _ -> ScrollIntoView())

// Handle mouse left button down event
// Define the handler
let LMouseClick _ (args:MouseButtonEventArgs) =
match args.Source with
| :? TextBlock as text ->
indexSelected <- stack.Children.IndexOf(text)
highlightSelected ()
triggerSelectionChanged()
| _ -> ()

// Adding new handler
base.AddHandler(TextBlock.MouseLeftButtonDownEvent,
new MouseButtonEventHandler(LMouseClick))

member this.SelectionChanged = selectionChangedEvent



member this.SelectedIndex
with get() =
indexSelected
and set value =
if ((value < -1) || (value >= this.Count)) then
raise ( ArgumentOutOfRangeException("SelectedIndex"))

indexSelected <- value
highlightSelected ()

// Trigger Selection Changed Event
triggerSelectionChanged()

member this.Add (o:obj) =
list.Add(o) |> ignore
let textblock = new TextBlock(Text=o.ToString())
stack.Children.Add(textblock) |> ignore


member this.Insert index (o:obj) =
list.Insert(index, o) |> ignore

(index,new TextBlock(Text=o.ToString()))
|> stack.Children.Insert |> ignore

member this.Clear() =
this.SelectedIndex <- -1
stack.Children.Clear()
list.Clear()

member this.Contains o =
list.Contains(o)

member this.Count
with get() = list.Count

member this.GoToLetter c =
// need to re-implement
let charlist = Array.map (fun (o:obj) -> o.ToString()) (list.ToArray())
try
let index = Array.find_index (fun item ->
try
(String.index (String.uppercase item) (Char.ToUpper(c))) = 0
with _ -> false) charlist
if index = this.SelectedIndex then
this.SelectedIndex <- index +1
else
this.SelectedIndex <- index
with _ -> this.SelectedIndex <- -1

member this.SelectedItem
with get() =
if this.SelectedIndex > -1 then
list.[this.SelectedIndex]
else
null
and set (value:obj) = this.SelectedIndex <- list.IndexOf(value)

member this.PageUp () =
if (this.SelectedIndex = -1 || this.Count = 0) then ()
else
let index = this.SelectedIndex -
Float.to_int ((Float.of_int this.Count) * scroll.ViewportHeight/scroll.ExtentHeight)
this.SelectedIndex <- Math.Max(index,0)

member this.PageDown () =
if (this.SelectedIndex = -1 || this.Count = 0) then ()
else
let index = this.SelectedIndex +
Float.to_int ((Float.of_int this.Count) * scroll.ViewportHeight/scroll.ExtentHeight)
this.SelectedIndex <- Math.Min(index,this.Count-1)

end


// TextBoxWithLister
type TextBoxWithLister() = class
inherit ContentControl() as base

let txtbox = new TextBox()
let lister = new Lister()
let mutable isReadOnly = true

let triggerSelectionChanged,selectionChangedEvent = IEvent.create()
let triggerTextChanged,textChangedEvent = IEvent.create()

do
let dock = new DockPanel()
base.Content <- dock

dock.Children.Add(txtbox) |> ignore
DockPanel.SetDock(txtbox,Dock.Top)
txtbox.TextChanged.Add(fun _ -> triggerTextChanged())

// ListerOnSelectionChanged
lister.SelectionChanged.Add(fun _ ->
if lister.SelectedIndex = -1 then
txtbox.Text <- ""
else txtbox.Text <- lister.SelectedItem.ToString()

// OnSelectionChanged(args)
triggerSelectionChanged()
)

dock.Children.Add(lister) |> ignore

()

member this.SelectionChanged = selectionChangedEvent
member this.TextChanged = textChangedEvent

member this.Add (o:obj) =
lister.Add(o)

member this.Insert index o =
lister.Insert index o

member this.Contains (o:obj) =
lister.Contains(o)

member this.Clear () =
lister.Clear()

member this.Text
with get() = txtbox.Text
and set value = txtbox.Text <- value

member this.Count
with get() = lister.Count

member this.IsReadOnly
with get() = isReadOnly
and set value = isReadOnly <- value


member this.SelectedIndex
with get() = lister.SelectedIndex
and set value =
lister.SelectedIndex <- value
if lister.SelectedIndex > -1 then
txtbox.Text <- lister.SelectedItem.ToString()
else txtbox.Text <- ""

member this.SelectedItem
with get() = lister.SelectedItem
and set (value:obj) =
lister.SelectedItem <- value
if lister.SelectedItem <> null then
txtbox.Text <- lister.SelectedItem.ToString()
else txtbox.Text <- ""


override this.OnMouseDown (args) =
base.OnMouseDown(args)
this.Focus() |> ignore

override this.OnPreviewTextInput (args) =
base.OnPreviewTextInput(args)
if isReadOnly then
lister.GoToLetter(args.Text.[0])
args.Handled <- true

override this.OnPreviewKeyDown (args) =
base.OnKeyDown(args)

if this.SelectedIndex > -1 then
match args.Key with
| Key.Home -> if lister.Count > 0
then this.SelectedIndex <- 0
| Key.End -> if lister.Count > 0
then this.SelectedIndex <- lister.Count -1
| Key.Down -> if lister.Count > 0
then this.SelectedIndex <- plus1 this.SelectedIndex
| Key.Up -> if this.SelectedIndex < (lister.Count-1)
then this.SelectedIndex <- minus1 this.SelectedIndex
| Key.PageUp -> lister.PageUp()
| Key.PageDown -> lister.PageDown()
| _ -> ()


end


// FontDialog
type FontDialog() = class
inherit Window() as base

let boxFamily = new TextBoxWithLister()
let boxStyle = new TextBoxWithLister()
let boxWeight = new TextBoxWithLister()
let boxStretch = new TextBoxWithLister()
let boxSize = new TextBoxWithLister()
let mutable canUpdate = false

let lblDisplay = new Label()

let setTypeface (box:TextBoxWithLister) (o:obj) =
if box.Contains(o) then box.SelectedItem <- o
else box.SelectedIndex <- 0


do
base.Title <- "Font"
base.ShowInTaskbar <- false
base.WindowStyle <- WindowStyle.ToolWindow
base.WindowStartupLocation <- WindowStartupLocation.CenterOwner
base.SizeToContent <- SizeToContent.WidthAndHeight
base.ResizeMode <- ResizeMode.NoResize


let gridMain = new Grid()
base.Content <- gridMain

[new GridLength(200.0, GridUnitType.Pixel); // TextBoxWithLister controls
new GridLength(150.0, GridUnitType.Pixel); // Sample text
GridLength.Auto] // Buttons
|> Seq.iter (fun height ->
let rowdef = new RowDefinition(Height=height)
gridMain.RowDefinitions.Add(rowdef))

gridMain.ColumnDefinitions.Add
(let coldef = new ColumnDefinition(Width=new GridLength(650.0, GridUnitType.Pixel))
coldef)

let gridBoxes = new Grid()
gridMain.Children.Add(gridBoxes) |> ignore

lblDisplay.Content <- "AaBbCc XxYzZz 012345"
lblDisplay.HorizontalContentAlignment <- HorizontalAlignment.Center
lblDisplay.VerticalContentAlignment <- VerticalAlignment.Center
gridMain.Children.Add(lblDisplay) |> ignore
Grid.SetRow(lblDisplay,1)

// Create 2 rows
[GridLength.Auto;
new GridLength(100.0, GridUnitType.Star)]
|> Seq.iter (fun height ->
let rowdef = new RowDefinition(Height=height)
gridBoxes.RowDefinitions.Add(rowdef))


// Create 5 columns
[175.0;100.0;100.0;100.0;75.0]
|> Seq.iter (fun size ->
let coldef = new ColumnDefinition(Width=new GridLength(size,GridUnitType.Star))
gridBoxes.ColumnDefinitions.Add(coldef))


[("Font Family",boxFamily,true);
("Style",boxStyle,true);
("Weight",boxWeight,true);
("Stretch",boxStretch,true);
("Size",boxSize,false); ]
|> Seq.iteri (fun i (label,txtboxlister,readOnlyFlag) ->
let lbl = new Label(Content=label,
Margin= new Thickness(12.0,12.0,12.0,0.0))
gridBoxes.Children.Add(lbl) |> ignore
Grid.SetRow(lbl,0)
Grid.SetColumn(lbl,i)
txtboxlister.IsReadOnly <- readOnlyFlag
txtboxlister.Margin <- new Thickness(12.0,0.0,12.0,12.0)
gridBoxes.Children.Add(txtboxlister) |> ignore
Grid.SetRow(txtboxlister,1)
Grid.SetColumn(txtboxlister,i))

// Create five-column Grid for Buttons
let gridButtons = new Grid()
gridMain.Children.Add(gridButtons) |>ignore
Grid.SetRow(gridButtons,2)

[1..5] |> Seq.iter ( fun _ ->
gridButtons.ColumnDefinitions.Add(new ColumnDefinition()))

let ok = new Button(Content = "OK",
IsDefault = true,
HorizontalAlignment = HorizontalAlignment.Center,
MinWidth = 60.0,
Margin = new Thickness(12.0))

let baseCtl = base
ok.Click.Add(fun _ -> basectl.DialogResult <- new Nullable<Boolean>(true))

gridButtons.Children.Add(ok) |> ignore
Grid.SetColumn(ok,1)

let cancel = new Button(Content = "Cancel",
IsCancel = true,
HorizontalAlignment = HorizontalAlignment.Center,
MinWidth = 60.0,
Margin = new Thickness(12.0))
gridButtons.Children.Add(cancel) |> ignore
Grid.SetColumn(cancel,3)

// Add content to FontFamily box
Fonts.SystemFontFamilies
|> Seq.iter (fun fam ->
boxFamily.Add(fam))

// Initialize font sizes
[8;9;10;11;12;14;16;18;20;22;24;26;28;36;48;72]
|> Seq.iter (fun size -> boxSize.Add(Int32.to_string size))

// Set selection changed events
boxFamily.SelectionChanged.Add( fun _ ->
canUpdate <- false

let family = boxFamily.SelectedItem :?> FontFamily
let checkfonts = boxFamily

// Get previous font values
let prevStyle =
if (boxStyle.Count > 0 && boxStyle.SelectedItem <> null) then
boxStyle.SelectedItem :?> FontStyle
else
FontStyles.Normal

let prevWeight =
if (boxWeight.Count > 0 && boxWeight.SelectedItem <> null) then
boxWeight.SelectedItem :?> FontWeight
else
FontWeights.Normal

let prevStretch =
if (boxStretch.Count > 0 && boxStretch.SelectedItem <> null) then
boxStretch.SelectedItem :?> FontStretch
else
FontStretches.Normal


boxStyle.Clear()
boxWeight.Clear()
boxStretch.Clear()

let setNormalTop (container:TextBoxWithLister) value defaultValue =
if container.Contains(value) then ()
else
if value = defaultValue then
container.Insert 0 (box(value))
else
container.Add value

family.FamilyTypefaces |> Seq.iter (fun ftf ->
// Set Style
setNormalTop boxStyle ftf.Style FontStyles.Normal
// Set Weight
setNormalTop boxWeight ftf.Weight FontWeights.Normal
// Set Stretch
setNormalTop boxStretch ftf.Stretch FontStretches.Normal)

// Need to rewrite this!
let setSelected (container:TextBoxWithLister) value =
if (container.Contains(value)) then
container.SelectedItem <- value
else
container.SelectedIndex <- 0

let style = boxStyle
let weight = boxWeight
let stretch = boxStretch


setSelected boxStyle prevStyle
setSelected boxWeight prevWeight
setSelected boxStretch prevStretch

lblDisplay.FontFamily <- family
lblDisplay.FontStyle <- (boxStyle.SelectedItem :?> FontStyle)
lblDisplay.FontWeight <- (boxWeight.SelectedItem :?> FontWeight)
lblDisplay.FontStretch <- (boxStretch.SelectedItem :?> FontStretch)
canUpdate <- true
)


boxWeight.SelectionChanged.Add( fun _ ->
if canUpdate then
lblDisplay.FontWeight <- (boxWeight.SelectedItem :?> FontWeight))

boxStyle.SelectionChanged.Add( fun _ ->
if canUpdate then
lblDisplay.FontStyle <- (boxStyle.SelectedItem :?> FontStyle))

boxStretch.SelectionChanged.Add( fun _ ->
if canUpdate then
lblDisplay.FontStretch <- (boxStretch.SelectedItem :?> FontStretch))

boxSize.SelectionChanged.Add( fun _ ->
let (isDouble,size) = Double.TryParse(boxSize.Text)
if isDouble then lblDisplay.FontSize <- (size/0.75))

boxFamily.Focus() |> ignore


member this.Typeface
with get() =
new Typeface((boxFamily.SelectedItem :?> FontFamily),
(boxStyle.SelectedItem :?> FontStyle),
(boxWeight.SelectedItem :?> FontWeight),
(boxStretch.SelectedItem :?> FontStretch))
and set (value:Typeface) =
setTypeface boxFamily (box(value.FontFamily))
setTypeface boxStyle (box(value.Style))
setTypeface boxWeight (box(value.Weight))
setTypeface boxStretch (box(value.Stretch))

member this.FaceSize
with get() =
let (isDouble,size) = Double.TryParse(boxSize.Text)
if isDouble then (size/0.75)
else 8.25
and set (value:double) =
let size = 0.75*value
boxSize.Text <- size.ToString()
if boxSize.Contains(size) then boxSize.SelectedItem <- size
else boxSize.Insert 0 (box(size.ToString()))

end

// PrintBanner
let stack = new StackPanel()
let window = new Window(Title="Choose Font",
Top=200.0,
Left=300.0,
SizeToContent = SizeToContent.WidthAndHeight)

let btn = new Button(Content=window.Title,
HorizontalAlignment = HorizontalAlignment.Center,
VerticalAlignment = VerticalAlignment.Center)
window.Content <- btn

btn.Click.Add(fun _ ->
let dlg = new FontDialog()
dlg.Owner <- window

dlg.Typeface <- new Typeface(window.FontFamily,
window.FontStyle,
window.FontWeight,
window.FontStretch)
dlg.FaceSize <- window.FontSize

let retval = dlg.ShowDialog().GetValueOrDefault()
if retval then
window.FontFamily <- dlg.Typeface.FontFamily
window.FontStyle <- dlg.Typeface.Style
window.FontWeight <- dlg.Typeface.Weight
window.FontStretch <- dlg.Typeface.Stretch
window.FontSize <- dlg.FaceSize)



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

PrintBetterBanner

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

open System
open System.Collections
open System.Printing
open System.Windows
open System.Windows.Controls
open System.Windows.Documents
open System.Windows.Input
open System.Windows.Media
open Chapter17
//
// From Chapter 17 - PrintBetterBanner
//
let stack = new StackPanel()
let window = new Window(Title="Print Better Banner",
Content=stack,
SizeToContent = SizeToContent.WidthAndHeight)

let mutable face = new Typeface(window.FontFamily,
window.FontStyle,
window.FontWeight,
window.FontStretch)

let txtbox = new TextBox(Width=250.0,
Margin=new Thickness(12.0))
stack.Children.Add(txtbox)

let fontclick _ =
let dlg = new FontDialog()
dlg.Owner <- window
dlg.Typeface <- face

if (dlg.ShowDialog().GetValueOrDefault()) then
face <- dlg.Typeface

let printclick _ =
let dlg = new PrintDialog()
if (dlg.ShowDialog().GetValueOrDefault()) then
let prntkt = dlg.PrintTicket
prntkt.PageOrientation <- new Nullable<PageOrientation>(PageOrientation.Portrait)
dlg.PrintTicket <- prntkt

let paginator = new BannerDocumentPaginator()

paginator.Text <- txtbox.Text
paginator.Typeface <- face
paginator.PageSize <- new Size(dlg.PrintableAreaWidth,
dlg.PrintableAreaWidth)
dlg.PrintDocument(paginator,"Banner: " + txtbox.Text)


// Add Font and Print Buttons
[("_Font...",fontclick);
("_Print...",printclick)]
|> Seq.iter (fun (label,func) ->
let btn = new Button(Content=label,
Margin=new Thickness(12.0),
HorizontalAlignment = HorizontalAlignment.Center)
stack.Children.Add(btn) |> ignore
btn.Click.Add(func))

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