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
Learning Objectives
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 Debug
or 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 Add Reference...
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.
Hello Squirrel
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.
This is an F# type named World
defined inside of the MattEland.FSharpGeneticAlgorithm.Logic.World
module. This odd World.World
syntax is a strong reason to not name a type the same name as a module or namespace.
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 TextGrid
property.
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.
NotifyPropertyChanged Support
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 PropertyChangedBase
class:
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.
ActionCommands
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.
In order to support commands, let’s make a minimal ICommand
implementation that takes in an Action
and invokes it when executed. This command will be flexible and reusable and allow our view model to define a wide variety of commands without needing a lot of classes implementing ICommand
.
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.
Note that this command is set to always return true for CanExecute
. This is because we don’t need to disable buttons in this application. If you needed to disable and re-enable buttons, you could implement CanExecute
and then raise CanExecuteChanged
when the command should be re-evaluated.
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()
.
When Reset()
is invoked it talks to F# to construct a new GameState
instance with a new World
inside of it. This State is then pumped into the State property setter.
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 Simulating
, Won
, or Lost
. While technically an F# discriminated union, this is effectively an enum. At least that’s what I thought.
I was wrong. When I tried to switch on the SimState
property of State
, C# would not let me match on its values because it didn’t think that Won
was constant, for example.
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 SimulationState.Lost
.
Adding Movement
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.
Squirrel Views
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.Left
and 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.
Actor ViewModels
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.
The bulk of this class is spent in the ImagePath
getter. Here, ActorKind
is a discriminated union and so we can’t do the same sort of switch expressions we can on actual enums. Since we’re relying on the Squirrel’s hasAcorn property, we can’t convert ActorKind
to an enum.
Another interesting note here is how F# requires you use NewSquirrel()
with the value passed in on whether or not the squirrel should have an acorn. Again, this is due to defining Squirrel as Squirrel of hasAcorn:bool
in our F# ActorKind
definition.
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.
With an ObservableCollection
, typically you bind the UI to the same collection and then modify the collection at will. This is what we’re doing here.
When the 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.
End Result
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.
What’s Next?
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.
Stay tuned.
The post WPF Core with F# Libraries appeared first on Kill All Defects.
Top comments (0)