Tech Babble | the random musings of a techie

F# and WPF

In certain projects, it may be deemed appropriate to utilize both F# and WPF. Visual Studio has a large number of issues that make this difficult, not the least of which is that it simply doesn't support creating a WPF project with F# out-of-the-box. What follows is a guide based on what I've run into, which should be enough to get up-and-running. Please be aware that I've tried to work with something resembling the Model-View-ViewModel design pattern, but I can't say as I've strictly adhered to it here or anywhere else -- I also provide an example type named CarListViewController, which is used to show how one might write a code-behind file with event handlers, etc., as has typically been done in WinForms projects.

Although I will explain some F#-isms, don't expect this tutorial to explain all F# to you. This will go more smoothly if you've played around with F# a bit already. Assume the same disclaimer for WPF, but doubled, as I touch on XAML and WPF less than I do on F#. This is really about mixing the two, not getting started in one of them.

Also of note, I'm writing this while working with Visual Studio 2013 Professional edition, and so some aspects of this may not match expectations if my steps are followed with a different version. If the licensing terms are acceptable, the Community edition of Visual Studio 2013 and later supports extensions, and should work well if Professional is not available.

Visual Studio 2010 Support

Visual Studio 2010 and earlier aren't supported at all for this method of building a F#/WPF application, because of the magic of type providers. Unfortunately, they were introduced into Visual Studio with 2012 and the release of F# 3.0. There are other ways this can be done, but none of them feel as well-polished, so I won't go into detail on them.

The best option I can think of when build a F#/WPF application when Visual Studio 2010 or earlier must be used is to build the WPF application in VB or C#, but only use it for the XAML and code-behind files, referencing a separate F# library containing the Model and ViewModel layers of the MVVM pattern.

I only mention all of this because I've had to support building everything in Visual Studio 2010, so while this tutorial may work well for some, it has the potential to be entirely useless for others.

One-Time Setup

These extra tools are not required when working with an existing project, but may still prove useful at some later point. First up, download and install the F# Empty WPF Project extension; this can be found here. Next, use Visual Studio's Extensions and Updates window to find and install the Visual F# Power Tools extension, and then under Tools -> Options -> General enable the Folder organization option. The F# Power Tools extension isn't strictly required at all, but makes the process of adding/using folders in an F# project a bit easier, among other wonderful goodies. It's certainly worth installing if you plan to use F#, even if you don't plan to work with WPF. See this section for some of the pitfalls of folders in F# projects; it's getting better, but isn't exactly a perfect situation.

Getting Started

Once the empty project extension is installed, create a new project using the F# Empty Windows App (WPF) template; I've named mine FSharpWpfGuide. This project type automatically includes three NuGet packages:

  • Expression.Blend.Sdk -- I haven't actually used this, so I'm not entirely sure of its purpose.
  • FSharp.ViewModule.Core -- provides base classes which I'll be using to make the MVVM pattern and use of ICommand much easier
  • FsXaml.Wpf -- includes a type provider for F# which parses XAML files and provides type definitions for them

The magic sauce here is the type provider from FsXaml.Wpf; for readers not familiar with type providers, what this does is parses a XAML file while developing in Visual Studio before compiling. The result is a type definition that can be used to generate the associated component, and provides properties for any XAML elements with an x:Name attribute. It might not seem like much, but I promise it's magic. ;)

Before moving forward, ask Visual Studio to build the solution, and a warning will appear. There's a lot of text in the warning window, but the gist is that since the type provider needs to read and write files on the local machine, and will execute code, there's potential for a malicious type provider to be written. This is the part where I warn that Visual Studio should not be run as an administrator. That said, to continue, the type provider needs to be enabled.

This warning doesn't come up anymore, as of Visual Studio 2015 with Update 1. Yay!

Files

According to the Solution Explorer, the new project should contain the following files:

  • MainWindow.xaml and MainWindow.xaml.fs
  • These are the XAML file and its code-behind for a default empty window

  • App.xaml

  • This file matches the file named Application.xaml which is typically created in Visual Studio's existing WPF project template. I won't get into the details of what it can do, but anything that a normal Application.xaml file could do can be done here

  • App.fs

  • This is akin to the Program.cs file in a C# project, or the default Module1.vb in a VB console application (VB doesn't give the developer access to this in a WinForms or WPF project), and it contains the main entry-point for the application. At this point, it should already contain use of the FsXaml.Xaml type provider to load up App.xaml and launch it.

  • App.config

  • As with any C# or VB project, this optionally contains settings and runtime configurations for the application.

  • packages.config

  • NuGet utilizes this XML file to track the packages installed, as well as the required version numbers.

Adding a Window

For the sake of going through more work, remove the files MainWindow.xaml and MainWindow.xaml.fs. "MainWindow" is a more apt name, but I'll be using "MainForm" simply to force myself to show how to change the startup window.

In the Solution Explorer, right-click App.xaml and select Add Above -> New Item..., then type MainForm.xaml into the file-name box. The particular file type selected doesn't matter much, so long as it's a single file being added. I strongly recommend simply not touching it, and leaving Source File selected.

To ensure that the XAML file is included correctly, right-click it in Solution Explorer, pick Properties, and change the Build Action to Resource. It is VERY IMPORTANT that this be done for EVERY *.xaml file, or the application will throw an exception at runtime while trying to utilize the associated control(s). It will not provide the wonderful compile-time errors and warnings that F# developers are used to.

Moving on, replace all of the text in the file with the following:

<Window
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="Testing"
    Height="200"
    Width="400"
    >
    <Canvas>
    </Canvas>
</Window>

The contents so far should be pretty easy to understand, so I'll gloss over them and stick to the F#-specifics. In the Solution Explorer, right-click Add Below -> New Item..., and name the new file MainForm.xaml.fs.

Based on the way F# handles values, if you're not already aware, F# code cannot reference values which have not been declared yet. By extension, this means that files have to be compiled in a certain order, else the compiler will reject code which references values from other un-compiled files. This is why Visual Studio gives us the option to arrange files in a different order with F#, offering the Add Above and Add Below options for new files.

Replace the contents of the new MainForm.xaml.fs with the following:

namespace FSharpWpfGuide.Views

type MainForm = FsXaml.XAML<"MainForm.xaml", true>

namespace FSharpWpfGuide.ViewModels

type MainFormViewModel() =
    member __.Title = "F# |> WPF Guide"

This new code uses the FsXaml.XAML type provider to generate a type definition named MainForm, which is based on the contents of MainForm.xaml. The second parameter, true in this case, indicates whether or not the type provider should generate properties for XAML nodes with an x:Name attribute; it's easiest to just assume this should always be true. In a different namespace, we then declare a simple type which contains a single value. There isn't much need for this, but it allows for our first data binding. Before proceeding, build the solution.

Back in MainForm.xaml, add the following:

  <Window
      xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation"
      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+     xmlns:viewmodels="clr-namespace:FSharpWpfGuide.ViewModels;assembly=FSharpWpfGuide"
-     Title="Testing"
+     Title="{Binding Title}"
      Height="200"
      Width="400"
      >
      <Window.DataContext>
          <viewmodels:MainFormViewModel />
      </Window.DataContext>
      <Canvas>
      </Canvas>
  </Window>

The additional xmlns attribute allows utilizing values from the second namespace used in MainForm.xaml.fs, the DataContext definition is added to allow nodes to bind their values to values from the type specified (in this case, the MainFormViewModel type), and the adjusted title attribute specifies that its value is to be bound to the Title property in the window's DataContext. Because the codebase was re-compiled after writing MainForm.xaml.fs, IntelliSense and the WPF designer should both know about the values specified there, and the title in the preview should show up after updating the XAML.

To ensure that the application will run correctly, there's one last change to make. In App.xaml, change the StartupUri attribute to "MainForm.xaml". The application should build and run with no problem now, although it's still a bit uninteresting. Hello, world!

Building a Data Model

Folders!

As mentioned previously, adding folders to an F# project can cause issues. The primary concern here is that for code in one file to reference code from another file, the "other" file must be compiled prior to compiling the "one file" that references it. Enjoy that headache.

Fortunately, with the MVVM pattern, there is more of a clear order to the files, where the View depends on the ViewModel, which depends on the Model; there should be no instances where a ViewModel depends on a View, nor a Model depending on a ViewModel. However, it does make perfect sense for a ViewModel or Model to reference other types at their level.

In the Solution Explorer, right-click the project and select F# Power Tools -> New Folder, and give it the name "Models". Later, it will be necessary to do this again for "ViewModels" and "Views". Next, use the F# Power Tools -> Move Folder Up option to put the folder at the top of the project file list.

Design Models?

To quickly explain the purposes of the three folders as they'll be used here:

  • Models hold data. In the MVVM pattern, they hold all business and data logic as well, although this doesn't always mesh well with F#, so I won't follow that aspect of MVVM at all.

  • ViewModels primarily exist to connect the View and Model together in MVVM. In this guide, most of the logic will reside here, resulting in something closer to the Model-View-Adapter pattern.

  • Views display content for the user and send commands to the ViewModel. Views really shouldn't be concerned with logic, with the most advanced logic here being to change some part of a layout based on content.

As mentioned, the way I'm organizing this doesn't perfectly match the MVVM pattern, nor the other well-known pattern, the MVC (Model-View-Controller) pattern; the important part about following any of these patterns is to attempt to separate data and how it behaves from the way that it's presented, such that UI problems are UI problems and code problems are just code problems. Whatever the exact results are, don't assume my way is the absolute best (although it definitely is, because I said so).

Now, MVVM can be strictly adhered to in F#, so why haven't I done so? Partly because I don't want to repeat as much code, and perfectly following the MVVM pattern means a bit more repeated code than I feel like writing.

Adding a Data Model

In the Models folder, add a file named Car.fs and paste the following code into it:

namespace FSharpWpfGuide.Models

type Make =
    | Ford
    | Chevy

type Car =
    {
        Make : Make
        Model : string
        Year : int
        Miles : int
    }

This super-short type definition for the Car type is half of the reason I'm avoiding strict MVVM adherence in F#; I'll explain why when I get to the ViewModels.

No longer assuming full familiarity with F#, the basic premise here is that a type named Make has been defined as being one of two cases: Ford or Chevy (sorta like an enum). In production code, it may be undesirable to do that for something like vehicle manufacturers, as the list may change. However, some things aren't likely to change much, and so defining a type like this makes life easier.

If it's not too much hassle to recompile later, using these "discriminated unions" as they're called would still be a good idea for manufactures, as unlike enums in C# or VB, F# tends to give compiler warnings anywhere that code hasn't been written to fulfill all possible cases it can think of. If special code is written for each manufacturer, adding a new one would automatically generate a bunch of warnings indicating which code needs to be updated!

The second type, Car, is defined as a "record", which basically means it has fields with names. A discriminated union can have properties as well, but they (usually) aren't named.

Fixing Compile Order

It won't be apparent right now, but Visual Studio has put the Car.fs file at the end of the compile listing. Unless that has been fixed since I wrote this, compiling the solution will now result in a build error complaining about the App.fs file, because it's not the "last" file anymore.

To fix this, right-click the project and select Unload Project, then right-click it again and select Edit FSharpWpfGuide.fspoj. Somewhere in the XML will be the following block:

<ItemGroup>
    <Resource Include="MainForm.xaml" />
    <Compile Include="MainForm.xaml.fs" />
    <Resource Includ="App.xaml" />
    <Compile Include="App.fs" />
    <Content Include="App.config" />
    <Content Include="packages.config" />
    <Compile Include="Models\Car.fs" />
<ItemGroup>

Just move that Models\Car line up above the MainForm.xaml line. After saving the file, right-click the project again and select Reload Project. If the file is still open, Visual Studio will complain; just say yes.

Note that project files use two spaces for indentation, so if you use a different number of spaces (or you're a heathen who uses tabs), it would be wise to fix the spacing after adjusting the file. Otherwise, Visual Studio will do it for you later. There isn't much else to say here except to be aware that the project file may need this kind of help again down the road. Welcome to folders in F# projects.

Adding a ViewModel

Go ahead and add a folder named "ViewModels" to the project, and move it up until it's directly below the "Models" folder, then add in a file named CarViewModel.fs. Given that this is the first file in the folder, it would be prudent to re-open the project file and make sure it was added in the correct order.

The contents of CarViewModel.fs will be a bit longer, so I'll go through it in sections.

namespace FSharpWpfGuide.ViewModels

open FSharpWpfGuide

type CarViewModel (vehicle : Models.Car) as self =
    inherit FSharp.ViewModule.ViewModelBase()

This first section is relatively harmless, defining a new class named CarViewModel which inherits from FSharp.ViewModule.ViewModelBase. The value in parentheses, vehicle, is where arguments to the default constructor go. If the class being inherited has any required parameters, they would go inside the parentheses following its name.

Last, the as self allows easy self-referencing from inside the type's body. This is akin to C#'s this keyboard, or VB's Me; in fact, replacing self with this or Me is entirely possible. I've chosen to use self simply because Everyone Else Is Doing It™.

    let mutable car = vehicle

This creates a mutable variable named car, and assigns it the value of vehicle. Mutable variables as A Bad Idea™ in general, and so functional programming tries to do away with them. However, we have a UI to build, and mutable state makes UIs much easier to work with. Sorry, F#.

    let milesToDrive = self.Factory.Backing(<@ self.MilesInput @>, 0)

The self identifier didn't take long to get used, did it? The ViewModelBase.Factory.Backing function used here takes what is known as a "quotation," a default value, and optionally some other values, and results in a NotifyingValue containing the default value. Whenever the contents of the value are updated, it automatically informs anyone listening that the specified property has changed. This provides the INotifyPropertyChanged usefulness with minimal work, and do to the quotation being actual code, renaming the property later will (assuming Visual Studio's refactoring tools are used)update this use of it.

    let drive () =
        let miles = milesToDrive.Value
        if (miles < 0) then invalidArg "miles" "Cannot drive a negative number of miles"
        let newMiles = miles + car.Miles
        cat <- { car with Miles = newMiles }
        self.RaisePropertyChanged <@ self.Miles @>

This function brings quite a few new things with it. Two parentheses together as () is known as the unit type, which is akin to void in C#, or similar to a Sub in VB. It isn't typically needed like this, but due to how this function is used later, this unit is specified as a parameter to indicate to code from outside F# that the function doesn't require any arguments. In most F# functions which take a unit as an argument, it simply indicates that the result of the function needs to be regenerated every time it's used, rather than caching the value somewhere in memory.

Next, invalidArg is a build-in F# tool that throws an ArgumentException. There are a handful of these exception-throwing functions built-in, and information on them and exceptions in F# is available here.

The <- operator updates a mutable variable (car in this case) and sets it to the value on the right. I mentioned earlier that I'd cover the choice to make Car a record type, and here it is: F# primarily deals with the mathematic concept of values, insomuch as that new values never replace old ones, and the contents of a value can never be changed. Of course this is a problem when one wishes to change one piece of a vehicle's data, and record types allow this syntax to do so. { car with Miles = newMiles } isn't actually changing the car's Miles value, it's telling F# to copy the car value and to use newMiles as the new car's Miles value. Still with me? While it's possible to use mutable values for each of the car's properties, as done for the entire car value, it's typically better and easier to follow F#'s concept of values as much as possible. Immutable unchanging values that simply get covered up by new values are much safer than constantly changing state.

Last, self.RaisePropertyChanged <@ self.Miles @> does the same thing that the milesToDrive value does automatically, and informs anyone listening that the Miles property has changed. This tells the View (which doesn't exist yet) that any bindings it has which reference that value need to be updated in the UI.

    let driveCommand =
        self.Factory.CommandSyncChecked(
            drive,
            (fun () -> milesToDrive.Value > 0),
            [<@ self.MilesInput @>]
            )

The CommandSyncChecked function creates a value which implements the ICommand interface, which means that driveCommand is now something that a XAML-defined button can use as its Command property. There are a handful of functions for synchronous vs asynchronous commands, with or without a parameter, and with or without a way to turn off the command. However, I've run into many issues trying to utilize the versions with parameters, so be aware that your results may vary if choosing to go that route.

(fun x -> ...) is the syntax for declaring a lambda, like (() => ...) in C# or Sub() ... in VB. This function is what the UI will call when it wants to know if it can use this command, and as with the drive function earlier, it needs to accept a unit value due to interop with C# and VB, and to indicate to other F# code that it has to be called every time and not cached.

Last, we provide a list of quotations mentioning which properties this command depends on. A list of two things in F# looks roughly like [ 1; 2 ]. In this case, since the contents of the milesToDrive value will be different after the MilesInput property has been changed, I've listed that property. Now, whenever the contents of milesToDrive are changed, the code from using self.Factory.Backing will tell the UI that the MilesInput property has changed, and the UI will automatically know that it needs to check this command as well.

    do
        self.DependencyTracker.AddPropertyDependency(
            <@ self.MilesInputText @>,
            <@ self.MilesInput @>
            )

Sometimes a class needs to do stuff when it's created. A do block is used to make that happen, since we haven't really defined a proper constructor yet for this class. We'll be creating a property named MilesInputText which just formats the contents of MilesInput for display. Since the value of the former will be different after the latter has changed, I let the DependencyTracker know that a change in the latter means a change in the former.

    new() =
        CarViewModel
            ({
                Make = Models.Ford;
                Model = "Mustang GT";
                Year = 2001;
                Miles = 10;
            })

To be used as a DataContext, classes must have a constructor with no parameters. Since the CarViewModel class needs some kind of starting value for its internal car value, this new() block is used to define another constructor which has no parameters, and just calls the other constructor.

More interestingly, this is the first example of the syntax for creating a value of a record type.

    member __.Make wth get () = car.Make
    member __.MakeStr
        with get () =
            match car.Make with
            | Models.Ford -> "Ford"
            | Models.Chevy -> "Chevy"
    member __.Model with get () = car.Model
    member __.Miles with get () = car.Miles
    member __.Drive with get () = driveCommand

    member __.MilesInput
        with get () = milesToDrive.Value
        and set value =
            milesToDrive.Value <- value
    member __.MilesInputText with get = sprintf "Drive %i miles" milesToDrive.Value

Last bit of code is the properties. Properties are defined with the member keyword, and can provide a get and/or set function as seen to match the read-only or write-only properties used in C# and VB. The exact layout is very flexible, so have fun (but not too much fun).

None of these properties need it, but the two underscores on each declaration can be replaced with an identifier, which then provides a reference back to the rest of the type. As with the self name at the beginning of this file (which does basically the same thing, but on a wider scope), words like this or Me can be used here. Although it's technically still possible to use it as a reference, two underscores are typically used like this whenever the get/set code doesn't need a reference to the rest of the type.

Adding a View

Now add a folder named "Views" and move it up to just below the "ViewModels" folder, and add files named CarView.xmal and CarView.xaml.fs. Make sure CarView.xaml's Build Action gets set to Resource, and then check the project file's build order again.

Replace the contents of CarView.xaml with the following:

<UserControl
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:viewmodels="clr-namespace:FSharpWpfGuide.ViewModels;assembly=FSharpWpfGuide"
    mc:Ignorable="d"
    d:DesignWidth="409"
    d:DesignHeight="92"
    >
    <Control.DataContext>
        <viewmodels:CarViewModel />
    </Control.DataContext>
    <StackPanel>
        <TextBox Text="{Binding MilesInput, UpdateSourceTrigger=PropertyChanged}" Name="MilesBox" />
        <Button Name="DriveButton" Command="{Binding Drive}" Content="{Binding MilesInputText}" />
        <Label Content="{Binding Miles}" />
    </StackPanel>
</UserControl>

Since the CarViewModel type was already defined in the last section, all of this XAML just works as-is. When it makes sense to do so, it can be very helpful to write the Model's code first, followed by the ViewModel, and leaving the View to the end. Of course working in that order isn't always possible.

This simple UI just displays a text box where the user can enter a number of miles, click a button, and update the number of miles on their Mustang. Note that the text box for entering miles has an UpdateSourceTrigger value in its binding; setting this to PropertyChanged on a TextBox element means that the property in the ViewModel will be updated on every keystroke, rather than just when focus leaves the box.

One more point of note here, the Text property on a TextBox control is a string, and the property it's being bound to here isn't. It wasn't specified anywhere in CarViewModel.fs, but the compiler automatically determined that the value of milesToDrive is an integer because the default value was zero. Based on that, the compiler also determines that the MilesInput property has to be an integer as well, because it's getter returns milesToDrive.Value, and its setter puts the provided value into milesToDrive.Value. What this means for the UI is that if the user enters 1a, the box will get a red outline because the underlying code for the UI encountered an exception trying to convert 1a to an integer. I won't go into detail on how to tell the user about that, but it's worth taking the time to do unless you completely trust your users.

Next, replace the contents of CarView.xaml.fs with the following:

namespace FSharpWpfGuide.Views

type CarView = FsXaml.XAML<"VIews/CarView.xaml", true>

As with MainForm.xaml.fs, the use of the FsXaml.XAML type provider is here to generate a type definition based on the contents of the *.xaml file. Unlike in MainForm.xaml.fs, this use is actually required! I'll wait to explain why, so as to build suspense.

Open MainForm.xaml and make the following changes:

  <Window
      xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
      xmlns:viewmodels="clr-namespace:FSharpWpfGuide.ViewModels;assembly=FSharpWpfGuide"
+     xmlns:views="clr-namespace:FSharpWpfGuide.Views;assembly=FSharpWpfGuide"
      Title="{Binding Title}"
      Height="200"
      Width="400"
      >
      <Window.DataContext>
          <viewmodels:MainFormViewModel />
      </Window.DataContext>
      <Canvas>
+         <views:CarView />
      </Canvas>
  </Window>

This change adds a CarView to MainForm.

In a VB or C# project with WPF, a bit of the extra work with XAML files is done automatically, but since WPF projects for F# aren't supported directly by Visual Studio, a type isn't generated for every *.xaml file in the project. To relieve the building suspense, what that means is that in a VB or C# project with WPF, rougly the same stuff that the FsXaml.XAML type provider built when called iin CarView.xaml.fs is normally built by Visual Studio. Without that line in CarView.xaml.fs, writing <views:CarView /> in MainForm.xaml would result in a compiler error, saying that it can't find the definition for CarView. Thank you, type provider!

Completion?

The project is now complete! The program allows a user to add miles to their virtual Mustang. Not terribly useful, but adding a save button or a load button wouldn't take much extra work. But wait, there's more!

Adding Another ViewModel

Back in the ViewModels folder, add a file named CarListViewModel.fs below CarViewModel.fs. When I did this, Visual Studio created the file at the bottom of the project, rather than in the folder specified; if this happens, right-click the file and use the F# Power Tools -> Move To Folder option to fix this.

Replace the contents of CarListViewModel.fs with the following:

namespace FSharpWpfGuide.ViewModels

open FSharpWpfGuide

type CarListViewModel() =
    inherit FSharp.ViewModule.ViewModelBase()

    let cars = System.Collections.ObjectModel.ObservableCollection<CarViewModel>()

    do
        cars.Add(new CarViewModel())
        cars.Add(new CarViewModel({ Make = Models.Chevy; Model = "Camaro ZL1"; Year = 2015; Miles = 10; }))


    member __.Cars with get () = cars

Not a lot to talk about here, so I'll focus on the new type being used, the ObservableCollection<T>. This collection type gives us a way to define a collection that can be watched for changes, which allows WPF elements such as the ListView control to use the collection as a source for list display. Adding or removing things from the collection automatically feeds information back to the UI.

Adding Another View

In the Views folder, add a file named CarListView.xaml and another named CarListView.xaml.fs. As before, adding directly to the folder will likely result in them showing up at the bottom of the project, at which point they'll have to be moved with that F# Power Tools option.

For the moment, replace the contents of CarListView.xaml with the following:

<UserControl
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:viewmodels="clr-namespace:FSharpWpfGuide.ViewModels;assembly=FSharpWpfGuide"
    mc:Ignorable="d"
    d:DesignWidth="200"
    d:DesignHeight="100"
    >
    <Control.DataContext>
        <viewmodels:CarListViewModel />
    </Control.DataContext>
    <StackPanel>
        <Button Content="Say Hi" x:Name="TestButton" />
        <ListView ItemsSource="{Binding Cars}" x:Name="CarList">
            <ListView.ItemTemplate>
                <DataTemplate>
                    <StackPanel Orientation="Horizontal">
                        <Label Content="{Binding MakeStr}" />
                        <Label Content="{Binding Model}" />
                    </StackPanel>
                </DataTemplate>
            </ListView.ItemTemplate>
        </ListView>
    </StackPanel>
</UserControl>

Notice that the ListView used here binds its ItemsSource to the Cars property from CarListViewModel.

Next, replace the contents of CarListView.xaml.fs with the following:

namespace FSharpWpfGuide.Views

type CarListView = FsXaml.XAML<"Views/CarListView.xaml", true>

Terribly exciting as usual, but now we have a form with a button that doesn't do anything. In general, it's preferred to use the ICommand stuff from before (the Drive property from CarViewModel.fs), but for the sake of showing off more stuff, I'll use the code-behind for something resembling the common coding style frequently seen with WinForms.

Adding a Controller

In the CarListView.xaml.fs file, add the following type definition:

type CarListViewController() =
    inherit FsXaml.UserControlViewController<CarListView>()

    let buttonClick (parent : CarListView) _ =
        let msg txt = System.Windows.MessageBox.Show(txt) |> ignore
        match parent.CarList.SelectedItem with
        | :? FSharpWpfGuide.ViewModels.CarViewModel as x ->
            sprintf "Hello. You've selected the %s %s" x.MakeStr x.Model
            |> msg
        | _ -> msg "Hi. You haven't selected a car yet."

    override self.OnInitialized view =
        view.TestButton.Click.Subscribe (buttonClick view) |> self.DisposeOnUnload

Suddenly there's a lot going on in a small space again. This new CarListViewController type inherits the UserControlViewController<T> type, which provides a short list of helper functions we can override, OnInitialized, OnLoaded, and OnUnloaded, as well as the DisposeOnUnload function. Although they don't provide a full way of linking a button's click event to an event handler as easily as we can in VB/C#, they do provide a place to manually do so.

Because the button and textbox defined in CarListView.xaml both have x:Name attributes, they can be referenced whenever an instance of the parent CarListView value is available. Thus, in the OnInitialized function, view.TestButton.Click.Subscribe allows us to add an event handler to the TestButton object's Click event.

Since the buttonClick function needs a reference back to the View to find out what the selected car is, I've used a functional-programming feature known as "currying." Notice that where the function is used, I've passed it view as an argument, but the function takes two parameters, not one! Through some background magic in F#, this is a normal thing, and a function that takes two parameters can be called with just one argument, and then result will actually be another function which only takes the other parameter. This primarily works with functions defined in F#, and doesn't work well with functions defined in VB/C#, but it's a useful feature to be aware of.

Next, calling the Subscribe function on an event like this returns a disposable object. As with external database connections or file handles, this object needs to be properly disposed of to avoid memory leaks, so we use |> self.DisposeOnUnload to make sure that it is disposed when the View unloads.

Moving on to the buttonClick function, I've used an underscore in two places that would normally have a variable name: in the function definition as a parameter, and later in the second cast of the match ... with block. An underscore in one of these places, as with the double-underscore on member definitions, is how we indicate that the actual value provided is unimportant to us; the main difference here is that when an underscore is used like this, F# will actually prevent us from utilizing the value, versus the double-underscore which still acted as a strange variable name. Sometimes to make things easier, F# developers will provide names for different values even though the value doesn't matter to them; in these cases it's preferred to start the name with an underscore still, to at least tell other developers that it is unused.

Next, I've used the match ... with block to pattern match against the selected item, but my first match case uses a type name and the :? operator, so that when the SelectedItem value is a CarViewModel, it will be stored temporarily in x (because I specified as x), and then the body of that match will run. If the user hasn't selected a vehicle yet, the SelectedItem value will be null, so the other match just accepts anything.

Wrapping Up

Head back into CarListView.xaml and apply the following change:

  <UserControl
      xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
      xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
      xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
      xmlns:viewmodels="clr-namespace:FSharpWpfGuide.ViewModels;assembly=FSharpWpfGuide"
+     xmlns:views="clr-namespace:FSharpWpfGuide.Views;assembly=FSharpWpfGuide"
+     xmlns:fsxaml="http://github.com/fsprojects/FsXaml"
+     fsxaml:ViewController.Custom="{x:Type views:CarListViewController}"
      mc:Ignorable="d"
      d:DesignWidth="200"
      d:DesignHeight="100"

The addition of the fscaml:ViewController.Custom value tells the type provider that the CarListViewController type should be the controller used for this XAML element.

Next, in MainForm.xaml, change <views:CarView /> to <views:CarListView />, and give the program a go. The window should now come up and display a short list of the two vehicles we put in, and a button that when clicked, either tells the user that no vehicle has been selected, or informs the user of the make and model. Still not a terribly useful application, but this should be enough examples to get started.

That's all I'm going to cover here, since again, this should be enough information to get started on useful projects. The resulting source for this tutorial is available on GitHub.