Do you write desktop apps? Do you love MVU? Elmish.WPF has you covered.
What Elmish.WPF is
According to Elmish.WPF's README,
Elmish.WPF is a production-ready library that allows you to write WPF apps with the robust, simple, well-known, and battle-tested MVU architecture, while still allowing you to use all your XAML knowledge and tooling to create UIs.
You write model and update code as you would for a regular Elmish application, but the view works differently. Rather than having a function that takes your model and dispatch function as input and gives a UI specification as output, in Elmish.WPF you instead have a function that sets up bindings to views defined in XAML.
Project structure
The XAML development experience is better with C# than F#. As a result, a typical Elmish.WPF project has a C# project for XAML and F# projects for everything else (model definitions, update code, bindings etc.). The standard way of doing things is to have a single F# project that the C# project refers to, but the library can handle alternative project structures.
Bindings
It's useful to know a little about how data binding in WPF works. Briefly, you have a target UI element with properties, which interact with properties on a binding source.
To better understand how bindings work, we'll take a look at the SingleCounter sample provided in the Elmish.WPF repo. Check out both the SingleCounter.Core project, which contains the core F# code, and the SingleCounter project, which contains the XAML.
Target Elements in the XAML project declare bindings from one of their properties to a property on the binding source:
<TextBlock Text="{Binding CounterValue, StringFormat='Counter value: {0}'}" Width="110" Margin="0,5,10,5" />
<Button Command="{Binding Decrement}" Content="-" Margin="0,5,10,5" Width="30" />
<Button Command="{Binding Increment}" Content="+" Margin="0,5,10,5" Width="30" />
<TextBlock Text="{Binding StepSize, StringFormat='Step size: {0}'}" Width="70" Margin="0,5,10,5" />
<Slider Value="{Binding StepSize}" TickFrequency="1" Maximum="10" Minimum="1" IsSnapToTickEnabled="True" Width="100" Margin="0,5,10,5" />
<Button Command="{Binding Reset}" Content="Reset" Margin="0,5,10,5" Width="50" />
For example, the Text
property on the first TextBlock
element is bound to the CounterValue
property on the binding source. The binding source is created in the bindings
function in the core project:
let bindings () : Binding<Model, Msg> list = [
"CounterValue" |> Binding.oneWay (fun m -> m.Count)
"Increment" |> Binding.cmd Increment
"Decrement" |> Binding.cmd Decrement
"StepSize" |> Binding.twoWay(
(fun m -> float m.StepSize),
int >> SetStepSize)
"Reset" |> Binding.cmdIf(Reset, canReset)
]
As you can see, there are multiple binding types available. Binding.oneWay
is for read-only properties, while Binding.twoWay
allows for properties that can be read from and written to. The Elmish.WPF tutorial has a section explaining the different binding types.
The rest of the F# code is standard Elmish:
type Model =
{ Count: int
StepSize: int }
type Msg =
| Increment
| Decrement
| SetStepSize of int
| Reset
let init =
{ Count = 0
StepSize = 1 }
let update msg m =
match msg with
| Increment -> { m with Count = m.Count + m.StepSize }
| Decrement -> { m with Count = m.Count - m.StepSize }
| SetStepSize x -> { m with StepSize = x }
| Reset -> init
Conclusion
Elmish.WPF is a great tool. It allows XAML developers to benefit from F# and MVU, and F# Elmish developers to use their skills to make desktop applications. Thanks to the contributors for providing another wonderful library to the F# community!
If you want to learn more about Elmish.WPF, check out the following resources:
- Matt Eland's getting started blog post
- The official tutorial
- The official samples