Reed Copsey, Jr. Thoughts on F#, C#, WPF, .NET, and programming for Scientific Visualization Home About Series Talks Contact Me Christmas Trees in WPF using FSharp.ViewModule Posted by Reed on Wednesday, December 9, 2015 · 9 Comments As my contribution to the F# Advent Calendar this year, I thought Iâ€™d write a bit about one approach I often use to create WPF user interfaces in a functional style – mixing MailboxProcessor with FSharp.ViewModule. This post will illustrate using a pair of MailboxProcessor instances mixed with FSharp.ViewModuleÂ toÂ construct a simple application. Â In the spirit of F# Advent, our application will be a small drawing application that allows us to place and decorate Christmas trees. In order to start, we need a way to represent a collection of trees.Â For this application, Iâ€™ve created a simple model, comprised of Tree and Forest records, with some functions for manipulating a Forest: type Location = { X: float; Y: float } type Tree = { Position : Location ; Height : float ; Decorated : bool } type Forest = { Trees : Tree list } with static member Empty with get() = { Trees = [] } member f.Add tree = { Trees = tree :: f.Trees } member f.Decorate tree = let existing = f.Trees let updated = existing |> List.except [ tree ] { Trees = { tree with Decorated = true } :: updated } member f.Prune max = let updated = if max < List.length f.Trees then f.Trees |> List.take max else f.Trees { Trees = updated } view raw Model.fs hosted with by GitHub In addition, I have a ForestManager module, whichÂ provides a mechanism to use MailboxProcessor to provide updates to a Forest. Â Discriminated unions provide the mechanisms forÂ specifyingÂ an update and receiving a result. Â In our case, an update can beÂ adding a new tree to a forest, or decorating an existing tree within a forest. Â As the entire Forest is immutable, either of these would need to create a new Forest. In order to request updates, as well as update our view, I’m using two MailboxProcessors. Â Our view creates a MailboxProcessor<ForestUpdateResult>, which is will use to receive notifications of new Forests. Â The ForestManager has a single function which accepts this processor, and createsÂ a secondary one which can be used to post updates. Â A single processor would work using PostAndReply, but the advantage of having two is that the model can request updates independently of the view layers, and all user events that require an update just post to a MailboxProcessor, whichÂ simplifies the viewÂ dramatically. The manager effectively just delegates the update requests to the Forest functions, and posts to the view’s Mailbox whenever an update needs to occur: type ForestUpdate = | Add of Tree * Forest | Decorate of Tree * Forest type ForestUpdateResult = | Success of Forest | Pruned of Forest | Error of string module ForestManager = let private update forest f (reporter : MailboxProcessor<ForestUpdateResult>) = let updated = f forest Success updated |> reporter.Post if List.length updated.Trees > 10 then updated.Prune 5 |> Pruned |> reporter.Post let createUpdateAgent reporter = let updater (inbox : MailboxProcessor<ForestUpdate>) = let rec loop() = async { let! forestUpdate = inbox.Receive() let f, forest = match forestUpdate with | Add(tree, forest) -> (fun _ -> forest.Add tree), forest | Decorate(tree, forest) -> (fun _ -> forest.Decorate tree), forest update forest f reporter do! loop() } loop() let result = new MailboxProcessor<ForestUpdate>(updater) result.Start() result view raw Manager.fs hosted with by GitHub While this is simple, in a more complicated application, the “manager” within the Model layer can very easily add more updates triggered by external sources, which providesÂ oneÂ advantage of having separate MailboxProcessors instead of using a single processorÂ via PostAndReply. Now that we have our Model layer defined, we need a way to interact with this from the View. Â This is where FSharp.ViewModuleÂ comes into play. Â For this application, my view model uses EventViewModelBaseÂ from FSharp.ViewModule. This allows us to react to user input as a stream of strongly typed events. The first thing we’ll need is to create our ViewModel and view Event types, and define a single property and backing field for the Forest we’re viewing. type TreeEvent = | Add of location:Location | Decorate of tree:Tree | Unknown type ForestViewModel () as self = inherit EventViewModelBase<TreeEvent>() // Create a backing field for our Forest using FSharp.ViewModule let forest = self.Factory.Backing(<@ self.Forest @>, Forest.Empty) // ... Other code... member __.Forest with get() = forest.Value view raw vm1.fs hosted with by GitHub This allowsÂ the current Forest, defaulting to an empty forest, to be bound by our view. Â All of the INotifyPropertyChanged plumbing gets handled internally by FSharp.ViewModule’s base classes for us. Next, we needÂ to create our MailboxProcessors. Â First, we need one that we can listen to in order to receive updates to our Forest. Â Once that’s created, we can request one to post updates to from the ForestManager module. Â Note that I’m capturing the SynchronizationContext and handling it within the agent – in this case, that’s actually unnecessary, but it makes the updates fully threadsafe with WPF if our Model were to post updates from multiple threads, even if we decided to change how we work with the Forest (such as keeping an ObservableCollection for history, which would require synchronization). let ui = SynchronizationContext.Current // Create an async update loop for our agent let update (inbox : MailboxProcessor<ForestUpdateResult>) = let rec loop() = async { let! update = inbox.Receive() match update with | Success updated -> do! Async.SwitchToContext ui forest.Value <- updated | Pruned updated -> // Wait brief period (so you see the tree added before pruning), then update us // Note: This creates a race condition if you click very fast do! Async.Sleep 250 do! Async.SwitchToContext ui forest.Value <- updated | Error _ -> // Handle error case here () do! loop() } loop() let reporter = new MailboxProcessor<_>(update) // Start our report handler do reporter.Start() // Create the agent used to update the model let updateAgent = ForestManager.createUpdateAgent reporter view raw vm2.fs hosted with by GitHub The reporter MailBoxProcessor we create listens for updates, and updates our current forest whenever an update occurs. Â We can handle different types if we wanted to post status updates to the user (which I’m currently not doing), and easily extend this to other types of updates or errors as needed. We pass the reporter to the Manager to create a second MailboxProcessor: updateAgent. Â This will beÂ then used within our event stream, and allow us to post update requests back to the Model. Â Each user event maps to one update request, and, from the ViewModel perspective, requests are completely decoupled from the updates to display back to the user: // Create a handler for our UI event stream let handleEvent event = match event with | Add(location) -> let height = 8.0 + rnd.NextDouble() * 4.0 updateAgent.Post <| ForestUpdate.Add ({ Position = location ; Height = height ; Decorated = false }, forest.Value) | Decorate(tree) -> updateAgent.Post <| ForestUpdate.Decorate (tree, forest.Value) | Unknown -> () do self.EventStream |> Observable.subscribe handleEvent |> ignore // Create an EventValueCommand for our UI to send us events member val MouseCommand = self.Factory.EventValueCommand() view raw vm3.fs hosted with by GitHub Once this is done, all of the logic for our program is complete. Â All that’s leftÂ is to wire up a user interface. Â FsXamlÂ provides some helpers which canÂ beÂ used to convert EventArgs into our TreeEvent discriminated union, so we create two converters, and use them via EventToCommand behaviors to push mouse clicks into our ViewModel. <ItemsControl Grid.Row="1" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" ItemsSource="{Binding Forest.Trees}" > <!-- One items control binding displays our trees.. --> <ItemsControl.ItemsPanel> <ItemsPanelTemplate> <Canvas HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Background="Gray" > <i:Interaction.Triggers> <i:EventTrigger EventName="MouseLeftButtonDown"> <fsx:EventToCommand Command="{Binding MouseCommand}" EventArgsConverter="{StaticResource addConverter}" /> </i:EventTrigger> </i:Interaction.Triggers> </Canvas> </ItemsPanelTemplate> </ItemsControl.ItemsPanel> <ItemsControl.ItemTemplate> <DataTemplate> <Canvas> <! -- Our template for a tree --> <i:Interaction.Triggers> <i:EventTrigger EventName="MouseLeftButtonDown"> <fsx:EventToCommand Command="{Binding DataContext.MouseCommand, ElementName=Win}" EventArgsConverter="{StaticResource decorateConverter}" /> </i:EventTrigger> </i:Interaction.Triggers> view raw view.xaml hosted with by GitHub The key portion is line 12 and 23 – by using our converter, we can map a MouseEventArgs instance when the user clicks into our strongly typed event, and route that directly to our VM. Once that’s done, if we run, we see: Clicking in the window should add a tree – and clicking on a tree will “decorate” it: Once we get more than 10 trees, adding another will prune the forest and remove trees,Â which illustrates how eventsÂ separate from the UI automatically populate through to the user interface automatically. All of the source code is available on GitHub atÂ https://github.com/ReedCopsey/AdventTrees. Â Please download, try it out, andÂ enjoy! And remember to enjoy the rest of theÂ F#-rich goodness that is the 2015 F# Advent! FILED UNDER ASYNC, F#, MVVM, UNCATEGORIZED, WPF · TAGGED WITH About Reed Reed Copsey, Jr. - http://www.reedcopsey.com - http://twitter.com/ReedCopsey Comments 9 Responses to “Christmas Trees in WPF using FSharp.ViewModule” Jay says: June 29, 2016 at 5:27 PM Hi Reed, would the use of “Async.AwaitObservable” be applicable here? Reply Reed says: June 29, 2016 at 9:55 PM Jay, I think you could write it using that, but it’s not part of the F# core library (it’s in FSharpX.Async), which would have meant bringing in another dependency. -Reed Reply Jay says: June 29, 2016 at 11:34 PM I see…thanks for this example, it brings together several aspects of F# very clearly. Reply Trackbacks Check out what others are saying about this post... F# Advent Calendar in English 2015 | Sergey Tihon's Blog says: December 9, 2015 at 11:01 PM […] Â Christmas Trees in WPF using FSharp.ViewModule […] Reply F# Weekly #50, 2015 | Sergey Tihon's Blog says: December 13, 2015 at 10:35 AM […] Christmas Trees in WPF using FSharp.ViewModuleÂ –Â Reed Copsey, Jr. […] Reply The week in .NET - 12/15/2015 - .NET Blog - Site Home - MSDN Blogs says: December 15, 2015 at 8:43 AM […] Christmas Trees in WPF using FSharp.ViewModule, by Reed Copsey, Jr.. […] Reply The week in .NET – 12/15/2015-ITå¤§é“ says: December 15, 2015 at 10:28 AM […] Christmas Trees in WPF using FSharp.ViewModule , by Reed Copsey, Jr.. […] Reply ì£¼ê°„ë‹·ë„· 2015ë…„ 12ì›” 15ì¼ - Korea Evangelist - Site Home - MSDN Blogs says: December 22, 2015 at 11:58 PM […] Reed Copsey, Jrì˜ Christmas Trees in WPF using FSharp.ViewModule […] Reply Christmas Trees in WPF, 2016 Edition : Reed Copsey, Jr. says: December 15, 2016 at 12:24 PM […] Christmas Trees in WPF using FSharp.ViewModule […] Reply Speak Your Mind Tell us what you're thinking... and oh, if you want a pic to show with your comment, go get a gravatar! Name (required) Mail (will not be published) (required) Website Search for: Categories Categories Select Category .NET (73) C# (67) F# (4) MVVM (15) WPF (19) Algorithms (34) Async (7) Parallelism (22) C Tech (1) C++ (3) Community (3) F# Software Foundation (1) Gjallarhorn (1) Performance (2) Professional (4) Uncategorized (5) Meta Log in Entries feed Comments feed WordPress.org Tags .NET .NET 4 .NET 4.5 Algorithms Async C# C# 4 C# 5 Charity Community F# F# Software Foundation FSSF GiveCamp IDisposable Interop MEF MVP MVVM Optimization Parallelism Performance PLINQ Programming Windows Forms WPF WPF Gjallarhorn F# StackOverflow Reed Copsey 530,914 ●7272 gold badges●11031103 silver badges Archives December 2020 December 2019 December 2018 December 2017 December 2016 December 2015 December 2014 October 2013 November 2011 October 2011 September 2011 August 2011 April 2011 January 2011 December 2010 October 2010 July 2010 April 2010 March 2010 February 2010 January 2010 December 2009 November 2009 October 2009 September 2009 August 2009 July 2009 May 2009 April 2009 March 2009 Twitter Feed META Log in Entries feed Comments feed WordPress.org CAREER C Tech RECENT POSTS F# Basics – Result<'a>: or How I Learned to Stop Worrying and Love the Bind F# Basics – From loops to folds 2018 Christmas Trees – Cross Platform Edition using Avalonia CATEGORIES .NET Algorithms Async C Tech C# C++ Community F# F# Software Foundation Gjallarhorn MVVM Parallelism Performance Professional Uncategorized WPF © 2021 REED COPSEY, JR. · SHADES OF BLUE THEME BY STUDIOPRESS · WORDPRESS · LOG IN