Wednesday, January 30, 2008

Learning WPF with F# - The Menu Hierarchy

In working through Chapter 14 of Petzold's book,Applications = Code + Markup: A Guide to the Microsoft Windows Presentation Foundation, the example that stood out the most for me was the PopupContextMenu. Petzold's implementation required that you implement a subclass of Windows class and override the OnMouseRightButtonUp. In addition, the definition of the menu items is separated from the behavioral implementation of the menu items...requiring the developer to scroll back and forth to understand the behavior to the implemented menu items. In addition, the member functions that implements the menu item behaviors does so for all the menu items.

In contrast, F# allows you to isolate the menu item creation and implement the behavior together without the need to subclass Window, override OnMouseRightButtonUp or have an "overlord" method MenuOnClick that implements the behavior of all the menu items. The creation of the menu items along with the behavioral implementation are all colocated and then the parent Menu object is attached to a standard Window object. I think this illustrates the power of functional programming language to build reusable component and then compose the components together to build functionality. Given what I've seen so far, I believe that F# and other functional programming languages will help move closer to the holy grail of reusable software components.

Here are the code samples:


PeruseTheMenu

#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
//
// From Chapter 14 - PeruseTheMenu
//
let dock = new DockPanel()

let window = new Window(Title="Peruse the Menu",
SizeToContent = SizeToContent.WidthAndHeight,
Content=dock)

// Define commands
let unimplemented args (item:MenuItem) =
let miLabel = item.Header.ToString().Replace("_"," ")
MessageBox.Show("The " + miLabel + " option has not yet been implemented",
window.Title) |> ignore

let sizeOnCheck (item:MenuItem) =
window.SizeToContent <-
match item.IsChecked with
| true -> SizeToContent.WidthAndHeight
| _ -> SizeToContent.Manual

let exitCmd () = window.Close()

// Add a menu to dock
let menu = new Menu()
dock.Children.Add
(// Add "File" Menu
menu.Items.Add
(let miFile = new MenuItem(Header = "_File")
// Add "New" MenuItem
miFile.Items.Add
(let item = new MenuItem(Header="_New")
item.Click.Add (fun args -> unimplemented args item)
item)|> ignore
// Add "Open" MenuItem
miFile.Items.Add
(let item = new MenuItem(Header="_Open")
item.Click.Add (fun args -> unimplemented args item)
item)|> ignore
// Add "Save" MenuItem
miFile.Items.Add
(let item = new MenuItem(Header="_Save")
item.Click.Add (fun args -> unimplemented args item)
item)|> ignore
// Add separator
miFile.Items.Add(new Separator()) |> ignore
// Add "Exit MenuItem
miFile.Items.Add
(let item = new MenuItem(Header="_Exit")
item.Click.Add (fun _ -> exitCmd ())
item)|> ignore
miFile)|> ignore
// Add "Window" Menu
menu.Items.Add
(let miWindow = new MenuItem(Header = "_Window")
// Add "Show in Taskbar" menuitem
miWindow.Items.Add
(let item = new MenuItem(Header="_Show in Taskbar",
IsCheckable=true,
IsChecked=window.ShowInTaskbar)
item.Click.Add(fun _ -> window.ShowInTaskbar <- item.IsChecked)
item)|>ignore
// Add "Size to Content" menuitem
miWindow.Items.Add
(let item =
new MenuItem(Header="Size to _Content",
IsCheckable=true,
IsChecked=(window.SizeToContent=SizeToContent.WidthAndHeight))
item.Checked.Add (fun _ -> sizeOnCheck item)
item.Unchecked.Add(fun _ -> sizeOnCheck item)
item)|>ignore
// Add "Resizable" menuitem
miWindow.Items.Add
(let item = new MenuItem(Header="_Resizable",
IsCheckable=true,
IsChecked=(window.ResizeMode=ResizeMode.CanResize))
item.Click.Add (fun _ ->
window.ResizeMode <-
if item.IsChecked
then ResizeMode.CanResize
else ResizeMode.NoResize)
item)|>ignore
// Add "Topmost" menuitem
miWindow.Items.Add
(let item = new MenuItem(Header="_Topmost",
IsCheckable=true,
IsChecked=window.Topmost)
item.Checked.Add (fun _ -> window.Topmost <- item.IsChecked)
item.Unchecked.Add (fun _ -> window.Topmost <- item.IsChecked)
item)|>ignore
miWindow) |> ignore
menu) |> ignore
DockPanel.SetDock(menu, Dock.Top)

// Add TextBlock to DockPanel
dock.Children.Add
(new TextBlock(Text=window.Title,
FontSize=32.0,
TextAlignment = TextAlignment.Center)
) |> ignore


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

CheckTheWindowStyle

#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
//
// From Chapter 14 - CheckTheWindowStyle
//

let mutable itemChecked:MenuItem = null

let dock = new DockPanel()

let window = new Window(Title="Check the Window Style",
SizeToContent = SizeToContent.WidthAndHeight,
Content=dock)

// Create MenuItem objects to change WindowStyle
let itemStyle = new MenuItem(Header="_Style");

// Add window styles menu items
[("_No border or caption", WindowStyle.None);
("_Single-border window",WindowStyle.SingleBorderWindow);
("3_D-border window",WindowStyle.ThreeDBorderWindow);
("_Tool window",WindowStyle.ToolWindow)]
|> Seq.iter (fun (str,style) ->
let item = new MenuItem(Header=str,
Tag=style,
IsChecked= (style=window.WindowStyle))
if item.IsChecked then itemChecked <- item
item.Click.Add( fun _ ->
itemChecked.IsChecked <- false
item.IsChecked <- true
window.WindowStyle <-(item.Tag :?> WindowStyle)
itemChecked <- item)

itemStyle.Items.Add(item) |> ignore)

// Add Style menu to dock panel
let menu = new Menu()
menu.Items.Add(itemStyle)|>ignore
dock.Children.Add(menu))|>ignore
DockPanel.SetDock(menu, Dock.Top)


// Add TextBlock to DockPanel
dock.Children.Add
(new TextBlock(Text=window.Title,
FontSize=32.0,
TextAlignment = TextAlignment.Center)
) |> ignore


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

CheckTheColor

#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
//
// From Chapter 14 - CheckTheColor
//
let dock = new DockPanel()

let window = new Window(Title="Check the Color",
Content=dock)

let text = new TextBlock(Text=window.Title,
TextAlignment=TextAlignment.Center,
FontSize=32.0,
Background=SystemColors.WindowBrush,
Foreground = SystemColors.WindowTextBrush)

let selectedColor (clr:Color) =
let check = clr.A = 255uy
let r = if clr.R = 255uy then 0uy else clr.R
let g = if clr.G = 255uy then 0uy else clr.G
let b = if clr.B = 255uy then 0uy else clr.B

match (r,g,b) with
| (0uy,0uy,_) -> check
| (0uy,_,0uy) -> check
| (_,0uy,0uy) -> check
| _ -> false

let colorProps = seq { for prop in (typeof<Colors>.GetProperties()) do
let clr = prop.GetValue(null,null) :?> Color
if selectedColor(clr) then yield (prop)}

let foregroundMenuItems = new ResizeArray<MenuItem>()
let backgroundMenuItems = new ResizeArray<MenuItem>()

// Create background menu items
colorProps |> Seq.iter (fun prop ->
let clr = prop.GetValue(null,null) :?> Color
let item = new MenuItem(Header = "_" + prop.Name,Tag=clr)
// Implement BackgroundOnClick
item.Click.Add( fun args ->
text.Background <- new SolidColorBrush(item.Tag :?> Color)
)
backgroundMenuItems.Add(item))

// Create foreground menu items
colorProps |> Seq.iter (fun prop ->
let clr = prop.GetValue(null,null) :?> Color
let item = new MenuItem(Header = "_" + prop.Name,Tag=clr)
// Implement ForegroundOnClick
item.Click.Add( fun args ->
text.Foreground <- new SolidColorBrush(item.Tag :?> Color)
)
foregroundMenuItems.Add(item))


let menu = new Menu()
let itemColor = new MenuItem(Header="_Color")
let itemForeground = new MenuItem(Header="_Foreground")
// FillWithColors for foreground
foregroundMenuItems |> ResizeArray.iter (fun x -> itemForeground.Items.Add(x)|>ignore)
// Implement ForegroundOnOpened
itemForeground.SubmenuOpened .Add(fun args ->
foregroundMenuItems |> ResizeArray.iter (fun item ->
item.IsChecked <- ((text.Foreground :?> SolidColorBrush).Color = (item.Tag :?> Color))))

let itemBackground = new MenuItem(Header="_Background")
backgroundMenuItems |> ResizeArray.iter (fun x -> itemBackground.Items.Add(x)|>ignore)

// Implement BackgroundOnOpened
itemBackground.SubmenuOpened .Add(fun args ->
backgroundMenuItems |> ResizeArray.iter (fun item ->
item.IsChecked <- ((text.Background :?> SolidColorBrush).Color = (item.Tag :?> Color))))

itemColor.Items.Add(itemForeground)
itemColor.Items.Add(itemBackground)
menu.Items.Add(itemColor)

// Add menu to doc panel
dock.Children.Add(menu)|>ignore
DockPanel.SetDock(menu, Dock.Top)

// Add TextBlock to DockPanel
dock.Children.Add(text) |>ignore


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

SelectColorFromMenuGrid

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

open System
open System.Windows
open System.Windows.Controls
open System.Windows.Input
open System.Windows.Media
open Chapter13
//
// From Chapter 14 - SelectColorFromMenuGrid
//
let dock = new DockPanel()

let window = new Window(Title="Select Color from Menu Grid",
Content=dock)

let menu = new Menu()
dock.Children.Add(menu) |>ignore
DockPanel.SetDock(menu, Dock.Top)
dock.Children.Add
(new TextBlock(Text=window.Title,
FontSize=32.0,
TextAlignment = TextAlignment.Center)) |> ignore

let itemColor = new MenuItem(Header="_Color")
menu.Items.Add(itemColor)

let itemForeground = new MenuItem(Header="_Foreground")
itemColor.Items.Add(itemForeground)

itemForeground.Items.Add
(let clrbox = new ColorGridBox()
clrbox.SetBinding(ColorGridBox.SelectedValueProperty,"Foreground") |> ignore
clrbox.DataContext <- window
clrbox)


let itemBackground = new MenuItem(Header="_Background")
itemColor.Items.Add(itemBackground)

itemBackground.Items.Add
(let clrbox = new ColorGridBox()
clrbox.SetBinding(ColorGridBox.SelectedValueProperty,"Background") |> ignore
clrbox.DataContext <- window
clrbox)

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

CutCopyAndPaste

#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.Media.Imaging
//
// From Chapter 14 - CutCopyAndPaste
//
let dock = new DockPanel()

let window = new Window(Title="Cut, Copy, and Paste",
Content=dock)

let mainMenu = new Menu()
dock.Children.Add(mainMenu) |>ignore
DockPanel.SetDock(mainMenu, Dock.Top)
let text = new TextBlock(Text = "Sample clipboard text",
HorizontalAlignment = HorizontalAlignment.Center,
VerticalAlignment = VerticalAlignment.Center,
FontSize = 32.0,
TextWrapping = TextWrapping.Wrap)
dock.Children.Add(text) |>ignore

// Experimenting with record types as containers for
// all actionable application menu items
let buildMenuItem (header,iconFile) =
let item = new MenuItem(Header=header)
if header <> "_Edit" then
let fullFilePath = @"file:///icons/" + iconFile
let bmp = new BitmapImage(new Uri(fullFilePath))
let image = new Image()
image.Source <- bmp
item.Icon <- image
item

type AppMenus =
{ Edit : MenuItem;
Cut : MenuItem;
Copy : MenuItem;
Paste : MenuItem;
Delete : MenuItem }

let menus =
{Edit=buildMenuItem ("_Edit","");
Cut=buildMenuItem ("Cu_t","Cut24.gif");
Copy=buildMenuItem ("_Copy","Copy24.gif");
Paste=buildMenuItem ("_Paste","Paste24.gif");
Delete=buildMenuItem ("_Delete","Delete24.gif")}

mainMenu.Items.Add(menus.Edit) |>ignore
menus.Edit.Items.Add(menus.Cut) |>ignore
menus.Edit.Items.Add(menus.Copy) |>ignore
menus.Edit.Items.Add(menus.Paste) |>ignore
menus.Edit.Items.Add(menus.Delete) |>ignore

// Generic functions
let CopyOnClick () =
if text.Text <> null && text.Text.Length > 0 then
Clipboard.SetText(text.Text)

let PasteOnClick () =
if Clipboard.ContainsText() then
text.Text <- Clipboard.GetText()

let DeleteOnClick () = text.Text <- null

let CutOnClick () =
CopyOnClick()
DeleteOnClick()


// Implement EditOnOpened
menus.Edit.SubmenuOpened.Add(fun _ ->
let enabledFlag = (text.Text <> null && text.Text.Length > 0)
menus.Cut.IsEnabled <- enabledFlag
menus.Copy.IsEnabled <- enabledFlag
menus.Delete.IsEnabled <- enabledFlag
menus.Paste.IsEnabled <- Clipboard.ContainsText())

menus.Cut.Click.Add( fun _ -> CutOnClick())
menus.Copy.Click.Add (fun _ -> CopyOnClick())
menus.Delete.Click.Add (fun _ -> DeleteOnClick())
menus.Paste.Click.Add (fun _ -> PasteOnClick())




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

ControlXCV

#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.Media.Imaging
//
// From Chapter 14 - ControlXCV
//
// Create TextBlock first, to be used in action commands
let text = new TextBlock(Text = "Sample clipboard text",
HorizontalAlignment = HorizontalAlignment.Center,
VerticalAlignment = VerticalAlignment.Center,
FontSize = 32.0,
TextWrapping = TextWrapping.Wrap)

// Action commands
let CopyOnClick () =
if text.Text <> null && text.Text.Length > 0 then
Clipboard.SetText(text.Text)

let PasteOnClick () =
if Clipboard.ContainsText() then
text.Text <- Clipboard.GetText()

let DeleteOnClick () = text.Text <- null

let CutOnClick () =
CopyOnClick()
DeleteOnClick()


// Need to create new class to override OnPreviewKeyDown
type ControlXCV() = class
inherit Window() as base

let gestCut = new KeyGesture(Key.X, ModifierKeys.Control)
let gestCopy = new KeyGesture(Key.C, ModifierKeys.Control)
let gestPaste = new KeyGesture(Key.V, ModifierKeys.Control)
let gestDelete = new KeyGesture(Key.Delete)

override this.OnPreviewKeyDown (args:KeyEventArgs) =
base.OnKeyDown(args)
args.Handled <- true

if gestCut.Matches(null,args) then CutOnClick()
else if gestCopy.Matches(null,args) then CopyOnClick()
else if gestPaste.Matches(null,args) then PasteOnClick()
else if gestDelete.Matches(null,args) then DeleteOnClick()
else args.Handled <- false

end

let dock = new DockPanel()

let window = new ControlXCV(Title="Control X, C, and V",
Content=dock)

let menu = new Menu()
dock.Children.Add(menu)
DockPanel.SetDock(menu, Dock.Top)

let itemEdit = new MenuItem(Header="_Edit")
menu.Items.Add(itemEdit) |>ignore

dock.Children.Add(text) |>ignore

// Experimenting with record types as containers for
// all actionable application menu items
let buildMenuItem (header,iconFile,keyGestureText) =
let item = new MenuItem(Header=header)
let fullFilePath = @"file:///icons/" + iconFile
let bmp = new BitmapImage(new Uri(fullFilePath))
let image = new Image()
image.Source <- bmp
item.Icon <- image
item.InputGestureText <- keyGestureText
item

type ActionMenus =
{ Cut : MenuItem;
Copy : MenuItem;
Paste : MenuItem;
Delete : MenuItem }

let actionMenus =
{Cut=buildMenuItem ("Cu_t","Cut24.gif","Control+X");
Copy=buildMenuItem ("_Copy","Copy24.gif","Control+C");
Paste=buildMenuItem ("_Paste","Paste24.gif","Control+V");
Delete=buildMenuItem ("_Delete","Delete24.gif","Delete")}

itemEdit.Items.Add(actionMenus.Cut) |>ignore
itemEdit.Items.Add(actionMenus.Copy) |>ignore
itemEdit.Items.Add(actionMenus.Paste) |>ignore
itemEdit.Items.Add(actionMenus.Delete) |>ignore



// Implement EditOnOpened
itemEdit.SubmenuOpened.Add(fun _ ->
let enabledFlag = (text.Text <> null && text.Text.Length > 0)
actionMenus.Cut.IsEnabled <- enabledFlag
actionMenus.Copy.IsEnabled <- enabledFlag
actionMenus.Delete.IsEnabled <- enabledFlag
actionMenus.Paste.IsEnabled <- Clipboard.ContainsText())

actionMenus.Cut.Click.Add( fun _ -> CutOnClick())
actionMenus.Copy.Click.Add (fun _ -> CopyOnClick())
actionMenus.Delete.Click.Add (fun _ -> DeleteOnClick())
actionMenus.Paste.Click.Add (fun _ -> PasteOnClick())


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

CommandTheMenu

#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
//
// From Chapter 14 - CommandTheMenu
//
// Use record type to track all application menu itesm
type AppMenu =
{ : Menu;
Edit : MenuItem;
Cut : MenuItem;
Copy : MenuItem;
Paste : MenuItem;
Delete : MenuItem }

member v.buildMenuTree (dock:DockPanel) =
dock.Children.Add(v.Main) |>ignore
DockPanel.SetDock(v.Main,Dock.Top)
v.Main.Items.Add(v.Edit) |>ignore
v.Edit.Items.Add(v.Cut) |>ignore
v.Edit.Items.Add(v.Copy) |>ignore
v.Edit.Items.Add(v.Paste) |>ignore
v.Edit.Items.Add(v.Delete)|>ignore

static member buildMenu header command =
new MenuItem(Header=header,Command=command)


// Create TextBlock first, to be used in action commands
let text = new TextBlock(Text = "Sample clipboard text",
HorizontalAlignment = HorizontalAlignment.Center,
VerticalAlignment = VerticalAlignment.Center,
FontSize = 32.0,
TextWrapping = TextWrapping.Wrap)

let dock = new DockPanel()

let window = new Window(Title="Command the Menu",
Content=dock)
let menus =
{Main = new Menu();
Edit = new MenuItem(Header="_Edit");
Cut = AppMenu.buildMenu "Cu_t" ApplicationCommands.Cut;
Copy = AppMenu.buildMenu "_Copy" ApplicationCommands.Copy;
Paste = AppMenu.buildMenu "_Paste" ApplicationCommands.Paste;
Delete = AppMenu.buildMenu "_Delete" ApplicationCommands.Delete; }

menus.buildMenuTree (dock)
dock.Children.Add(text) |>ignore

// Define applications commands
let CutCanExecute (args:CanExecuteRoutedEventArgs) =
args.CanExecute <- text.Text <> null && text.Text.Length > 0

let PasteCanExecute (args:CanExecuteRoutedEventArgs) =
args.CanExecute <- Clipboard.ContainsText()

// Bind commands

window.CommandBindings.Add
(new CommandBinding(ApplicationCommands.Cut,
(fun _ _->
ApplicationCommands.Copy.Execute(null, window)
ApplicationCommands.Delete.Execute(null, window)),
(fun _ args -> CutCanExecute(args))))

window.CommandBindings.Add
(new CommandBinding(ApplicationCommands.Copy,
(fun _ _->
Clipboard.SetText(text.Text)),
(fun _ args -> CutCanExecute(args))))


window.CommandBindings.Add
(new CommandBinding(ApplicationCommands.Paste,
(fun _ _->
text.Text <- Clipboard.GetText()),
(fun _ args -> PasteCanExecute(args))))

window.CommandBindings.Add
(new CommandBinding(ApplicationCommands.Delete,
(fun _ _->
text.Text <- null),
(fun _ args -> CutCanExecute(args))))


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

PopupContextMenu

#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.Documents
open System.Windows.Input
open System.Windows.Media
open Microsoft.FSharp.Compatibility
//
// From Chapter 14 - PopupContextMenu
//
let window = new Window(Title="Popup Context Menu")

let text= new TextBlock(FontSize=32.0,
HorizontalAlignment = HorizontalAlignment.Center,
VerticalAlignment = VerticalAlignment.Center)
window.Content <- text

let quote = "To be, or not to be, that is the question"
quote |> String.split [' '] |> Seq.iter (fun word ->
let menu = new ContextMenu()
let run = new Run(word)
run.TextDecorations <- new TextDecorationCollection()
run.ContextMenu <- menu
text.Inlines.Add(run)
text.Inlines.Add(" ")

menu.Items.Add
(let item = new MenuItem(Header="Bold")
item.Click.Add (fun args ->
if run.FontWeight = FontWeights.Bold then
item.IsChecked <- false
run.FontWeight <- FontWeights.Normal
else
item.IsChecked <- true
run.FontWeight <- FontWeights.Bold
)
item) |>ignore

menu.Items.Add
(let item = new MenuItem(Header="Italic")
item.Click.Add (fun args ->
if run.FontStyle = FontStyles.Italic then
item.IsChecked <- false
run.FontStyle <- FontStyles.Normal
else
item.IsChecked <- true
run.FontStyle <- FontStyles.Italic)
item) |>ignore

let decorateText (marker:Run) loc =
let decor = new TextDecoration()
decor.Location <- loc
let item = new MenuItem(Header=loc.ToString(),Tag=decor)
item.Click.Add(fun args ->
if item.IsChecked then
marker.TextDecorations.Remove(item.Tag :?> TextDecoration) |> ignore
item.IsChecked <- false
else
marker.TextDecorations.Add(item.Tag :?> TextDecoration)
item.IsChecked <- true)
menu.Items.Add(item) |>ignore

// Add TextDecorationLocation members to Context Menu
let decorations = Enum.GetValues(typeof<TextDecorationLocation>)
:?> (TextDecorationLocation [])
let decorate = decorateText run
Seq.iter decorate decorations)



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

Saturday, January 19, 2008

Learning WPF with F# - ListBox Selection

I think I'm getting a better handle of F# programming language. For the sample codes in Chapter 13, I've decided to assume a Perl hacker mentality, which is try to do the same thing in different ways. One example is shown in the SelectColorGrid example. At the start of this series of blogs on F# and WPF, I would have written the following chunk of F# code for adding FrameworkElementFactory based on Rectangle to the FrameworkElementFactory based on StackPanel:

let factoryStack = new FrameworkElementFactory(typeof<StackPanel>)

let factoryRectangle = new FrameworkElementFactory(typeof<Rectangle>)
factoryRectangle.SetValue(Rectangle.WidthProperty,16.0)
factoryRectangle.SetValue(Rectangle.HeightProperty,16.0)
factoryRectangle.SetValue(Rectangle.MarginProperty,new Thickness(2.0)
factoryRectangle.SetValue(Rectangle.StrokeProperty,SystemColors.WindowTextBrush)
factoryRectangle.SetBinding(Rectangle.FillProperty,new Binding("Brush"))

factoryStack.AppendChild(factoryRectangle)

With some experimentation, I would can rewrite the above chunk of code as:

let factoryStack = new FrameworkElementFactory(typeof<StackPanel>)

factoryStack.AppendChild
  (let factory = new FrameworkElementFactory(typeof<Rectangle>)
  
   let setval = factory.SetValue
   let setbind = factory.SetBinding
   
   (Rectangle.WidthProperty,16.0)                          |> setval
   (Rectangle.HeightProperty,16.0)                         |> setval
   (Rectangle.MarginProperty,new Thickness(2.0))           |> setval
   (Rectangle.StrokeProperty,SystemColors.WindowTextBrush) |> setval
   (Rectangle.FillProperty,new Binding("Brush"))           |> setbind
   factory)

A couple points on the revised version of the example code. Note that I was able to define what I call function aliases via let setval = factory.SetValue. These provide syntatic sugar so I don't have to write verbose code. I can do this only because I can limit the scope within parameter tuple to factoryStack.AppendChild. I don't have to worry about setval being used elsewhere in my code. The other usage is flipping the order of parameter and function name with the forward pipe operator (|>). I'm not sure if this makes the code any more readable, but it sure warps my way of thinking when it comes to writing code and allows me to think about the code differently. The other operator I hope to take more advantage of is the forward composition operator (>>).

Another interesting representation of the code is implemented in the ColorWheel example. I'm able to isolate an entire chunk of functionality without exposing any variable. This ensures that I don't have to worry about change impacts to any other parts of the code. Here is where I wish I had a better understanding of F# compiler works and be able to tell which representation allows for better compiler optimization. When I'm ready for the challenge, I'm going to try to read through Simon Peyton Jone's book The Implementation of Functional Programming Languages which he has made freely available online.

Here are more code examples from working on Chapter 13 of Petzold's book Applications = Code + Markup: A Guide to the Microsoft Windows Presentation Foundation.


ListColorNames

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

open System
open System.Reflection
open System.Windows
open System.Windows.Controls
open System.Windows.Input
open System.Windows.Media
//
// From Chapter 13 - ListColorNames
//
let lstbox = new ListBox(Width=150.0,Height=150.0)

// Fill ListBox with Color names
let props = (typeof<Colors>).GetProperties()
props |> Seq.iter (fun p -> lstbox.Items.Add(p.Name)|>ignore)

let window = new Window(Title="List Color Names",Content=lstbox)

// ListBoxOnSelectionChanged
lstbox.SelectionChanged.Add( fun _ ->
let str = lstbox.SelectedItem :?> string
if str <> null then
let clr = (typeof<Colors>).GetProperty(str).GetValue(null,null) :?> Color
window.Background <- new SolidColorBrush(clr))

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

ListColorValues

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

open System
open System.Reflection
open System.Windows
open System.Windows.Controls
open System.Windows.Input
open System.Windows.Media
//
// From Chapter 13 - ListColorValues
//
let lstbox = new ListBox(Width=150.0,Height=150.0)

// Fill ListBox with Color values
let props = (typeof<Colors>).GetProperties()
props |> Seq.iter (fun p -> lstbox.Items.Add(p.GetValue(null,null))|>ignore)

let window = new Window(Title="List Color Values",Content=lstbox)

// ListBoxOnSelectionChanged
lstbox.SelectionChanged.Add( fun _ ->
if lstbox.SelectedIndex <> -1 then
let clr = lstbox.SelectedItem :?> Color
window.Background <- new SolidColorBrush(clr))

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

NamedColor & ListNamedColors

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

open System
open System.Reflection
open System.Windows
open System.Windows.Controls
open System.Windows.Input
open System.Windows.Media
//
// From Chapter 13 - NamedColor
//
type NamedColor =
{clr : Color; clrname : string}

// Using v instead of this...
override v.ToString() = v.clrname
// normally I would write the above line of code as
// override this.ToString() = this.clrname. There's nothing
// special with "this"

member v.Name
with get() =
let retval = v.clrname |> String.map_concat (fun c ->
if Char.IsUpper(c)
then " " + String.of_char(c)
else String.of_char(c))
retval.Trim()

member v.Color
with get() = v.clr

static member All =
let props = (typeof<Colors>).GetProperties()
props |> Seq.map (fun prop -> {clrname=prop.Name; clr=(prop.GetValue(null,null):?>Color)})
//
// From Chapter 13 - ListNamedColors
//
let lstbox = new ListBox(ItemsSource=NamedColor.All,
DisplayMemberPath="Name",
SelectedValuePath="Color",
Width=150.0,
Height=150.0)

let window = new Window(Title="List Named Colors",Content=lstbox)

// ListBoxOnSelectionChanged
lstbox.SelectionChanged.Add( fun _ ->
if lstbox.SelectedValue <> null then
let clr = lstbox.SelectedValue :?> Color
window.Background <- new SolidColorBrush(clr))

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

NamedBrush & ListNamedBrushes

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

open System
open System.Reflection
open System.Windows
open System.Windows.Controls
open System.Windows.Input
open System.Windows.Media
//
// From Chapter 13 - NamedBrush
//
type NamedBrush =
{brush : Brush; name : string}

override v.ToString() = v.name

member v.Name
with get() =
let retval = v.name |> String.map_concat (fun c ->
if Char.IsUpper(c)
then " " + String.of_char(c)
else String.of_char(c))
retval.Trim()

member v.Brush
with get() = v.brush

static member All =
let props = (typeof<Brushes>).GetProperties()
props |> Seq.map (fun prop -> {name=prop.Name; brush=(prop.GetValue(null,null):?>Brush)})
//
// From Chapter 13 - ListNamedBrushes
//
let lstbox = new ListBox(ItemsSource=NamedBrush.All,
DisplayMemberPath="Name",
SelectedValuePath="Brush",
Width=150.0,
Height=150.0)

let window = new Window(Title="List Named Brushes",Content=lstbox)

// Bind the SelectedValue to window Background
lstbox.SetBinding(ListBox.SelectedValueProperty,"Background")
lstbox.DataContext <- window

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

ListColorShapes

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

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

//
// From Chapter 13 - ListColorShapes
//
let lstbox = new ListBox(Width=150.0,
Height=150.0)

// Fill ListBox with Ellipse objects
(typeof<Brushes>).GetProperties()
|> Seq.iter (fun prop ->
let brush = prop.GetValue(null,null) :?> Brush
let ellip = new Ellipse(Width=100.0,
Height = 25.0,
Margin = new Thickness(10.0,5.0,0.0,5.0),
Fill = brush)
lstbox.Items.Add(ellip)|>ignore)

let window = new Window(Title="List Color Shapes",Content=lstbox)

lstbox.SelectionChanged.Add( fun _ ->
if lstbox.SelectedIndex <> -1 then
window.Background <- (lstbox.SelectedItem :?> Shape).Fill)


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

ListColorLabels

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

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

//
// From Chapter 13 - ListColorLabels
//
let lstbox = new ListBox(Width=150.0,
Height=150.0)

// Fill ListBox with label controls
(typeof<Colors>).GetProperties()
|> Seq.iter (fun prop ->
let clr = prop.GetValue(null,null) :?> Color
let b2f x = Float.of_int (Byte.to_int x)
let isBlack = 0.222*(b2f clr.R) + 0.707 * (b2f clr.G) + 0.071 * (b2f clr.B) > 128.0
let lbl = new Label(Content=prop.Name,
Background = new SolidColorBrush(clr),
Foreground = (if isBlack then Brushes.Black else Brushes.White),
Width = 100.0,
Margin = new Thickness(15.0,0.0,0.0,0.0),
Tag = clr)
lstbox.Items.Add(lbl) |> ignore)


let window = new Window(Title="List Color Labels",Content=lstbox)

lstbox.SelectionChanged.Add( fun _ ->
match lstbox.SelectedItem with
| :? Label as lbl ->
let clr = lbl.Tag :?> Color
window.Background <- new SolidColorBrush(clr)
| _ -> ())



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

ListWithListBoxItems

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

open System
open System.Reflection
open System.Windows
open System.Windows.Controls
open System.Windows.Input
open System.Windows.Media

//
// From Chapter 13 - ListWithListBoxItems
//
let lstbox = new ListBox(Width=150.0,
Height=150.0)

// Fill ListBox with label controls
(typeof<Colors>).GetProperties()
|> Seq.iter (fun prop ->
let clr = prop.GetValue(null,null) :?> Color

let isBlack =
let b2f x = Float.of_int (Byte.to_int x)
0.222*(b2f clr.R) + 0.707 * (b2f clr.G) + 0.071 * (b2f clr.B) > 128.0

let item = new ListBoxItem(Content=prop.Name,
Background = new SolidColorBrush(clr),
Foreground = (if isBlack then Brushes.Black else Brushes.White),
HorizontalContentAlignment = HorizontalAlignment.Center,
Padding = new Thickness(2.0))
lstbox.Items.Add(item) |> ignore)


let window = new Window(Title="List with ListBoxItem",Content=lstbox)

lstbox.SelectionChanged.Add( fun args ->
if args.RemovedItems.Count > 0 then
let item = args.RemovedItems.[0] :?> ListBoxItem
let str = item.Content :?> string
item.Content <- String.sub str 2 (str.Length-4)
item.FontWeight <- FontWeights.Regular

if args.AddedItems.Count > 0 then
let item = args.AddedItems.[0] :?> ListBoxItem
let str = item.Content :?> string
item.Content <- "[ " + str + " ]"
item.FontWeight <- FontWeights.Bold


match lstbox.SelectedItem with
| :? ListBoxItem as item ->
window.Background <- item.Background
| _ -> ())



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

ColorListBoxItem, ColorListBox & ListColorsElegantly

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

open System
open System.Reflection
open System.Windows
open System.Windows.Controls
open System.Windows.Input
open System.Windows.Media
open System.Windows.Shapes
//
// From Chapter 13 - ColorListBoxItem, ColorListBox & ListColorsElegantly
//
//
// From Chapter 13 - ColorListBoxItem
//
type ColorListBoxItem() = class
inherit ListBoxItem() as base

let mutable str = ""
let stack = new StackPanel(Orientation=Orientation.Horizontal)
let text = new TextBlock(VerticalAlignment = VerticalAlignment.Center)

let rect = new Rectangle(Width=16.0,
Height=16.0,
Margin=new Thickness(2.0),
Stroke = SystemColors.WindowTextBrush)
do
base.Content <- stack
stack.Children.Add(rect) |> ignore
stack.Children.Add(text) |> ignore

member this.Text
with get() = str
and set value =
str <- value
let strSpaced = value |> String.map_concat (fun c ->
if Char.IsUpper(c)
then " " + String.of_char(c)
else String.of_char(c))
text.Text <- strSpaced

member this.Color
with get() =
let brush = rect.Fill :?> SolidColorBrush
if brush = null then Colors.Transparent else brush.Color
and set value =
rect.Fill <- new SolidColorBrush(value)

override this.OnSelected (args:RoutedEventArgs) =
base.OnSelected(args)
text.FontWeight <- FontWeights.Bold

override this.OnUnselected (args:RoutedEventArgs) =
base.OnUnselected(args)
text.FontWeight <- FontWeights.Regular

override this.ToString() = str
end
//
// From Chapter 13 - ColorListBox
//
type ColorListBox() = class
inherit ListBox() as base

do
let this = base
typeof<Colors>.GetProperties()
|> Seq.iter (fun prop ->
let item = new ColorListBoxItem(Text=prop.Name,
Color=(prop.GetValue(null,null):?> Color))
this.Items.Add(item) |> ignore
())
this.SelectedValuePath <- "Color"

member this.SelectedColor
with get() = this.SelectedValue :?> Color
and set (value:Color) = this.SelectedValue <- value

end
//
// From Chapter 13 - ListColorsElegantly
//

let lstbox = new ColorListBox(SelectedColor = SystemColors.WindowColor,
Width=150.0,
Height=150.0)


let window = new Window(Title="List Colors Elegantly",Content=lstbox)

lstbox.SelectionChanged.Add( fun _ ->
window.Background <- new SolidColorBrush(lstbox.SelectedColor))


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

NamedBrush & ListColorsEvenMoreElegantlier

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

open System
open System.Reflection
open System.Windows
open System.Windows.Data
open System.Windows.Controls
open System.Windows.Input
open System.Windows.Media
open System.Windows.Shapes
//
// From Chapter 13 - NamedBrush
//
type NamedBrush =
{brush : Brush; name : string}

override v.ToString() = v.name

member v.Name
with get() =
let retval = v.name |> String.map_concat (fun c ->
if Char.IsUpper(c)
then " " + String.of_char(c)
else String.of_char(c))
retval.Trim()

member v.Brush
with get() = v.brush

static member All =
(typeof<Brushes>).GetProperties()
|> Seq.map (fun prop -> {name=prop.Name; brush=(prop.GetValue(null,null):?>Brush)})

//
// From Chapter 13 - ListColorsEvenMoreElegantlier
//

// Create a data template for the items and populate it.
let template =
let brushTemplate = new DataTemplate(typeof<NamedBrush>)

// Create a FrameworkElementFactory based on StackPanel
let factoryStack = new FrameworkElementFactory(typeof<StackPanel>)
factoryStack.SetValue(StackPanel.OrientationProperty,Orientation.Horizontal)

brushTemplate.VisualTree <- factoryStack

// Create a FrameworkElementFactory based on Rectangle and
// add it to the stack panel. Note, with this construct, I don't
// pollute the rest of the code with the factory definition
factoryStack.AppendChild
(let factory = new FrameworkElementFactory(typeof<Rectangle>)
// function aliases (syntactic sugar) so I don't have to type as much
let setval = factory.SetValue
let setbind = factory.SetBinding

// I flipped the parameters with function name via the forward (|>) operator
// Not sure is this is a more readable format or not, but it sure is a
// departure from normal C# forms.
(Rectangle.WidthProperty,16.0) |> setval
(Rectangle.HeightProperty,16.0) |> setval
(Rectangle.MarginProperty,new Thickness(2.0)) |> setval
(Rectangle.StrokeProperty,SystemColors.WindowTextBrush) |> setval
(Rectangle.FillProperty,new Binding("Brush")) |> setbind
factory)

// Create a FrameworkElementFactory based on TextBlock and add it to stack panel
(let factory = new FrameworkElementFactory(typeof<TextBlock>)
(TextBlock.VerticalAlignmentProperty,VerticalAlignment.Center) |> factory.SetValue
(TextBlock.TextProperty,new Binding("Name")) |> factory.SetBinding
factory) |> factoryStack.AppendChild

// return the data template
brushTemplate

// variables factoryRectangle, factoryStack & factoryTextBlock no longer pollute
// the rest of the code space

let lstbox = new ListBox(Width=150.0,
Height=150.0,
ItemTemplate=template,
ItemsSource=NamedBrush.All,
SelectedValuePath="Brush")


let window = new Window(Title="List Colors Even Elegantlier",Content=lstbox)
lstbox.SetBinding(ListBox.SelectedValueProperty,"Background")
lstbox.DataContext <- window

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

ColorGridBox & SelectColorFromGrid

#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.Data
open System.Windows.Controls
open System.Windows.Controls.Primitives
open System.Windows.Input
open System.Windows.Media
open System.Windows.Shapes
//
// From Chapter 13 - ColorGridBox
//
type ColorGridBox() as this = class
inherit ListBox() as base

// list of colors to be displayed
let strColors =
["Black"; "Brown"; "DarkGreen"; "MidnightBlue";
"Navy"; "DarkBlue"; "Indigo"; "DimGray";
"DarkRed"; "OrangeRed"; "Olive"; "Green";
"Teal"; "Blue"; "SlateGray"; "Gray";
"Red"; ""; "YellowGreen"; "SeaGreen";
"Aqua"; "LightBlue"; "Violet"; "DarkGray";
"Pink"; "Gold"; "Yellow"; "Lime";
"Turquoise"; "SkyBlue"; ""; "LightGray";
"LightPink"; "Tan"; "LightYellow"; "LightGreen";
"LightCyan"; "LightSkyBlue"; "Lavender"; "White"]

do
let factory = new FrameworkElementFactory(typeof<UniformGrid>)
(UniformGrid.ColumnsProperty,8) |> factory.SetValue

this.ItemsPanel <- new ItemsPanelTemplate(factory)

// Add items to the ListBox
strColors |> Seq.iter (fun color ->
(new Rectangle(Width=12.0,
Height=12.0,
Fill=(typeof<Brushes>.GetProperty(color).GetValue(null,null):?>Brush),
ToolTip = new ToolTip(Content=color),
Margin=new Thickness(4.0))
|> this.Items.Add |> ignore))

this.SelectedValuePath <- "Fill"
end
//
// From Chapter 13 - SelectColorFromGrid
//

let stack = new StackPanel()

let window = new Window(Title="Select Color from Grid",
SizeToContent = SizeToContent.WidthAndHeight,
Content=stack)

let createDoNothingButton () =
new Button(Content = "Do-nothing button\nto test tabbing",
Margin = new Thickness(24.0),
HorizontalAlignment = HorizontalAlignment.Center,
VerticalAlignment = VerticalAlignment.Center)

// Create do-nothing button to test tabbing and add to StackPanel
createDoNothingButton () |> stack.Children.Add |> ignore

// Create ColorGridBox control and add to StackPanel
(let clrgrid = new ColorGridBox(Margin = new Thickness(24.0),
DataContext=window,
HorizontalAlignment = HorizontalAlignment.Center,
VerticalAlignment = VerticalAlignment.Center)
// Bind background
(ColorGridBox.SelectedValueProperty,"Background")
|>clrgrid.SetBinding |> ignore
clrgrid) |> stack.Children.Add |> ignore

// Create another do-nothing button to test tabbing and add to StackPanel
createDoNothingButton () |> stack.Children.Add |> ignore


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

ColorWheel & SelectColorFromWheel

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

open System
open System.Windows
open System.Windows.Data
open System.Windows.Controls
open System.Windows.Controls.Primitives
open System.Windows.Input
open System.Windows.Media
open System.Windows.Shapes
open Chapter12

//
// From Chapter 13 - ColorWheel
//
type ColorWheel() as this = class
inherit ListBox() as base

do
this.ItemsPanel <- new ItemsPanelTemplate
(new FrameworkElementFactory(typeof<RadialPanel>))

// Create DataTemplate for the items and create a FrameworkElementFactory
// based on Rectangle and use that factory for visual tree.
// Again, I'm just playing around with the expressiveness of F# and not
// necessarily recommending coding in this format.
// Look ma! No let bindings....
new DataTemplate(typeof<Brush>) |> (fun template ->
new FrameworkElementFactory(typeof<Rectangle>) |> (fun f ->
(Rectangle.WidthProperty, 4.0) |> f.SetValue
(Rectangle.HeightProperty, 12.0) |> f.SetValue
(Rectangle.MarginProperty, new Thickness(1.0, 8.0, 1.0, 8.0)) |> f.SetValue
(Rectangle.FillProperty, new Binding("")) |> f.SetBinding
template.VisualTree <- f)
this.ItemTemplate <- template)

// Set the items in the ListBox
(typeof<Brushes>).GetProperties() |> Seq.iter (fun prop ->
(prop.GetValue(null,null) :?> Brush) |> this.Items.Add |> ignore)
end
//
// From Chapter 13 - SelectColorFromWheel
//


let createDoNothingButton () =
new Button(Content = "Do-nothing button\nto test tabbing",
Margin = new Thickness(24.0),
HorizontalAlignment = HorizontalAlignment.Center,
VerticalAlignment = VerticalAlignment.Center)

let stack = new StackPanel(Orientation=Orientation.Horizontal)

// Add a do-nothing button to test tabbing
createDoNothingButton() |> stack.Children.Add |> ignore

// Create ColorWheel Control
let clrwheel = new ColorWheel(Margin = new Thickness(24.0),
HorizontalAlignment = HorizontalAlignment.Center,
VerticalAlignment = VerticalAlignment.Center)
clrwheel |> stack.Children.Add |> ignore

// Add another do-nothing button to test tabbing
createDoNothingButton() |> stack.Children.Add |> ignore


let window = new Window(Title="Select Color from Wheel",
SizeToContent = SizeToContent.WidthAndHeight,
Content=stack)

// Bind Background of window to selected value of ColorWheel
(ColorWheel.SelectedValueProperty, "Background") |> clrwheel.SetBinding
clrwheel.DataContext <- window

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