Christmas Trees in WPF, 2017 Update
Last year, I wrote about making Christmas Trees in WPF using Gjallarhorn. The Gjallarhorn project has improved dramatically since last year, and I thought I’d update the project, demonstrating the newer API and significant improvements in usability and ease of development. I recommend skimming through the previous post, as this post expands on the information there.
This post will modernize the Christmas Tree application from last year, improving it, and highlighting the changes in Gjallarhorn, showing the dramatic improvements inspired by great projects like Elmish. My hope is that more developers will see the benefits of unidirectional architecture for UI development, and try using Elmish for web, Gjallarhorn for XAML, or other unidirectional, functional approaches to user interfaces.
Over the last year, Gjallarhorn has matured greatly. Earlier this year, the project reached the version 1.0 milestone, and the user interface framework was split off into it’s own project – Gjallarhorn.Bindable. Gjallarhorn.Bindable has taken inspiration from Elmish, and developed a newer, simpler API, which adds significant benefits.
The new API has been driven with a few distinct goals in mind. First off, we wanted to simplify the code required. We also wanted to improve type safety and eliminate magic strings whenever possible. Finally, many new scenarios have been enabled via the new navigation API, which allows applications to cleanly work with multiple windows, views, and more.
That being said, the model and message types, and update function, are completely unchanged. The changes all relate to the binding components and application framework setup.
The first major change in migrating to the new API is the addition of a “view model” type. This is a simple type (currently typically a record) used for multiple purposes – it enables design time data for the XAML designer (with type and error checking, intellisense, and more when using Resharper), it eliminates the magic strings in code, and it adds type checking to the binding generation. The “View Model” type is simply a record with one field per binding, used to create an instantiated binding of that record type. For example, our Tree binding from last year is now augmented with the following “VM” type and binding:
The VmCmd type here is provided by Gjallarhorn.Bindable, and allows you to represent a typed message which will be sent back to the update function. Any command bound to the view will be represented this way.
One side note: the current design for the VM was created with the proposed anonymous records kept in mind. If implemented as currently proposed and approved in principle, anonymous records will, in most cases, eliminate the need to create the VM as a type – a simple binding with an anonymous record will work, making this a one-line addition over the previous API.
Once the VM binding exists, it can be used via the new Gjallarhorn API to create our binding much simpler than the previous mechanism. Previously, the tree component was created like so:
With the new VM and new API, this is significantly simpler:
Note that the magic strings used before have been replaced with quotations. In addition, the types defined in the VM are used to type check the bindings directly. We also no longer need to worry or think about the internal types in Gjallarhorn – the BindingSource and ISignal types are eliminated, and we directly specify a mapping from the model type to the bound value type as a simple function (in this case, id, as we’re binding the model directly).
(The unit generic type here refers to the Navigation types – as we are not enabling any navigation messages, I’m passing unit through this everywhere, to effectively ignore the navigation options.)
Our Forest component gets the same treatment:
In addition, the main “Application” components have been simplified. In last year’s version, we managed state ourselves. An AsyncMutable<‘a> from Gjallarhorn was manually created to hold the application state, and an initialization function started an infinite loop to externally update the model state as needed.
The new API simplifies this. The application now takes an initial state, and external updates are now managed via formalized patterns and types defined in the framework. For our pruning routine, instead of directly manipulating the global model, we now create a function which can produce and dispatch messages via a Dispatch<‘a> type passed in, which is simply a function that’s ‘Msg -> unit.
With this, we can create an “Executor” – which is a Gjallarhorn type which can be used to start and stop asynchronous, external (unrelated to the UI) operations. The application framework is dramatically simplified – instead of managing state, we just pass in the initial state, update, view component, and navigation dispatcher (unused in this application). We then attach our executor, and the framework takes care of all of the state management in a thread safe, clean manner:
Not only is this far simpler and more concise (7 lines of code vs 25), it eliminates most of the major potential for mistakes. The focus moves away from working with types defined in Gjallarhorn (mutables and signals) to focusing on the model, message, and update function. External operations can be attached easily, in a predictable manner. The same pattern also allows for logging and other scenarios to be managed in a formalized, simple manner.
The final change required to update this application is in the WPF project. Gjallarhorn.Bindable now supports navigation, which requires some minor changes. Our new entry point function is now:
For this application, we’re working in a single view in an single application, so we’re using the predefined “singleView” navigation type, and unit for the navigation message type. However, the framework supports other navigation types, including single page apps, and fully extendable, customizable navigation enabling multiple windows, each with multiple views, and other scenarios previously difficult to manage with unidirectional architectures.
When run, the new updated version produces the exact same runtime behavior as last year, but now our code is simpler, type safe, and focused more on our domain types and less on the framework enabling the user interface.
Please feel free to clone the repository, try the application, and play with the new and improved framework. Gjallarhorn is still evolving, but now provides a clean, cross platform, unidirectional approach to developing programs based on XAML platforms.
I also recommend reading the other 2017 F# Advent posts being written by the amazing F# community!
Cool post, Reed!
XAML platforms seem bright for 2018.
I was searching a way of how to use F# + XAML (WPF, for example) then our famous bookwriter Scott Wlaschin told me to give a look on your library.
I would like to see a Dummy blog posting, entry level:
I did not find a easy blog posting showing how to do
MVVM (or MVC) in XAML (WPF / UWP / Xamarin.Forms) using F#
For example: Invoice, Customer, Invoice Items, Total.
In C# we used a lot
INotifyPropertyChanged,
ObservableCollection,
In F# it tends to be
InvoiceItem list
And it is immutable, so every change returns a new object instance.
How it should be done in F# ?
Tony,
There are actually quite a few options. If you look through the 3 years of “Christmas Tree” articles, I show an MVVM approach, the older style unidirectional, and the latest shows the “current” unidirectional approach (my favorite) that I currently use. In general, I don’t really recommend following the C# approach – there are options in F# that are really, in my opinion, far superior to using MVVM or MVC, and which work in WPF, Xamarin, and even web targeting.
However, this is a large topic – I really recommend asking on the F# Software Foundation’s Slack team (you can join the Foundation and automatically get an invite if you’re not a member already). Would be happy to have a detailed conversation with you (or anybody) about the pros and cons of the various approaches.
-Reed
Downloaded the code, built it with VS2017 and it runs like a charm.
However, the XAML designer throws an error and cannot show the user interface.
Any tips how to solve this?
MissingMethodException: Method not found: ‘Void FsXaml.EventArgsConverter`2..ctor(Microsoft.FSharp.Core.FSharpFunc`2, !1)’.
I’ve seen this at times, and not sure why. There’s nothing that should prevent it from working, but I’ve had mixed results with the XAML designer in VS 2017 when using converters. I think it’s a problem with FSharp.Core mismatches when the designer loads the assembly.