f you develop UI applications in F#, you probably miss the familiar Visual Studio form editor that comes with C# or VB. This is due to a simple reason: there are no partial classes in F#. This is precisely what the code generator behind the scenes would need to synthesize code for your design forms. While the F# team is working hard to find the best approach to support partial classes and bring the Visual Studio integration up to par with that of mainstream front-ends, there are a number of things you can do to bypass this soon-to-disappear annoyance.
For one, you can set up a multi-project solution and implement the UI as a C# or VB UI project, and code the rest of the application logic in F#. This works so well that it is hard to argue that you could do better unless you had in turn the ability to design the UI with F# code behind. But after you start making incremental changes to your UI project, various small issues start to crop up. One such issue is the accessibility of certain UI elements that you need for wiring event handlers; these need to be manually made public in your UI library project.
In this article, you will see two different approaches for making your UI coding easier and still retain all your application code in F#. In the first approach, you will develop a DSL to describe UI elements and their layout, and then implement a tool that generates F# code from this specification. While this has a sizeable gain over writing ordinary UI code in F# it also has some drawbacks: any errors you make are not discovered until you generated F# code and tried to compile it.
In the second part of the article, to remedy the problems in the first approach, you need to develop a computation
expression notation for building UI code directly in F#. Besides retaining a succinct declarative style, this also
has the added benefit of all code being in F#—so you get type safety and the discovery of errors is instantaneous.
Notepad: A Simple WinForms Application
Figure 1. Notepad Application: Here is the UI of a simple Notepad application.|
shows the UI of a simple Notepad application—basically a form with a menu bar—and an editor control that allows the user to edit text. Fundamentally, even the most complex UIs are built using the very same forms and a breadth of user controls. However, building up the hierarchy of these controls in code is repetitious and error-prone. Consider the following simple example:
let _ =
let form = new Form(Text="Example", Width=400, Height=300)
new Button(Text="Click me...") |> form.Controls.Add
new Button(Text="And me...", Left=80) |> form.Controls.Add
form |> Application.Run
Here, even though you exploit the handy F# shorthand notation for creating a form, and two buttons, and initialize their key properties in one step, you have to manually nest the controls to build the main form before you can run it as an application. This extra bit of work is more apparent and tedious if you had to build a larger collection of controls, say a menu bar with a handful of main menu items and their nested sub menus. Wouldn't it be nice to declaratively express the parent-child relationship that exists between these UI controls? Well, that is not that difficult as you will see shortly.
Part 1: Formgen, A DSL for Declaring UIs
If you had a tool to declare and generate your UI layer, you want it to support the following:
- The ability to name certain controls
- The ability to set control properties
- The ability to nest controls into other controls (containers)
Listing 1 shows an example (Notepad.form) of a DSL that is able to support all the above.
This snippet defines two forms, an empty one (AboutForm) and a more complex main form
(MainForm). In this DSL, you can introduce a new control by the plus
(+) symbol, give its type (for instance, Form) and name
(for instance, MainForm) followed by an optional set of constructor arguments
(for instance, mFile("&File")), and an optional set of property assignments in the form of
<minus><property><space><value> (for instance, -Width 400). Property
values can be of the usual types, with a "code" type being something extra—this can be given inside brackets as
a string (for example, look at the shortcut properties of most menu items). Child controls can be nested inside the
block delimited by braces, with an optional with <property> prefix that lets you designate the
parent control property to which the child nodes are added (this property should support the Add method to add a new item).
By default, this property is assumed to be Controls.
Occasionally, you need the ability to specify that certain child controls are assigned to a different property of the
parent control—this you can do with an optional as <property> suffix after giving the
control's name. For example, the MainForm form has two child controls: a MainMenu control that is
added as the form's Menu, and a Panel that is added to the form's
Each control in this DSL is named, but there are times when a given name is irrelevant. For example, above you used
the same name for separator menu items. By using +: to introduce a new control, you can essentially cause it not to be exposed, thus its name becomes irrelevant.
Furthermore, as you might have spotted, the Click event handler for the exit menu item: by
using ++<event-name> you can introduce a new event handler for the parent control and specify its code as a string.
Without further due on the syntax, you can proceed to create an AST definition to hold the necessary information for
the above DSL. Your FormType.fs file looks like the following:
module Types =
type comp =
| Component of bool * string * (string * par list) * string option * prop list * string * comp list
| EventHandler of string * string
and par =
| IntPar of int
| FloatPar of float
| StringPar of string
| BoolPar of bool
and prop =
| IntProp of string * int
| FloatProp of string * float
| StringProp of string * string
| BoolProp of string * bool
| CodeProp of string * string
Here, the comp
type defines a component or an event handler. A Component
Boolean flag that determines whether its name is exported, its type as a string, its name, and a list of constructor
parameters (defined as par
objects), an optional name for the as clause, a list of properties
(defined as prop
objects), the name of the container property to which child controls are
added, and a list of child controls and event handlers. Event handlers have a name and a code block defined as a
The lexer (FormLexer.fsl) follows a "typical" FsLex definition, except that you added the ability to
lex comments and strings with proper escape characters. You can easily add position information also to each token to
recover from later semantic errors with an exact position; this was not added in the implementation as shown in
The parser (FormParser.fsy) is equally straightforward and without much explanation you can
find it in Listing 3.
At this point, all you have left is a pretty printer that outputs F# code for an AST in your DSL. Here is
module Print =
exception DuplicateName of string
let rec collect_exports env = function
| Component (export, ty, (id, pars), _, props, _, comps) ->
let env' =
if export then
if Set.exists (fun (id', _) -> id = id') env then
raise (DuplicateName id)
Set.add (id, ty) env
List.fold_left (fun env comp ->
collect_exports env comp) env' comps
| EventHandler _ ->
takes a component, returns all exported names from it and all of its
child nodes, and rejects any duplicate names. This is because for each top-level component, you generate a record type
with the exported names as labels. The rest of the file contains various functions for pretty printing
(see Listing 4
Now you can tie it all together: in the main F# module (formgen.fs) call the lexer/parser with
an input file, and pretty print the result into an F# code file (see Listing 5).
Figure 2. References: Your F# project should contain all of the files and references shown here.|
For this, your F# project should contain all of the above files and references as shown in Figure 2
. To obtain the
lexer and parser implementation (FormLexer.fs
respectively) from FormLexer.fsl
you need to run:
are in the F# distribution (in the bin
This completes the formgen tool. To test it, take the Notepad snippet you saw earlier and run the
formgen tool on it to generate Notepad.fs. Then add this file to a new F# project,
add references to System.Drawing and System.Windows.Forms, and create a new F# file
with the following:
let _ =
let form = Notepad.CreateMainForm ()
form.mAbout.Click.Add (fun _ -> Notepad.CreateAboutForm().AboutForm.Show())
form.MainForm |> Application.Run
This code uses the generated Notepad module to create the main and about forms, and shows how to register an event handler for displaying the about box. You can add further event handlers to fill the Notepad UI with full functionality in a similar fashion.