Chapter 8. User Interfaces

In this chapter, you will learn about one of the most common tasks a programmer needs to perform: the art of putting pixels on the screen. In F#, this is all about the libraries and APIs that you call, and you have a lot of choices in this area, with more emerging as the .NET platform involves. The first choice you need to make is whether you want to build a desktop application; an application that runs locally and uses a series of windows and controls to display information to the user; or a web application, where you define the application's interface in HTML, which is then rendered by a browser.

You have four GUI library choices when creating desktop applications in .NET: WinForms, Windows Presentation Foundation (WFP), GTK#, and DirectX. In this chapter, you'll learn about WinForms, WFP and GTK#, but not DirectX. WinForms, WFP, and GTK# have the same basic metaphors of windows and controls. WinForms is the oldest and simplest, and you've already met a few WinForms examples. WFP is a new library; it's slightly more complex than WinForms, but it's also more consistent and offers more features, including impressive 3D graphics. GTK# offers much better platform support than the other two libraries. WinForms now runs on all platforms, thanks to the Mono implementation, and GTK# is recommended by the Mono team for non-Windows platforms. DirectX is mainly targeted at game producers who want fast 3D graphics. WFP offers a simpler way to produce 3D graphics, so this book won't cover DirectX.

To create a web application, you can use the ASP.NET framework, which gives you a simple way to create server-based dynamic HTML applications. ASP.NET provides a flexible way for you to generate HTML in response to a HTTP request from a browser. Web applications have evolved greatly in recent years; not surprisingly, the ASP.NET platform has evolved greatly alongside it. ASP.NET has added ASP.NET AJAX, ASP.NET MVC, and Silverlight, and a rival platform called Mono Rail has also emerged. However, this book doesn't cover these things because the aim is to show off the basics of generating HTML using F#.

Whole books have been written on each of the topics you'll learn about in this chapter, so be aware that this chapter focuses mainly on the basics. Whichever technology you choose to create your UIs, you're probably going to have to invest some time learning how the library works before you can create great UIs.

Introducing WinForms

The WinForms classes are contained in the System.Drawing.dll and System.Forms.Windows.dll, and you will need to add references to these to compile all of the WinForms examples. The libraries are based on the System.Windows.Forms.Form class, which represents a window that you show to the user. You essentially create a new window when you create an instance of this class. You must then create an event loop, a way of ensuring that you respond to user interactions with the window. You do this by calling the System.Windows.Forms.Application.Run method and passing it the form object you created. You can control the look of the form by setting its properties and calling its methods. The following example demonstrates how to do this:

open System.Drawing
open System.Windows.Forms

// create a new form
let form = new Form(BackColor = Color.Purple, Text = "Introducing WinForms")

// show the form
Application.Run(form)

This example will not work with F# interactive, fsi, because you cannot start an event loop from within fsi. To work with forms in fsi, you call the form's Show method or set the form's Visible property to true. This example shows the second technique:

> open System.Drawing
open System.Windows.Forms

let form = new Form(BackColor=Color.Purple,
                    Text="Introducing WinForms",
                    Visible=true);;

Either way, you can dynamically interact with your form object:

> form.Text <- "Dynamic !!!";;

When working with WinForms, you can take one of two approaches: draw the forms yourself or use controls to build them. You'll begin by drawing your own forms, and then move on to using controls.

Drawing WinForms

Drawing your own forms means that you take responsibility for the pixels that appear on the screen. This low-level approach might appeal to those F# users who believe that many controls that come with the WinForms library are not perfectly suited to displaying data structures or the results of functions and algorithms. However, be warned that this approach can be time-consuming, and your time is usually better spent looking for a graphics library that abstracts away some of the presentation logic.

To draw a WinForm, you attach an event handler to the Paint event of the form or the control. This means that your function is called every time Windows requests the form to be drawn. The event argument that is passed into this function has a property called Graphics, which contains an instance of a class also called Graphics. This class has methods (such as DrawLine) that allow you to draw pixels on the form. The following example shows a simple form where you draw a pie on it:

open System.Drawing
open System.Windows.Forms

let form =
    // create a new form setting the minimum size
    let temp = new Form(MinimumSize = new Size(96, 96))
// repaint the form when it is resize
    temp.Resize.Add (fun _ -> temp.Invalidate())

    // a brush to provide the shapes color
    let brush = new SolidBrush(Color.Red)
    temp.Paint.Add (fun e ->
        // calculate the width and height of the shape
        let width, height = temp.Width - 64, temp.Height - 64
        // draw the required shape
        e.Graphics.FillPie (brush, 32, 32, width, height, 0, 290))

    // return the form to the top level
    temp

Application.Run(form)

You can see the form that results from executing this code in Figure 8-1.

A WinForm containing a pie shape

Figure 8.1. A WinForm containing a pie shape

This image is linked to the size of the form, so you must tell the form to redraw itself whenever the form is resized. You do this by attaching an event-handling function to the Resize event. In this function, you call the form's Invalidate method, which tells the form that it needs to redraw itself.

Now let's look at a more complete WinForms example. Imagine you want to create a form to display the Tree type defined in the next code example (see Figure 8-2):

// The tree type
type 'a Tree =
| Node of 'a Tree * 'a Tree
| Leaf of 'a

// The definition of the tree
let tree =
    Node(
        Node(
            Leaf "one",
            Node(Leaf "two", Leaf "three")),
        Node(
            Node(Leaf "four", Leaf "five"),
            Leaf "six"))
A WinForm showing a tree structure

Figure 8.2. A WinForm showing a tree structure

You can draw this tree with the code shown in Listing 8-1.

Example 8.1. Drawing a Tree

open System
open System.Drawing
open System.Windows.Forms

// The tree type
type 'a Tree =
| Node of 'a Tree * 'a Tree
| Leaf of 'a
// The definition of the tee
let tree =
    Node(
        Node(
            Leaf "one",
            Node(Leaf "two", Leaf "three")),
        Node(
            Node(Leaf "four", Leaf "five"),
            Leaf "six"))

// A function for finding the maximum depth of a tree
let getDepth t =
    let rec getDepthInner t d =
        match t with
        | Node (l, r) ->
            max
                (getDepthInner l d + 1.0F)
                (getDepthInner r d + 1.0F)
        | Leaf x -> d
    getDepthInner t 0.0F

// Constants required for drawing the form
let brush = new SolidBrush(Color.Black)
let pen = new Pen(Color.Black)
let font = new Font(FontFamily.GenericSerif, 8.0F)

// a useful function for calculating the maximum number
// of nodes at any given depth
let raise2ToPower (x : float32) =
    Convert.ToSingle(Math.Pow(2.0, Convert.ToDouble(x)))

let drawTree (g : Graphics) t =
    // constants that relate to the size and position
    // of the tree
    let center = g.ClipBounds.Width / 2.0F
    let maxWidth = 32.0F * raise2ToPower (getDepth t)
    // function for drawing a leaf node
    let drawLeaf (x : float32) (y : float32) v =
        let value = sprintf "%A" v
        let l = g.MeasureString(value, font)
        g.DrawString(value, font, brush, x - (l.Width / 2.0F), y)
// draw a connector between the nodes when necessary
    let connectNodes (x : float32) y p =
        match p with
        | Some(px, py) -> g.DrawLine(pen, px, py, x, y)
        | None -> ()
    // the main function to walk the tree structure drawing the
    // nodes as we go
    let rec drawTreeInner t d w p =
        let x = center - (maxWidth * w)
        let y = d * 32.0F
        connectNodes x y p
        match t with
        | Node (l, r) ->
            g.FillPie(brush, x - 3.0F, y - 3.0F, 7.0F, 7.0F, 0.0F, 360.0F)
            let d = (d + 1.0F)
            drawTreeInner l d (w + (1.0F / d)) (Some(x, y))
            drawTreeInner r d (w - (1.0F / d)) (Some(x, y))
        | Leaf v -> drawLeaf x y v
    drawTreeInner t 0.0F 0.0F None

// create the form object
let form =
    let temp = new Form(WindowState = FormWindowState.Maximized)
    temp.Resize.Add(fun _ -> temp.Invalidate())
    temp.Paint.Add
        (fun e ->
            e.Graphics.Clip <-
                new Region(new Rectangle(0, 0, temp.Width, temp.Height))
            drawTree e.Graphics tree)
    temp

Application.Run(form)

Note how you define a function, drawTree. This function has two parameters, the Graphics object and the tree you want to draw:

let drawTree (g : Graphics) t =

This is a common pattern when drawing WinForms. Creating a function that takes the Graphics object and a data type to be drawn allows different forms and controls to reuse the function easily.

To implement drawTree, you first calculate a couple of constants the function will use: center and maxWidth. Functions outside drawTree can't see these constants, so you can use them in drawTree's inner functions without having to pass them around as parameters:

// constants that relate to the size and position
// of the tree
let center = g.ClipBounds.Width / 2.0F
let maxWidth = 32.0F * raise2ToPower (getDepth t)

You implement the rest of the function by breaking it down into inner functions. You define drawLeaf to take care of drawing leaf nodes:

// function for drawing a leaf node
let drawLeaf (x : float32) (y : float32) v =
    let value = sprintf "%A" v
    let l = g.MeasureString(value, font)
    g.DrawString(value, font, brush, x - (l.Width / 2.0F), y)

You use connectNodes to take care of drawing the connections between nodes, where appropriate:

// draw a connector between the nodes when necessary
let connectNodes (x : float32) y p =
    match p with
    | Some(px, py) -> g.DrawLine(pen, px, py, x, y)
    | None -> ()

Finally, you define drawTreeInner as a recursive function that does the real work of walking the Tree type and drawing it:

// the main function to walk the tree structure drawing the
// nodes as we go
let rec drawTreeInner t d w p =
    let x = center - (maxWidth * w)
    let y = d * 32.0F
    connectNodes x y p
    match t with
    | Node (l, r) ->
        g.FillPie(brush, x - 3.0F, y - 3.0F, 7.0F, 7.0F, 0.0F, 360.0F)
        let d = (d + 1.0F)
        drawTreeInner l d (w + (1.0F / d)) (Some(x, y))
        drawTreeInner r d (w - (1.0F / d)) (Some(x, y))
    | Leaf v -> drawLeaf x y v

This function uses parameters to store values between recursive calls. Because this is an inner function, you know that the outside world cannot misuse it by initializing it incorrectly; this is because the outside world cannot see it. Hiding parameters to store working values between recursive function calls is another common pattern in functional programming.

In some ways, this tree-drawing function is satisfactory: it gives a nice hierarchical overview of the tree in a fairly concise 86 lines of F# code. However, this approach scales only so far. As you draw more complicated images, the number of lines of code can grow rapidly, and working out all the geometry can become time-consuming. To help manage this complexity, F# lets you use controls, as you'll learn in the next section.

To make the most of drawing on WinForms, you should get to know the System.Drawing namespace contained in System.Drawing.dll. You should also concentrate on two areas. First, you need to learn how to use the Graphics object, particularly its overloads of methods prefixed with either Draw or Fill. Table 8-1 provides a helpful summary of the methods on the Graphics object.

Table 8.1. Important Methods on the System.Drawing.Graphics Object

Method Name

Description

DrawArc

Draws a portion of an ellipse

DrawBezier

Draws a Bézier spline, which is a curve represented by two endpoints and two free-floating points that controll the angle of the curve

DrawCurve

Draws a curved line defined by an array of points

DrawClosedCurve

Draws a closed curved line defined by an array of points

DrawEllipse

Draws the outline of an ellipse, which you represent with a rectangle or rectangular set of points

DrawPie

Draws a portion of the outline of an ellipse, which you represent with a rectangle and two radial lines that illustrate the start and finish angles

DrawLine

Draws a single line from two points

DrawLines

Draws a set of lines from an array of points

DrawPolygon

Draws the outline of a polygon, which is a closed set of lines from an array of points

DrawRectangle

Draws the outline of a rectangle, which you represent with a coordinate, as well as its width and height

DrawRectangles

Draws the outline of a set of rectangles from an array of rectangles

FillClosedCurve

Draws a solid closed curve defined by an array of points

FillEllipse

Draws a solid ellipse, which you represent with a rectangle or rectangular set of points

FillPie

Draws a portion of a solid ellipse, which you represent with a rectangle and two radial lines that show the start and finish angles

FillPolygon

Draws a solid polygon, which is a closed set of lines from an array of points

FillRectangle

Draws a solid rectangle, which you represent with a coordinate and its width and height

FillRectangles

Draws a solid set of rectangles from an array of rectangles.

DrawIcon

Draws an image specified by the System.Drawing.Icon type

DrawImage

Draws an image specified by the System.Drawing.Image type

DrawImageUnscaled

Draws an image specified by the System.Drawing.Image type with no scaling

DrawString

Draws a string of characters

MeasureString

Gives the dimensions of a string of characters, so you can calculate where you want to place it on the image

DrawPath

Draws an outline, which you represent with the System.Drawing.Drawing2D.GraphicsPath, a class that allows you to add geometric constructs such as the curves, rectangles, ellipses, and polygons described earlier to save you from recalculating them each time (this is useful if you want to draw something that is complicated but fairly static)

FillPath

Provides the same functionality as DrawPath, except that it draws an image that is solid, rather than an outline

The second area you need to concentrate on in the System.Drawing namespace is closely related to the System.Drawing.Graphics object; you need to learn how to create the Icon, Image, Pen, and Brush objects used by the methods of the Graphics object. Table 8-2 shows examples of how to create these objects via their constructors.

Table 8.2. Creating Objects Used with the System.Drawing.Graphics Object

Snippet

Description

Color.FromArgb(33, 44, 55)

Creates a color from its red, green, and blue components

Color.FromKnownColor(KnownColor.Crimson)

Creates a color from a member of the KnownColor enumeration

Color.FromName("HotPink")

Creates a color from its name in string form

new Font(FontFamily.GenericSerif, 8.0f)

Creates a new generic serif font that is eight points tall

Image.FromFile("myimage.jpg")

Creates a new image from a file

Image.FromStream(File.OpenRead ("myimage.gif"))

Creates a new image from a stream

new Icon("myicon.ico")

Creates a new icon from a file

new Icon(File.OpenRead("myicon.ico"))

Creates a new icon from a stream

new Pen(Color.FromArgb(33, 44, 55))

Creates a colored pen that you can use to draw lines

new Pen(SystemColors.Control, 2.0f)

Creates a pen, that you can use to draw lines, from a color and with a width of 2 pixels

new SolidBrush(Color.FromName("Black"))

Creates a solid brush that you can use to draw filled shapes

new TexturedBrush(Image.FromFile ("myimage.jpg"))

Creates a new textured brush from an image and draws a filled shape with an image mapped across it

If you prefer to use standard objects, you can use several classes in the System.Drawing namespace that contain predefined objects, including Brushes, Pens, SystemBrushes, SystemColors, SystemFonts, SystemIcons, and SystemPens. The following quick example illustrates how to use these predefined objects:

open System.Drawing

let myPen = Pens.Aquamarine
let myFont = SystemFonts.DefaultFont

Working with Controls in WinForms

A control is simply a class that derives from System.Windows.Forms.Control. You can display any class that derives from this class in a form by adding it to the Controls collection on the form object.

Next, you'll look at a way to draw the tree using controls. The WinForms library defines a TreeView class, which exists specifically for displaying tree-like structures; obviously you use this control to display the tree. To use TreeView, you create an instance of it and configure it by setting its properties and calling its methods. Most importantly, you add the nodes you want to display to its Nodes collection. Once the control is ready to be displayed, you add it to the form's Controls collection.

The TreeView class uses TreeNode objects to represent nodes, so you define the function mapTreeToTreeNode to walk the tree structure recursively and create a TreeNode graph. The program in Listing 8-2 produces the tree in Figure 8-3.

Example 8.2. Drawing a Tree via a TreeView Control

open System.Windows.Forms

// The tree type
type 'a Tree =
| Node of 'a Tree * 'a Tree
| Leaf of 'a

// The definition of the tree
let tree =
    Node(
        Node(
            Leaf "one",
            Node(Leaf "two", Leaf "three")),
        Node(
            Node(Leaf "four", Leaf "five"),
            Leaf "six"))

// A function to transform our tree into a tree of controls
let mapTreeToTreeNode t =
    let rec mapTreeToTreeNodeInner t (node : TreeNode) =
        match t with
        | Node (l, r) ->
            let newNode = new TreeNode("Node")
            node.Nodes.Add(newNode) |> ignore
            mapTreeToTreeNodeInner l newNode
            mapTreeToTreeNodeInner r newNode
        | Leaf x ->
            node.Nodes.Add(new TreeNode(sprintf "%A" x)) |> ignore
    let root = new TreeNode("Root")
    mapTreeToTreeNodeInner t root
    root

// create the form object
let form =
    let temp = new Form()
    let treeView = new TreeView(Dock = DockStyle.Fill)
    treeView.Nodes.Add(mapTreeToTreeNode tree) |> ignore
    treeView.ExpandAll()
    temp.Controls.Add(treeView)
    temp

Application.Run(form)
A TreeView control used to view a tree

Figure 8.3. A TreeView control used to view a tree

This code is about half the length of Listing 8-1, where you drew the tree yourself. It is also more functional because it allows you to fold away parts of the tree that don't interest you. This greatly improves the size of tree that you display in a manageable way.

In this example, you use the dock style to control how the control looks. You do this by setting the control's Dock property with a member of the DockStyle enumeration. A docked control takes up as much space as is available in the portion of the form that contains it. For example, you can dock a control on the left side if you use DockStyle.Left, on the right side if you use DockStyle.Right, at the top if you use DockStyle.Top, on the bottom if you use DockStyle.Bottom, and on the whole form if you use DockStyle.Fill. This is great when you have only a few controls because it creates a nice, dynamic effect where the controls are resized when the user resizes the form. However, this approach doesn't work well with a lot of controls because it is difficult to get many controls to fit together nicely using this technique. For example, if you have two controls that are docked to the left, it can be confusing to determine which one is supposed to be the leftmost one and how much of the left side they should both take up. If you have a lot of controls, a better solution is to control their layout explicitly using the Top and Left properties. You can create a dynamic effect by using the Anchor property to anchor the control to the edge of the containing form. The following example creates a form with a single textbox on it; this form grows or shrinks as the user resizes the form:

open System
open System.Windows.Forms

let form =
    // create a form
    let temp = new Form()
// create a text box and set its anchors
    let textBox = new TextBox(Top=8,Left=8, Width=temp.Width - 24,
                              Anchor = (AnchorStyles.Left |||
                                        AnchorStyles.Right |||
                                        AnchorStyles.Top))

    // add the text box to the form and return the form
    temp.Controls.Add(textBox)
    temp

[<STAThread>]
do Application.Run(form)

Unfortunately, this method of working with controls is not always satisfactory. Here you displayed only one control, but often you want to display tens (or even hundreds) of controls on a form. Writing all the code to create and configure the controls can quickly become tedious and error-prone. To help you get around this, Visual Studio provides some form designers that allow you to create forms visually. However, no designer exists for F# at this time, so the next section will explain how to use F# to work with forms created with the C# designer.

One of the difficulties facing the WinForms programmer when working with controls is that there are many controls from which to choose. This chapter covers only one control. Unfortunately, there's no substitute for experience when it comes to learning what works. The MSDN library (http://msdn.microsoft.com) provides an excellent reference, but the volume of information in that library can be a little off-putting for those new to the subject. Table 8-3 flattens out this learning curve slightly by summarizing some of the most useful controls.

Table 8.3. Common WinForm Controls and How to Use Them

Control

Description

Label

This control to displays text information to the user; most other controls should be accompanied by a Label that explains their usage. Placing an & in the text of the Text property of the Label underlines the letter directly after it, enabling the keyboard user to hop to the control associated with the Label (the control next in the tab order) by pressing Alt+<letter>; this is a good technique for improving application usability.

TextBox

This control provides a box for entering text. The default is a single line of text, but you can change this to support multiple-line entry if you set the Multiline property to true. You should also check that the WordWrap and ScrollBar properties are to your liking. This control is also useful for displaying text to the user that you want them to be able to copy and paste; you do this by setting the ReadOnly property to true.

MaskedTextBox

This control is a textbox similar in a lot of respects to the previous control; it allows you limit the data a user can enter by setting the Mask property.

Button

This control provides a button for the user to click; as with the Label control, placing an & in the text of the Text property of the Button control underlines the letter directly after it and allows the keyboard user to hop to the Button by pressing Alt+<letter>. Again, this is great for usability.

LinkLabel

This control's name is slightly misleading. You don't really use this as a label, but as a type of button that looks like an HTML link. This control works great for users used to a web environment; it also lets you indicate that clicking the button will open a web page.

CheckBox

This control displays a box for the users to check if you have a set of options that are not mutually exclusive.

RadioButton

This control behaves a lot like a CheckBox, but you use it to display options that are mutually exclusive. Placing several of these in the same container makes all the options mutually exclusive. The container is usually a Form.

DateTimePicker

This control allows the user to pick a date via a drop-down calendar.

MonthCalander

This control allows a user to pick a date from a calendar that is permanently on display.

ComboBox

This control allows a user to make a selection from a drop-down list; this is great for showing a dynamic set of data via data binding (see Chapter 9 for more details on this).

ListBox

This control is similar to a ComboBox, but the list of items is displayed within the form rather than as a drop-down list. You should favor this one if your form has a lot of free space.

DataGridView

This control provides an excellent way to display information from a database table, although you can use it to display any kind of tabular data. You should always choose this option over the older DataGrid (you'll learn more about this in Chapter 9).

TreeView

This control is also great for showing dynamic data; however, it is most useful for displaying data in a tree-like form.

ProgressBar

This control gives your users feedback about any long-running activity that is vital for a usable application.

RichTextBox

This control provides a way to display and edit rich text documents, which is useful if your users want a little more formatting than what the standard textbox offers.

WebBrowser

This control displays HTML documents; it's useful because a lot of information is available in HTML format.

Panel

This control breaks your form into different sections; this is highly effective when you combine the control with HScrollBar and VScrollBar.

HScrollBar

This control is a horizontal scroll bar; you can use it to fit more information on a Form or Panel.

VScrollBar

This control is a vertical scroll bar; you use it to fit more information on a Form or Panel.

TabControl

This control is a form that uses a series of tabs to display user controls.

Using the Visual Studio Form Designer's Forms in F#

F# does not yet have a form designer of its own; however, thanks to the great interoperability of .NET, it is easy to use forms created with the Visual Studio's form designer in F#. You have two approaches. First, you can create an F# library and call functions from this library in your Windows form. Second, you can create a library of forms and use them from your F# application. You learn both approaches in this chapter, as well as their comparative strengths and weaknesses. Both examples will rely on the same Fibonacci calculator (see Figure 8-4).

Warning

This book is about F#, and you don't need knowledge of any other programming language for the majority of its material. However, this topic requires that you understand a little of bit about another .NET programming language: C#. Specifically, you'll see two short C# listings in this section. You can easily replace the C# code with Visual Basic .NET code if you feel more comfortable with that language.

A Fibonacci calculator form created with the Visual Studio designer

Figure 8.4. A Fibonacci calculator form created with the Visual Studio designer

Your main consideration in creating an F# library that you can use from a form: You want to make it easy to use that library from your form. In this case, you'll create a function to calculate the Fibonacci number, which takes and returns an integer. This makes things simple because a form has no problem using the .NET integer type. You want the library to be reasonably efficient, so you also want to create a lazy list of Fibonacci numbers and define a function that can get the nth number:

module Strangelights.Fibonacci

// an infinite sequence of Fibonacci numbers
let fibs =
    (1,1) |> Seq.unfold
        (fun (n0, n1) ->
            Some(n0, (n1, n0 + n1)))

// a function to get the nth fibonacci number
let get n =
    Seq.nth n fibs

It's easy to use this function from a form; you just need to reference your F# .dll from the Visual Studio form project. You can use the module Strangelights.Fibonacci by opening the Strangelights namespace and treating Fibonacci as if it were a class in C#. The following example shows you how to call the function in C# and place the result in a control. Note that this form was created with Visual Studio 2005, so the control definitions are in a separate source file:

using System;
using System.Windows.Forms;
using Strangelights;

namespace CSApp {
        public partial class FibForm : Form {
                public FibForm() {
                        InitializeComponent();
                }

                private void calculate_Click(object sender, EventArgs e) {
                        // convert input to an integer
                        int n = Convert.ToInt32(input.Text);
                        // caculate the apropreate fibonacci number
                        n = Fibonacci.get(n);
                        // display result to user
                        result.Text = n.ToString();
                }
        }
}

If you want to use the form created in C# from F#, you need to expose certain controls as properties. You don't need to expose all of the controls—just the ones that you want to interact with from F#. The following example shows how to do this in C#; again, any designer-generated code is hidden in a separate file:

using System;
using System.Windows.Forms;

namespace Strangelights.Forms {
        public partial class FibForm : Form {
                // public constructor for the form
                public FibForm() {
                        InitializeComponent();
                }

                // expose the calculate button
                public Button Calculate {
                        get { return calculate; }
                }
                // expose the results label
                public Label Result {
                        get { return result; }
                }
// expose the inputs text box
                public TextBox Input {
                        get { return input; }
                }
        }
}

It is then very straightforward to reference the C# .dll from F# and create an instance of the form and use it. The following example demonstrates the code you use to do this:

open System.Windows.Forms
open Strangelights.Forms

// an infinite sequence of Fibonacci numbers
let fibs =
    (1,1) |> Seq.unfold
        (fun (n0, n1) ->
        Some(n0, (n1, n0 + n1)))

// a function to get the nth fibonacci number
let getFib n =
    Seq.nth n fibs

let form =
    // create a new instance of the form
    let temp = new FibForm()
    // add an event handler to the form's click event
    temp.Calculate.Click.Add
        (fun _ ->
            // convert input to an integer
            let n = int temp.Input.Text
            // caculate the apropreate fibonacci number
            let n = getFib n
            // display result to user
            temp.Result.Text <- string n)
    temp

Application.Run(form)

You can use both techniques to produce similar results, so the question remains: which is best to use for which occasions? The problem with a C# form calling F# is that you will inevitably end up writing quite a bit of C# to glue everything together. It can also be difficult to use some F# types, such as union types, from C#. Given these two facts, I generally create a C# forms library and use this from F#. You can learn more about making F# libraries ready for use with other .NET languages in Chapter 14.

Working with WinForms Events and the Event Module

Chapter 7 introduced the Event module, which can be useful when working with events in WinForms. When working with events in a WinForm, you often encounter cases where no event exactly fits what you want. For example, the MouseButton event is raised when either the left or right mouse button is clicked, but you might want to respond only to the click of the left-mouse button. In this case, you might find it useful to use the Event.filter function to create a new event that responds only to a click of the leftmouse button. The next example demonstrates how to do this:

open System.Windows.Forms
let form =
    // create a new form
    let temp = new Form()

    // subscribe the mouse click event filtering so it only
    // reacts to the left button
    temp.MouseClick
    |> Event.filter (fun e -> e.Button = MouseButtons.Left)
    |> Event.add (fun _ -> MessageBox.Show("Left button") |> ignore)

    // return the form
    temp

Application.Run(form)

Here you use the filter function with a function that checks whether the left-mouse button is pressed; the resulting event is then piped forward to the listen function that adds an event handler to the event, exactly as if you had called the event's .Add method. You could have implemented this using an if expression within the event handler, but this technique has the advantage of separating the logic that controls the event firing from what happens during the event itself. Several event handlers can reuse the new event, depending on your needs.

Listing 8-3 demonstrates how to use more of Event's functions to create a simple drawing application (see Figure 8-5). Here you want to use the MouseDown event in different ways: first, you use it to monitor whether the mouse is pressed at all; second, you use it to split the event into left- or right-button presses using the Event.partition function. You can use this to control the drawing color, whether red or black:

Example 8.3. Using Events to Implement a Simple Drawing Application

open System
open System.Drawing
open System.Windows.Forms

let form =
    // create the form
    let temp = new Form(Text = "Scribble !!")
// some refrence cells to hold the applications state
    let pointsMasterList = ref []
    let pointsTempList = ref []
    let mouseDown = ref false
    let pen = ref (new Pen(Color.Black))

    // subscribe to the mouse down event
    temp.MouseDown.Add(fun _ -> mouseDown := true)

    // create a left mouse down and right mouse down events
    let leftMouse, rightMouse =
        temp.MouseDown
        |> Event.partition (fun e -> e.Button = MouseButtons.Left)

    // use the new left and right mouse events to choose the color
    leftMouse.Add(fun _ -> pen := new Pen(Color.Black))
    rightMouse.Add(fun _ -> pen := new Pen(Color.Red))

    // the mouse up event handler
    let mouseUp _ =
        mouseDown := false
        if List.length !pointsTempList > 1 then
            let points = List.toArray !pointsTempList
            pointsMasterList :=
                (!pen, points) :: !pointsMasterList
        pointsTempList := []
        temp.Invalidate()

    // the mouse move event handler
    let mouseMove (e: MouseEventArgs) =
        pointsTempList := e.Location :: !pointsTempList
        temp.Invalidate()

    // the paint event handler
    let paint (e: PaintEventArgs) =
        if List.length !pointsTempList > 1 then
            e.Graphics.DrawLines
                (!pen, List.toArray !pointsTempList)
        !pointsMasterList
        |> List.iter
            (fun (pen, points) ->
                e.Graphics.DrawLines(pen, points))
// wire up the event handlers
    temp.MouseUp |> Event.add mouseUp

    temp.MouseMove
    |> Event.filter(fun _ -> !mouseDown)
    |> Event.add mouseMove

    temp.Paint |> Event.add paint

    // return the form object
    temp

[<STAThread>]
do Application.Run(form)
Scribble: a simple drawing application implemented using events

Figure 8.5. Scribble: a simple drawing application implemented using events

You can also publish events you create in this way on the form's interface, which means the code that consumes the form can also take advantage of these events.

Again, a big problem facing a programmer working with events in WinForms is the volume of events available, which can make choosing the right one difficult. Perhaps surprisingly, most events are defined on the class Control, with each specialization providing only a handful of extra events. This generally makes life a bit easier because, if you have used an event with a control, odds are it will also be available on another control. Table 8-4 provides a helpful summary of the most common events on the Control class:

Table 8.4. A Summary of Events on the Control Class

Event

Description

Click

This event is caused by the user clicking the control. It is a high-level event, and although it is ordinarily caused by the user clicking with the mouse, it might also be caused by the user pressing Enter or the spacebar when hovering over a control. A series of events—MouseDown, MouseClick, and MouseUp— provide more detailed information about the actions of the mouse. However, these events provide information only about the mouse actions, so you should usually handle the Click, instead of these events. Otherwise, this will lead to the control not responding in the way users expect, because it will not respond to keystrokes and only mouse clicks.

DoubleClick

This event is raised when a user clicks the mouse twice in quick succession. Note that the user's operating system settings determine the interval the two clicks must occur in. You need to be careful when handling this event because every time this event is raised, a Click event will have been raised immediately before it. This means you must handle either this event or the Click event.

Enter

This event is raised when the control becomes active—either because the user presses Tab to enter it, the programmer calls Select or SelectNextControl, or the user clicks it with the mouse. Typically, you use this control to draw attention to the fact that the control is active; for example, you might set the background to a different color. Note that this event is suppressed on the Form class, where you should use Activated instead.

Leave

This event is raised when the control is deactivated—either because the user presses Tab to leave it, the programmer calls Select or SelectNextControl, or the user clicks another control with the mouse. You might be tempted to use this event for validation, but you should use the Validating and Validated events, instead. This event is suppressed on the Form class, where you should use Activated instead.

KeyPress

This event is part of a sequence of events that you can use to get detailed information about the state of the keyboard. To get details about when a key is first pressed, use KeyDown; to find out when it is released, use KeyUp.

Move

This event is raised whenever a user moves the control.

MouseHover

This event is useful for finding out whether the mouse is hovering over a control; you can use it to give a user more information about the control. The events MouseEnter and MouseLeave are also useful for this.

Paint

This event occurs when the form will be repainted by Windows; you need to handle this event if you want to take care of drawing the control yourself. For more information about this, see the "Drawing WinForms" section earlier in this chapter.

Resize

This event occurs when the user resizes the form; you could handle this event to adjust the layout of the form to a new size.

Creating New Forms Classes

So far you've looked only at a script style of programming, where you use an existing form and controls to put forms together quickly. This style of programming is great for the rapid development of single-form applications, but it has some limitations in cases where you want to create applications composed of multiple forms quickly, or you want to create libraries of forms for use with other .NET languages. In these cases, you must take a more component-oriented approach.

Typically, you want to use some forms repeatedly when you create a large WinForms application; furthermore, you typically want these forms to be able to communicate with each other by adjusting each other's properties and calling each other's methods. You usually do this by defining a new form class that derives from System.Windows.Forms. Listing 8-4 shows a simple example of this, using the class syntax introduced in Chapter 5.

Example 8.4. A Demonstration of Creating a New Type of Form

open System
open System.Windows.Forms

// a class that derives from "Form" and add some user controls
type MyForm() as x =
    inherit Form(Width=174, Height=64)

    // create some controls to add the form
    let label = new Label(Top=8, Left=8, Width=40, Text="Input:")
    let textbox = new TextBox(Top=8, Left=48, Width=40)
    let button = new Button(Top=8, Left=96, Width=60, Text="Push Me!")

    // add a event to the button
    do button.Click.Add(fun _ ->
        let form = new MyForm(Text=textbox.Text)
        form.Show())

    // add the controls to the form
    do x.Controls.Add(label)
    do x.Controls.Add(textbox)
    do x.Controls.Add(button)

    // expose the text box as a property
    member x.Textbox = textbox

// create an new instance of our form
let form =
    let temp = new MyForm(Text="My Form")
    temp.Textbox.Text <- "Next!"
    temp

[<STAThread>]
do Application.Run(form)

Executing the preceding code produces the forms shown in Figure 8-6.

Creating a new type of form for easy reuse

Figure 8.6. Creating a new type of form for easy reuse

In the preceding example, you create a form with three fields: label, textbox, and button. You can then manipulate these fields using external code. At the end of the example, you create a new instance of this form and then set the Text property of the textbox field.

Events can be exposed on the interface of a form in much the same way that fields can. This takes a little more work because of some inherent restrictions. The idea is to create a new event, store this event in a field in the class, and finally, to make this event a subscriber to the filtered event. You can see this demonstrated in the next example, where you filter the MouseClick event to create a LeftMouseClick:

open System.Windows.Forms

// a form with addation LeftMouseClick event
type LeftClickForm() as x =
    inherit Form()
    // create the new event
    let event = new Event<MouseEventArgs>()

    // wire the new event up so it fires when the left
    // mouse button is clicked
    do x.MouseClick
        |> Event.filter (fun e -> e.Button = MouseButtons.Left)
        |> Event.add (fun e -> event.Trigger e)

    // expose the event as property
    [<CLIEvent>]
    member x.LeftMouseClick = event.Publish

Forms you create in this component-based manner will undoubtedly be easier to use than forms you create with a more scripted approach; however, you still face pitfalls when you create libraries for other .NET languages. Please refer to Chapter 13 for more information about making F# libraries usable by other .NET languages.

Introducing Windows Presentation Foundation

WPF is a library that offers a completely new programming model for user interfaces. It is aimed at creating desktop applications that have more pizzazz than the ones that are created with WinForms. WPF also comes with a new XML-based language called XAML, which you can use to code the bulk of this form's layout, leaving you free to use F# to code the interesting parts of your application.

Note

Several XAML designers now exist; these designers allow F# users to design their interface using a graphical WYSWIG tool and then add interactivity to it using F#. For example, Mobiform offers a designer called Aurora (www.mobiform.com/products/aurora/aurora.htm), and Microsoft offers a designer called Expression Blend (www.microsoft.com/expression/products/overview.aspx?key=blend).

WPF is part of .NET 3.0, and it installs by default if you use Windows Vista. Users of other versions of Windows need to install .NET 3.0 to get access to WPF; the easiest way to do this is to download the .NET Windows SDK for Windows Server 2008 and .NET Framework 3.5 (http://is.gd/521hd). To make the examples in this section work, you need to add references to the following dlls: PresentationCore.dll, PresentationFramework.dll, and WindowsBase.dll.

The first example you'll look at shows you how to create a simple form in XAML and then display it to the user using F# (see Listing 8-5). This example shows the XAML definition of a form with four controls: two labels, a textbox, and a button.

Example 8.5. A Simple Form Created in XAML

<Window
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" >
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="64" />
<ColumnDefinition Width="128" />
<ColumnDefinition Width="128" />
<ColumnDefinition Width="128" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="24"/>
</Grid.RowDefinitions>
<Label Grid.Row="0" Grid.Column="0" >Input: </Label>
<TextBox Name="input" Grid.Column="1" Text="hello" />
<Label Name="output" Grid.Row="0" Grid.Column="2" ></Label>
<Button Name="press" Grid.Column="3" >Press Me</Button>
</Grid>
</Window>

To make this XAML definition of a form useful, you need to do two things. First, you must load the form's definition and show it to the user; however, doing only this will offer no interaction with the user. Thus, the second thing you need to do is make the form interactive. To do this, you use F# to add event handlers to the controls; in this case, you use it to add an event handler to the button that places the contents of the textbox into the second label. The function createWindow is a general-purpose function for loading a XAML form. You then use this function to create the value window, and you pass this value to the form's FindName method to find the controls within the form, so you can interact with them. Finally, you create an instance of the Application class in the main function and use this to show the form (see Listing 8-6).

Example 8.6. Displaying the XAML Form and Adding Event Handlers to It

open System
open System.Collections.Generic
open System.Windows
open System.Windows.Controls
open System.Windows.Markup
open System.Xml

// creates the window and loads the given XAML file into it
let createWindow (file : string) =
    using (XmlReader.Create(file)) (fun stream ->
        (XamlReader.Load(stream) :?> Window))

// create the window object and add event handler
// to the button control
let window =
    let temp = createWindow "Window1.xaml"
    let press = temp.FindName("press") :?> Button
    let textbox = temp.FindName("input") :?> TextBox
    let label = temp.FindName("output") :?> Label
    press.Click.Add (fun _ -> label.Content <- textbox.Text )
    temp

// run the application
let main() =
    let app = new Application()
    app.Run(window) |> ignore

[<STAThread>]
do main()

To get this program to compile, you must add references to PresentationCore.dll, PresentationFramework.dll, and WindowsBase.dll, which you can usually find0 in the directory C:Program FilesReference AssembliesMicrosoftFrameworkv3.0. In the other examples in this chapter, you didn't need to add references because the compiler referenced the libraries automatically. You can see the form created by the preceding example as in Figure 8-17.

A form created using XAML and F#

Figure 8.7. A form created using XAML and F#

Introducing Windows Presentation Foundation 3D

Another great advantage of WPF is the huge number of controls it offers. One control that you'll dig a little deeper into is Viewport3D, which offers the ability to create impressive 3D graphics, something not readily available with the WinForms library. You'll learn how you can use this control to display a 3D plane and then map an equation over it.

The example starts with the XAML script. Both XAML and 3D graphics are huge topics, and it would it exceed this book's ambitions to cover them fully. But you'll still learn enough about them to get an idea of what they involve, as well as how you might and branch off and explore these topics on your own. The following XAML script describes a window with one control, a Viewport3D, on it. The script is fairly lengthy because making a 3D scene requires that you include quite a few elements. You begin by defining a camera, so you know which direction you're looking at the scene from. You do this using the <Viewport3D.Camera> element:

<Viewport3D.Camera>
  <PerspectiveCamera Position="0,0,2" LookDirection="0,0,-1" FieldOfView="60" />
</Viewport3D.Camera>

The tags inside <Model3DGroup> describe what the scene will look like, while the <AmbientLight Color="White" /> tag describes how you will light the scene, and the <GeometryModel3D.Geometry> tag describes the 3D shape in the scene:

<GeometryModel3D.Geometry>
  <MeshGeometry3D />
</GeometryModel3D.Geometry>

Here you could use the <MeshGeometry3D /> tag to describe all the objects that make up the scene by providing the specific points that make up the objects; however, you don't use this tag to describe the points that make up the shape because it is a lot easier to do this in F# than in XAML. The <GeometryModel3D.Material> tag describes what the surface of the shape will look like:

<GeometryModel3D.Material>
  <DiffuseMaterial>
    <DiffuseMaterial.Brush>
      <ImageBrush ImageSource="venus.jpg" />
    </DiffuseMaterial.Brush>
  </DiffuseMaterial>
</GeometryModel3D.Material>

The <GeometryModel3D.Transform> tag describes a transformation that you want to apply to the shape; that is, it describes the angle you want to rotate the shape by:

<GeometryModel3D.Transform>
  <RotateTransform3D>
    <RotateTransform3D.Rotation>
      <AxisAngleRotation3D
        x:Name="MyRotation3D"
        Angle="45"
        Axis="0,1,0"/>
      </RotateTransform3D.Rotation>
    </RotateTransform3D>
</GeometryModel3D.Transform>

You do this mainly so you can use the <Viewport3D.Triggers> tag to define an animation that you use to alter the angle you display the shape at over time:

<Viewport3D.Triggers>
      <EventTrigger RoutedEvent="Viewport3D.Loaded">
        <EventTrigger.Actions>
          <BeginStoryboard>
            <Storyboard>
              <DoubleAnimation
                From="-80"
                To="80"
                Duration="0:0:12"
Storyboard.TargetName="MyRotation3D"
                Storyboard.TargetProperty="Angle"
                RepeatBehavior="Forever"
                AutoReverse="True" />
            </Storyboard>
          </BeginStoryboard>
        </EventTrigger.Actions>
      </EventTrigger>
    </Viewport3D.Triggers>

Listing 8-7 shows the complete example, so you can see how these various sections hang together.

Example 8.7. A XAML Definition of a 3D Scene

<Window
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Viewport3D Name="ViewPort">
<Viewport3D.Camera>
<PerspectiveCamera Position="0,0,2" LookDirection="0,0,-1" FieldOfView="60" />
</Viewport3D.Camera>
<Viewport3D.Children>
<ModelVisual3D>
<ModelVisual3D.Content>
<Model3DGroup >
<Model3DGroup.Children>
<AmbientLight Color="White" />
<GeometryModel3D>
<GeometryModel3D.Geometry>
<MeshGeometry3D />
</GeometryModel3D.Geometry>
<GeometryModel3D.Transform>
<RotateTransform3D>
<RotateTransform3D.Rotation>
<AxisAngleRotation3D
x:Name="MyRotation3D"
Angle="45"
Axis="0,1,0"/>
</RotateTransform3D.Rotation>
</RotateTransform3D>
</GeometryModel3D.Transform>
<GeometryModel3D.Material>
<DiffuseMaterial>
<DiffuseMaterial.Brush>
<ImageBrush ImageSource="venus.jpg" />
</DiffuseMaterial.Brush>
</DiffuseMaterial>
</GeometryModel3D.Material>
</GeometryModel3D>
</Model3DGroup.Children>
</Model3DGroup>
</ModelVisual3D.Content>
</ModelVisual3D>
</Viewport3D.Children>
<Viewport3D.Triggers>
<EventTrigger RoutedEvent="Viewport3D.Loaded">
<EventTrigger.Actions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimation
From="-80"
To="80"
Duration="0:0:12"
Storyboard.TargetName="MyRotation3D"
Storyboard.TargetProperty="Angle"
RepeatBehavior="Forever"
AutoReverse="True" />
</Storyboard>
</BeginStoryboard>
</EventTrigger.Actions>
</EventTrigger>
</Viewport3D.Triggers>
</Viewport3D>
</Window>

You will use F# to extend Listing 8-7 in Listing 8-8, borrowing a couple of functions from Listing 8-6; note that you save the code in Listing 8-7 to a file called Window2.xaml. You use the createWindow function to load the window and use a similar main function to display the window. You then use the findMeshes function to find any meshes in the picture (a mesh is a set of points used to describe the 3D plane). You find the meshes by walking the various objects in the Viewport3D and building up a list:

// finds all the MeshGeometry3D in a given 3D view port
let findMeshes ( viewport : Viewport3D ) =
    viewport.Children
    |> Seq.choose
        (function :? ModelVisual3D as c -> Some(c.Content) | _ -> None)
|> Seq.choose
        (function :? Model3DGroup as mg -> Some(mg.Children) | _ -> None)
    |> Seq.concat
    |> Seq.choose
        (function :? GeometryModel3D as mg -> Some(mg.Geometry) | _ -> None)
    |> Seq.choose
        (function :? MeshGeometry3D as mv -> Some(mv) | _ -> None)

You should keep this function generic, so it can work with any Viewport3D. It is highly likely that you will want to grab a list of all the meshes in your 3D scene for any 3D work you do in XAML and F#. The reason: You will probably want to use F# to manipulate your meshes in some way. Then you use createPlaneItemList, createSquare, createPlanePoints, createIndicesPlane, and addPlaneToMesh to add a flat plane to the mesh object in the scene. The function mapPositionsCenter centers the plane, placing it in the middle of the scene. Finally, a clever little function called changePositions maps the function movingWaves repeatedly across the plane ten times a second. The core of changePositions creates a new Point3DCollection from the Point3D objects contained in the old collection using the function movingWaves to decide what the new Z position should be:

let changePositions () =
    let dispatcherTimer = new DispatcherTimer()
    dispatcherTimer.Tick.Add
        (fun e ->
            let t = (float DateTime.Now.Millisecond) / 2000.0
            let newPositions =
                mesh.Positions
                |> Seq.map
                    (fun position ->
                        let z = movingWaves t position.X position.Y
                        new Point3D(position.X, position.Y, z))
            mesh.Positions <- new Point3DCollection(newPositions))
    dispatcherTimer.Interval <- new TimeSpan(0,0,0,0,100)
    dispatcherTimer.Start()

You use the DispatcherTimer class to execute the code on the thread that created the form, which means you don't need to call back to this thread to update the form. You need to call this class at least ten times a second to create a smooth animation effect (see Listing 8-8 for the complete example).

Example 8.8. Displaying and Interacting with a 3D XAML Scene

open System
open System.Collections.Generic
open System.IO
open System.Windows
open System.Windows.Controls
open System.Windows.Markup
open System.Windows.Media
open System.Windows.Media.Media3D
open System.Windows.Threading
open System.Xml

// creates the window and loads the given XAML file into it
let createWindow (file : string) =
    using (XmlReader.Create(file))
        (fun stream ->
            let temp = XamlReader.Load(stream) :?> Window
            temp.Height <- 400.0
            temp.Width <- 400.0
            temp.Title <- "F# meets Xaml"
            temp)
// finds all the MeshGeometry3D in a given 3D view port
let findMeshes ( viewport : Viewport3D ) =
    viewport.Children
    |> Seq.choose
        (function :? ModelVisual3D as c -> Some(c.Content) | _ -> None)
    |> Seq.choose
        (function :? Model3DGroup as mg -> Some(mg.Children) | _ -> None)
    |> Seq.concat
    |> Seq.choose
        (function :? GeometryModel3D as mg -> Some(mg.Geometry) | _ -> None)
    |> Seq.choose
        (function :? MeshGeometry3D as mv -> Some(mv) | _ -> None)

// loop function to create all items necessary for a plane
let createPlaneItemList f (xRes : int) (yRes : int) =
    let list = new List<_>()
    for x = 0 to xRes - 1 do
        for y = 0 to yRes - 1 do
            f list x y
    list

// function to initialize a point
let point x y = new Point(x, y)

// function to initialize a "d point
let point3D x y = new Point3D(x, y, 0.0)

// create all the points necessary for a square in the plane
let createSquare
    f (xStep : float) (yStep : float) (list : List<_>) (x : int) (y : int) =
    let x' = float x * xStep
    let y' = float y * yStep
list.Add(f x' y')
    list.Add(f (x' + xStep) y')
    list.Add(f (x' + xStep) (y' + yStep))
    list.Add(f (x' + xStep) (y' + yStep))
    list.Add(f x' (y' + yStep))
    list.Add(f x' y')

// create all items in a plane
let createPlanePoints f xRes yRes =
    let xStep = 1.0 / float xRes
    let yStep = 1.0 / float yRes
    createPlaneItemList (createSquare f xStep yStep) xRes yRes

// create the 3D positions for a plane, i.e., the thing that says where
// the plane will be in 3D space
let createPlanePositions xRes yRes =
    let list = createPlanePoints point3D xRes yRes
    new Point3DCollection(list)

// create the texture mappings for a plane, i.e., the thing that
// maps the 2D image to the 3D plane
let createPlaneTextures xRes yRes =
    let list = createPlanePoints point xRes yRes
    new PointCollection(list)

// create indices list for all our triangles
let createIndicesPlane width height =
    let list = new System.Collections.Generic.List<int>()
    for index = 0 to width * height * 6 do
        list.Add(index)
    new Int32Collection(list)

// center the plane in the field of view
let mapPositionsCenter (positions : Point3DCollection) =
    let newPositions =
        positions
        |> Seq.map
            (fun position ->
                new Point3D(
                            (position.X - 0.5 ) * −1.0,
                            (position.Y - 0.5 ) * −1.0,
                            position.Z))
    new Point3DCollection(newPositions)
// create a plane and add it to the given mesh
let addPlaneToMesh (mesh : MeshGeometry3D) xRes yRes =
    mesh.Positions <- mapPositionsCenter
                        (createPlanePositions xRes yRes)
    mesh.TextureCoordinates <- createPlaneTextures xRes yRes
    mesh.TriangleIndices <- createIndicesPlane xRes yRes

let movingWaves (t : float) x y =
    (Math.Cos((x + t) * Math.PI * 4.0) / 3.0) *
    (Math.Cos(y * Math.PI * 2.0) / 3.0)

// create our window
let window = createWindow "Window2.xaml"

let mesh =
    // grab the 3D view port
    let viewport = window.FindName("ViewPort") :?> Viewport3D
    // find all the meshes and get the first one
    let meshes = findMeshes viewport
    let mesh = Seq.head meshes
    // add plane to the mesh
    addPlaneToMesh mesh 20 20
    mesh

let changePositions () =
    let dispatcherTimer = new DispatcherTimer()
    dispatcherTimer.Tick.Add
        (fun e ->
            let t = (float DateTime.Now.Millisecond) / 2000.0
            let newPositions =
                mesh.Positions
                |> Seq.map
                    (fun position ->
                        let z = movingWaves t position.X position.Y
                        new Point3D(position.X, position.Y, z))
            mesh.Positions <- new Point3DCollection(newPositions))
    dispatcherTimer.Interval <- new TimeSpan(0,0,0,0,100)
    dispatcherTimer.Start()

let main() =
    let app = new Application()
    changePositions()
// show the window
    app.Run(window) |> ignore

[<STAThread>]
do main()

Executing the code in Listing 8-8 produces the window shown in Figure 8-8. It doesn't show off the animated results, but you should try out the application and see the animated effects for yourself.

A 3D scene created using XAML and F#

Figure 8.8. A 3D scene created using XAML and F#

You should also play with this sample in fsi. You can subtly alter the sample to run inside fsi, and then dynamically alter the function you apply to the plane. You must alter the original script in several small ways.

Begin by setting the reference to the .dll files in an fsi style:

#I @"C:Program FilesReference AssembliesMicrosoftFrameworkv3.0" ;;
#r @"PresentationCore.dll" ;;
#r @"PresentationFramework.dll" ;;
#r @"WindowsBase.dll" ;;

Next, you must alter the changePositions function to use a mutable function:

// mutable function that is used within changePositions function
let mutable f  = (fun (t : float) (x : float) (y : float) -> 0.0)

// function for changing the plane over time
let changePositions () =
    let dispatcherTimer = new DispatcherTimer()
    dispatcherTimer.Tick.Add
        (fun e ->
            let t = (float DateTime.Now.Millisecond) / 2000.0
            let newPositions =
                mesh.Positions
                |> Seq.map
                    (fun position ->
                        let z = f t position.X position.Y
                        new Point3D(position.X, position.Y, z))
            mesh.Positions <- new Point3DCollection(newPositions))
    dispatcherTimer.Interval <- new TimeSpan(0,0,0,0,100)
    dispatcherTimer.Start()

Finally, you show the window using its .Show() method rather than the Application class's Run method; be careful that you set its Topmost property to true so that it is easy to interact with the window and see the results:

// show the window, set it the top, and activate the function that will
// set it moving
window.Show()
window.Topmost <- true
changePositions ()

Finally, you need to define some other functions to map across the plane. This can be any function that takes three floating-point numbers (the first representing the time and the next two representing the X and Y coordinates, respectively) and returns a third floating-point that represents the Z coordinate. I'm particularly fond of using sine and cosine functions here because these generate interesting wave patterns. The following code includes some examples of what you might use, but please feel free to invent your own:

let cosXY _ x y =
    Math.Cos(x * Math.PI) * Math.Cos(y * Math.PI)

let movingCosXY (t : float) x y =
    Math.Cos((x + t) * Math.PI) * Math.Cos((y - t) * Math.PI)

You can then easily apply these functions to the plane by updating the mutable function:

f <- movingCosXY

Using this technique produces the image you see in Figure 8-9.

Controlling a 3D XAML scene interactively using F# interactive

Figure 8.9. Controlling a 3D XAML scene interactively using F# interactive

The WPF framework contains lots of types and controls that will take any programmer some time to learn. Fortunately, you can find many resources available on the Internet to help you do this. One good resource is the NetFx3 WPF site (http://wpf.netfx3.com); another is the WPF section of MSDN (http://msdn2.microsoft.com/en-us/netframework/aa663326.aspx).

Introducing GTK#

GTK# is a .NET wrapper around the popular cross-platform GUI library, GTK+. If you want to build a local application and want it to run on platforms other than Windows, GTK is probably the logical choice. GTK# works in a similar way to both WinForms and WFP; in GTK#, you base your windows on the Gtk.Window and use widgets (the equivalent of controls) based on the Gtk.Widget class.

GTK# is distributed with Mono Project distribution, so the easiest way to get access to it is to install Mono (http://www.go-mono.com/mono-downloads/download.html). The classes that make up GTK# are spread across four .dlls: atk-sharp.dll, gdk-sharp.dll, glib-sharp.dll, and gtk-sharp.dll. You need to add references to these .dlls to make the example in this section work.

Before creating any GTK# widgets, you must call the Application.Init() to initialize the GTK environment. After the controls are visible, you need to call the Application.Run() method to start the event loop; if you do not call this method, the window and widgets will not react user clicks and other inputs. When the user closes all the windows, you need to close the event loop by calling Application.Quit(). In the GTK# example (see Listing 8-9), you we have only one window, so you quit the GTK environment when this window closes:

// close the event loop when the window closes
     win.Destroyed.Add(fun _ -> Application.Quit())

You use a widget called an HBox or a VBox to lay out a GTK# application. Unlike the Window class, these widgets can contain more than one widget, stacking the widgets contained in them either horizontally or vertically. In Listing 8-9, you can see that you create a VBox, which means that the widgets contained within it are laid out horizontally:

// create a new vbox and add the sub controls
     let vbox = new VBox()
     vbox.Add(label)
     vbox.Add(button)

You can see the complete example in Listing 8-9; executing the code in these listings produces the image shown in Figure 8-10.

Example 8.9. A Simple Example of a GTK# Application

open Gtk

let main() =
     // initalize the GTK environment
     Application.Init()
// create the window
     let win = new Window("GTK# and F# Application")
     // set the windows size
     win.Resize(400, 400)

     // create a label
     let label = new Label()

     // create a button and subscribe to
     // its clicked event
     let button = new Button(Label = "Press Me!")
     button.Clicked.Add(fun _ ->
        label.Text <- "Hello World.")

     // create a new vbox and add the sub controls
     let vbox = new VBox()
     vbox.Add(label)
     vbox.Add(button)

     // add the vbox to the window
     win.Add(vbox)

     // show the window
     win.ShowAll()

     // close the event loop when the window closes
     win.Destroyed.Add(fun _ -> Application.Quit())

     // start the event loop
     Application.Run()

do main()
The GTK# application running under linux

Figure 8.10. The GTK# application running under linux

Introducing ASP.NET

ASP.NET is a technology intended to simplify creating dynamic web pages. The simplest way to do this is to implement an interface called IHttpHandler. This interface allows the implementer to describe how an HTTP request should be responded to; the next section of the chapter will explain how this works.

Merely implementing the IHttpHandler interface doesn't allow you to take full advantage of the ASP.NET feature set. ASP.NET allows users to create web forms, which are composed of controls that know how to render themselves into HTML. The advantage of this is that the programmer has a nice object model to manipulate, rather than having to code HTML tags. It also allows a programmer to separate the layout of controls into an .aspx file. An .aspx file basically contains all the static HTML you don't want to worry about in your F# code, plus a few placeholders for the dynamic controls. This approach is great for programming in F# because it allows you to separate the code that represents the layout of a form, which can look a little long in F#, from the code that controls its behavior. ASP.NET also lets you store configuration values in an XML-based web.config file.

Working with ASP.NET presents an additional challenge; you must configure the web server that will host the ASP.NET application. Your configuration will vary, depending on your development environment.

Visual Studio has come with a built-in web server since Visual Studio 2005. Creating a new web site is a simple matter of selecting File~TRANew~TRAWeb Site and then choosing the location for the web site. This site will run only pages written in C# or Visual Basic .NET, so you need to add an F# project to the solution and then manually alter the solution file so that it lives inside the web site directory. This is easier than it sounds. All you need to do is copy the .fsproj file to the web site directory, open the .sln file in Notepad, and alter the path to the .fsproj file. After this, you need to configure the project file to output a library and write this to a bin subdirectory. This might seem like a lot of effort, but once you do this, you can press F5 to make your project compile and run.

If you do not have Visual Studio, then your next best choice is host the site in IIS. In some ways, this is easier than hosting your site in Visual Studio; however, IIS doesn't let you just execute your code once you finish writing it. To host your code in IIS, you need to create an IIS virtual directory with a subdirectory called bin. You then need to copy your .aspx pages and your web.config file to the virtual directory.

ASP.NET has always been part of the .NET framework, so you don't need to install any additional features to make these examples work; however, you do need to add a reference to the System.Web.dll to make all of the examples in this section work.

Creating an IHttpHandler

Creating an IHttpHandler is the simplest way to take advantage of ASP.NET 2.0. This simple interface has only two members. The first member is a read-only Boolean property called IsReusable that you use to indicate whether the runtime can reuse the instance of the object. It is generally best to set this to false.

The second member of the interface is the ProcessRequest method, which is called when a web request is received. It takes one parameter of HttpContent type; you can use this type to retrieve information about the request being made through its Request property, as well as to respond to the request via its Response property. The following, simple example of an IHttpHandler responds to a request with the string "<h1>Hello World</h1>":

namespace Strangelights.HttpHandlers
open System.Web

// a http handler class
type SimpleHandler() =
    interface IHttpHandler with
        // tell the ASP.NET runtime if the handler can be reused
        member x.IsReusable = false
        // The method that will be called when processing a
        // HTTP request
        member x.ProcessRequest(c : HttpContext) =
            c.Response.Write("<h1>Hello World</h1>")

Next, you must configure the URL where the IHttpHandler is available. You do this by adding an entry to the web.config file. If don't already have a web.config file in the project, you can add one by right-clicking the web project and choosing Add New Item. The handlers are added to the httpHandlers section, and you need to configure four properties for each handler: path, which is the URL of the page; verb, which configures which HTTP verbs the handler will respond to; type, which is the name of the type that you will use to handle the request; and validate, which tells the runtime whether it should check the availability of the type when the application loads:

<configuration>
    <system.web>
        <httpHandlers>
            <add path="hello.aspx"
                 verb="*"
                 type="Strangelights.HttpHandlers.SimpleHandler"
                 validate="true"/>
       </httpHandlers>
    </system.web>
</configuration>

Executing SimpleHandler produces the web page shown in Figure 8-11.

The resulting web page when the SimpleHandler is executed

Figure 8.11. The resulting web page when the SimpleHandler is executed

This technique is unsatisfactory for creating web pages because it requires that you mix HTML tags t into your F# code. It does have some advantages, though. You can use this technique to put together documents other than HTML documents; for example, you can use it to dynamically create images on the server. The following example shows an IHttpHandler that generates a JPEG image of a pie shape. The amount of pie shown is determined by the angle value that that is passed in on the query string. Making this example work requires that you add a reference to System.Drawing.dll:

namespace Strangelights.HttpHandlers

open System.Drawing
open System.Drawing.Imaging
open System.Web
// a class that will render a picture for a http request
type PictureHandler() =
    interface IHttpHandler with
        // tell the ASP.NET runtime if the handler can be reused
        member x.IsReusable = false
        // The method that will be called when processing a
        // HTTP request and render a picture
        member x.ProcessRequest(c : HttpContext) =
            // create a new bitmap
            let bitmap = new Bitmap(200, 200)
            // create a graphics object for the bitmap
            let graphics = Graphics.FromImage(bitmap)
            // a brush to provide the color
            let brush = new SolidBrush(Color.Red)
            // get the angle to draw
            let x = int(c.Request.QueryString.Get("angle"))
            // draw the pie to bitmap
            graphics.FillPie(brush, 10, 10, 180, 180, 0, x)
            // save the bitmap to the output stream
            bitmap.Save(c.Response.OutputStream, ImageFormat.Gif)

Again, you still need to register this type in the web.config file; the required configuration looks like this:

<configuration>
    <system.web>
        <httpHandlers>
            <add path="pic.aspx"
                 verb="*"
                 type="Strangelights.HttpHandlers.PictureHandler"
                 validate="true"/>
       </httpHandlers>
    </system.web>
</configuration>

Executing this code produces the image in shown in Figure 8-12. In this case, I passed in an angle of 200.

Using an IHttpHandler to dynamically generate a picture

Figure 8.12. Using an IHttpHandler to dynamically generate a picture

Although this is a great technique for spicing up web sites, but you should be careful when using it. Generating images can be processor intensive, especially if the images are large or complicated. This can lead to web sites that do not scale up to the required number of concurrent users; therefore, if you do use this technique, ensure you profile your code correctly.

Working with ASP.NET Web Forms

If you want to create dynamic web pages, then you will probably have an easier time using ASP.NET forms than implementing your own IHttpHandler. The main advantage of web forms is that you do not need to deal with HTML tags in F# code; most of this is abstracted away for you. This approach confers other, smaller advantages too. For example, it means you do not have to register the page in web.config.

To create an ASP.NET web form, you generally start by creating the user interface, defined in an .aspx file. The .aspx file contains all your static HTML, plus some placeholders for the dynamic controls. An .aspx file always starts with a Page directive; you can see this at the top of the next example. The Page directive allows you to specify a class that the page will inherit from; you do this by using the Inherits attribute and giving the full name of the class. You will use an F# class to provide the dynamic functionality.

The following example includes some tags prefixed with asp: among the regular HTML tags. These are ASP.NET web controls, and they provide the dynamic functionality. A web control is a class in the .NET Framework that knows how to render itself into HTML; for example, the <asp:TextBox /> tag will become an HTML <input /> tag. You can take control of these controls in your F# class and use them to respond to user input:

<%@ Page Inherits="Strangelights.HttpHandlers.HelloUser" %>
<html>
    <head>
        <title>F# - Hello User</title>
    </head>
    <body>
        <p>Hello User</p>
        <form id="theForm" runat="server">
        <asp:Label
            ID="OutputControl"
            Text="Enter you're name ..."
            runat="server" />
        <br />
        <asp:TextBox
            ID="InputControl"
            runat="server" />
        <br />
        <asp:LinkButton
            ID="SayHelloButton"
            Text="Say Hello ..."
            runat="server"
            OnClick="SayHelloButton_Click" />
        </form>
    </body>
</html>

When designing your class, you need to provide mutable fields with the same name as the controls you want to manipulate. The HTML page you created had three controls in it, but you provide only two mutable fields, because you don't want to manipulate the third control, a link button. You just want that button to call the SayHelloButton_Click function when a user clicks it. You do this by adding the function name to the OnClick attribute of the asp:LinkButton control.

When the other two controls are created, a label and a textbox, they will be stored in the mutable fields OutputControl and InputControl, respectively. It is the code contained in the .aspx page, not your class, that is responsible for creating these controls. This is why you explicitly initialize these controls to null in the constructor. All that remains in SayHelloButton_Click is to take the input from InputControl and place it into OutputControl:

namespace Strangelights.HttpHandlers

open System
open System.Web.UI
open System.Web.UI.WebControls
// class to handle to provide the code behind for the .aspx page
type HelloUser =
    inherit Page
    // fields that will hold the controls reference
    val mutable OutputControl: Label
    val mutable InputControl: TextBox
    // the class must have a parameterless constructor
    new() =
        { OutputControl = null
          InputControl = null }
    // method to handle the on click event
    member x.SayHelloButton_Click((sender : obj), (e : EventArgs)) =
        x.OutputControl.Text <- ("Hello ... " + x.InputControl.Text)

Executing the preceding example produces the web page shown in Figure 8-13.

A page created using an ASP.NET form

Figure 8.13. A page created using an ASP.NET form

This form doesn't look great, but the nice thing about defining your application in HTML is that you can quickly use images and Cascading Style Sheets (CSS) to spice up the application. Figure 8-14 shows the results of adding a little CSS magic.

A web page that takes full advantage of HTML and CSS

Figure 8.14. A web page that takes full advantage of HTML and CSS

You have taken only a brief look at all the functionality offered by ASP.NET. Table 8-5 summarizes all the namespaces available in System.Web.dll that contain ASP.NET functionality.

Table 8.5. A Summary of the Namespaces Available in System.Web.dll

Namespace

Description

System.Web

This namespace provides types that form the basis of ASP.NET's HTML rendering process; this is where the IHttpHander interface discussed in this chapter lives.

System.Web.Mail

This namespace provides types that you can use to send emails from ASP.NET applications.

System.Web.HtmlControls

This namespace provides controls that are exact copies of HTML tags.

System.Web.WebControls

This namespace provides controls that behave like HTML tags, but are more abstract. For example, the TextBox control is rendered as an input tag if you set its TextMode property to TextBoxMode.SingleLine and as a textarea if you set that property to TextBoxMode.MultiLine.

System.Web.WebControls.Adapters

This namespace provides adapters that you can use to affect the rendering of other controls. For example, you can alter their behavior or render different HTML tags for different types of browsers.

System.Web.WebControls.WebParts

This namespace provides web parts, controls that support a system where users can add, remove, and dynamically configure them within a page to give a personalized experience.

Summary

This chapter provided an overview of various options for creating user interfaces with F#. The scope of this topic is frankly enormous, and it would be impossible to cover all the options for user-interface programming in F#. For example, you can find hundreds of third-party components built on ASP.NET, WinForms, or WPF. These help raise the level of abstraction when creating user interfaces. You can also find libraries that offer complete alternative programming models, such as DirectX, which is designed for high-performance 3D graphics.

The next chapter will take a look at another important programming task—how to access data.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset