In this article, we’ll take an F# class library and build a graphical user interface for it. This library was previously designed to be called from an F# class library. Since we can’t currently use F# in a WPF application out of the box, we’re going to explore how C# can work with F# code.
This article stands on its own, but is also part 4 in an ongoing series on building a genetic algorithm in F#. Previous entries are listed below:
- Creating a F# Console Application
- F# Squirrel Brains: Adding Actors and Getting Functional
- F# Unit Testing — Refining the Squirrel Simulation
In this article we’ll cover:
- Creating a WPF Core application in .NET 3.0
- Calling F# code from C# code
By the end of the article we’ll have a working WPF app that provides a graphical version of the console application from previous entries.
What is WPF and how does it work?
WPF was created as the successor to Windows Forms. At its center is XAML or eXtensible Application Markup Language. XAML is UI markup that binds to an underlying view model. This powerful language gives WPF its flexibility and power.
The view model is what ties the UI (or view) to the application’s logic and the logic to the data (or model). This follows the Model / View / ViewModel pattern, or MVVM for short.
In MVVM, views are responsible for the static presentation of data and binding to the view model. The view model is responsible for executing commands invoked by the view and adapting the model into a format that is easy to use for the view. The view model is also responsible for telling the view when data changes via property changed notifications.
While WPF has now been around for awhile, it only recently became part of .NET Core with the release of .NET Core 3.
Creating the WPF Application
This article assumes that you’re starting from the article4start tag on my GitHub repository, but it also works as a loose guide if you have an existing solution with an F# class library.
Click Next, then name your project and click Create.
Once your project is created, we’ll need to set the WPF Core app as our startup project by right clicking on it and selecting
Set as StartUp Project as pictured below.
This will launch the application when you click
Start without Debugging (my preferred way).
Go ahead and start the application now and you should see an empty application.
Referencing the F# Library
Once your project is created, right click on it in the solution explorer and select
Next, select your F# library project and click OK.
We’ll now be able to reference F# functions from C# classes in our WPF Core app.
Next we’re going to make our first view model and wire up the user interface to display a text-based grid. This grid consists of objects defined at the simulation layer in the F# class library.
In a moment we’ll embrace WPF and make this look nice, but for the time being let’s just display some text on the screen to demonstrate that the connection is working.
Building the ViewModel
Create a new class called
MainViewModel and paste in the following code:
This is a fairly simple ViewModel that doesn’t even have property changed notifications. It creates a new
World instance in its constructor.
At line 15 we declare a public get property named TextGrid. This property is what we’ll bind to in the user interface. Because complex logic should be done in methods and not property getters, I call BuildAsciiGrid from the property getter. Technically speaking, I shouldn’t even be doing that type of method invoke here, but we’ll change the implementation in a bit to something more standard.
BuildAsciiGrid is able to work with properties on the
World type as if they were on any other object, and is able to call out to functions in the
World module by name.
Next, we’ll go into the MainWindow.xaml.cs file and modify its constructor to set its
DataContext to the ViewModel like so:
DataContext is essentially the binding context inside of the XAML file. While you can change an element’s DataContext, by default it will inherit the parent’s DataContext and so, we’re going to set the entire Window’s DataContext to our object.
Binding the UI in XAML
Finally, let’s modify MainWindow.xaml to update the UI to make use of the new context:
This XAML markup is a flavor of XML that WPF uses to construct the user interface. It is intended to be a designer and tool accessible file, much like a HTML document for the web.
At the top you notice a number of
xmlns declarations to define various XML Namespaces. The one to highlight here is xmlns:local on line 6 as this is a line I added in order to refer to our view model by class later on in line 12.
Line 12 declares the design time DataContext for the Window, which helps with code completion. Note that the
d: prefix references the blend/2008 namespace definition on line 4.
Line 14 is really what does everything for us. We have a TextBlock that displays some text on the screen. We set some basic font properties for display purposes, and then on line 16 we bind the TextBlock’s Text to the ViewModel’s
Now when we run the application we get a static view of the simulation:
Connecting the WPF Core App to our Logic
Okay, so we have a weird-looking grid layout. So what? In our console application we could at least control the squirrel.
Let’s add some UI controls and start making this a bit more polished.
We’re going to start simply by adding a Restart button.
In order to support changes to the UI, we’re going to need to raise property changed notifications. In order to do this, let’s add a new
WPF looks for implementations of
INotifyPropertyChanged and subscribes to
PropertyChanged event invocations in order to update the user interface. It expects either the name of the property on the ViewModel that changed, or an empty string to indicate that all properties have changed.
We’ll see how we invoke this in a moment, after we introduce our command object.
In WPF you can handle click events manually, but it’s considered somewhat of a bad design practice. It’s much more standard to create commands that can be bound directly to the user interface. This way commands can be shared between related controls and the view layer is not responsible for things like click handlers.
Here’s our simple command:
The most critical bits of this class definition are lines 5 where we accept in an
Action into the constructor and store it, and line 12 where we invoke the
Action when the command is executed.
Putting it All Together
Now lets see how these two techniques come together to serve up a responsive UI. We’ll start at with the XAML.
This is a bit more complex now. Note that we’re defining a style we can apply to buttons and using more complex layout controls like DockPanel and StackPanel. This article won’t cover how those panels function, as UI design isn’t our focus here, but know that these are panels that have different layout strategies for their child elements.
Notice that in line 30 we’re now binding the button’s Command to a property on our ViewModel called ResetCommand. We’ll see what that is in a minute.
Okay, now that we’ve seen the view changes, let’s take a look at the view model changes:
First of all, note that we now inherit from NotifyPropertyChangedBase so we can fire off property changed notifications.
Secondly, instead of relying on a World instance, we now hold a GameState instance, which is what the F# library uses internally to hold the full session state.
Third, notice the ResetCommand. We define that as an
ActionCommand and the
Action it takes in its constructor is the
Reset() method. This is a shortcut for the full syntax of
() => Reset().
The property setter for State is interesting in that it fires off notifications for a few different properties. This means that when the state changes, WPF Core will re-evaluate anything bound on those properties, making UI refreshes efficient.
When you put it all together, you get something that updates the UI with a new layout when reset is clicked:
Cleaning up F# Enums
Before we go on, I want to point out an oddity I noticed in working with a F# type.
I had defined the following faux-enum in F#:
type SimulationState = Simulating | Won | Lost
Seems pretty simple.
SimulationState is either going to be
Lost. While technically an F# discriminated union, this is effectively an enum. At least that's what I thought.
If I instead define
type SimulationState = Simulating = 0 | Won = 1 | Lost = 2, I can use it in a normal switch case or switch expression like follows:
The downside here is that my F# code now always has to qualify enum values with their type name. For example, any instance of
Lost before in F# code now appears as
Now that we’ve gotten the basics of WPF UI, let’s add some more commands around player movement.
In order to support 9 similar movement commands (including wait) without adding a lot more code to the view model, let’s take advantage of
CommandParameters in WPF Core. These parameters are passed into the
ICommand instance and can modify how commands behave.
Let’s take a look at how these are used from XAML:
Here our 3×3 grid of movement commands all take in a command parameter that is different, but all point to the same movement command.
MoveCommand interprets the parameter and switches on possible values, then maps each to a corresponding value to send on to the F# class library:
This is largely nothing new, though we have a new
direction parameter. Note that with some of this syntax such as
??= and the switch expressions, I am using C# 8 language features.
In order to support the
direction property, we now need to be able to specify that an
ActionCommand either works with an
Action or an
Action<T> - that is to say, a version that doesn't need parameters and a version that does. This is accomplished by adding a new constructor to
ActionCommand as follows:
And now, when we run everything, we get some semblance of our console application inside of WPF (The player controls the S):
Dressing up a Squirrel
Okay, so we’re now at functional parity with where we were before, but the game still looks ugly. Let’s clean it up slightly for entirely cosmetic purposes.
I’m going to start by finding a number of images for actors in the game world as well as a background image for grass. I’ll then paste them into the WPF Core project directory.
The images should show up in Visual Studio and you will then be able to click on them in solution explorer and view their properties. Make sure each image has its Build Action property set to Resource as pictured below. This will allow WPF to embed it into the application at runtime.
Next let’s look at how these images will be rendered on the screen. We’ll replace the grid containing the ASCII text with the following XAML:
First of all, we start with an image background bound to the grass texture. This will help with overall theming.
Secondly, we introduce a ViewBox hosting a Border hosting an ItemsControl. We’ll talk about the former to in a bit, so let’s focus on that ItemsControl.
The ItemsControl is a built-in control for rendering a collection of objects. In our case, we want to render images for each actor, and we want each actor to appear in the appropriate spot.
In order to do this, we have the ItemsControl use a Canvas for its layout, starting at line 13, instead of the built-in vertical StackPanel.
Next, we need to tell each
ContentPresenter that the
ItemsControl builds where to position itself on the canvas in order to present the content appropriately. We do this by styling the
ContentPresenter and binding the
Canvas.Top attached properties to properties on the ViewModel we're about to create.
Finally, we customize the presentation of the item by providing a custom ItemContainerTemplate and specifying an image with a border around it. The image’s content is bound to a property on the view model we’ll be creating in a moment.
Finally, to back up a moment, the viewbox and border at the top level help us see the boundaries of the world appropriately while letting the game world scale up and down as the application resizes, while not stretching abnormally.
We’ve talked about the view model for the individual actor. Let’s look a bit more at that now:
There’s not a lot in here that’s new.
Note that we’re computing the canvas position of the item by multiplying by a size of 10 pixels per item. This is consistent with the overall size of the canvas. The ViewBox actually scales the images up larger than this, so what’s really important is the proportion to the full ItemsControl width of 130. Since we have a 13×13 grid, 10 is appropriate.
A final note on this view model is that it has no property changed notifications since it works with a single piece of immutable state. My suspicion is that with F#’s preference for immutability, this may be a common practice when working with F# models.
Bringing Actors Together
So how are the
ActorViewModel instances handled?
The UI is bound to an object called an
ObservableCollection. This is a fancy type of collection that fires collection changed notifications so that WPF Core knows that items were added or removed.
ObservableCollection, typically you bind the UI to the same collection and then modify the collection at will. This is what we're doing here.
State changes, we clear out the collection of Actors and then add in new
ActorViewModel instances for each active (non-dead) actor inside the game world.
Ultimately, we were able to take our console application and port it over to WPF Core fairly easily and add visual polish in the process.
The finished result from this article is on GitHub in the article4 branch if you want to play more with the application or see how it works in more detail.
Ultimately, F# and C# can talk together fairly easily, though there are a few minor bumps and hurdles to overcome.
It’s also remarkably easy to get started in WPF Core, and XAML remains a very strong language for application development.
The squirrel application is not yet complete. While the core simulation logic and WPF Core UI are ready, we haven’t even started playing with artificial intelligence.
Next up, we’re going to start exploring genetic algorithms and fitness functions and see how they can be applied to teach the computer how to train a squirrel brain to win the game as easily as a human player can.
Originally published at https://killalldefects.com on October 24, 2019.