In a previous question I asked how to idiomatically implement an observer pattern for an F# application. My application now uses a MailboxProcessor as reccomended and I've created some helper functions to create sub-MailboxProcessor's etc. However, I'm at a mental block when it comes to specific case scenarios w.r.t. GUI binding.
Lets say I have a model as such:
type Document = {
Contents : seq<DocumentObject>
}
And the GUI (WPF, XAML) requires binding like so:
interface IMainWindowViewModel
{
IEnumerable<Control> ContentViews { get; }
}
Each ViewModel
for each Control
will require a DocumentObject
(its underlying model) and a way of knowing if it has changed. I supply this as a sub-MailboxProcessor<DocumentObject>
so that changes may be propagated correctly, I'm moderately confident this pattern works. Essentially, it maps the service outputs and wraps modification requests (outer interface example below):
let subSvc = generateSubSvc svc (fun doc -> doc.Contents[0]) (fun f -> fun oldDoc -> { oldDoc with Contents[0] = f Contents[0] })
let viewModel = new SomeDocObjViewModel(docObjSvc)
new DocObjView(viewModel)
Now, imagine a modification command now deletes a DocumentObject
from MyDocument
. The top-level MailboxProcessor
now echoes the change to IMainWindowViewModel
using it's IEvent<MyDocument>
. And here's where my problems begin.
My IMainWindowViewModel
doesn't really know which DocumentObject
has been deleted. Only that there's a new Document
and it has to deal with it. There may be ways of it figuring out but it never really knows directly. This can force me down the path of having to either re-create all the Control
's for all DocumentObject
's to be safe (inefficient). There are additional problems (such as dangling subSvc
's) which I also haven't mentioned here for brevity.
Normally, these kind of dynamic changes would be dealt with something like an ObservableCollection<DocumentObject>
which is then mapped into an ObservableCollection<Control>
. This comes with all the caveats of shared mutable state and is a little 'hackish'; however, it does do the job.
Ideally, I'd like a 'pure' model, free from the trappings of PropertyChanged
and ObservableCollections
, what kind of patterns in F# would satisfy this need? Where is it appropriate to draw the line between idiomatic and realistic?
Have you considered using the Reactive Extensions (and Reactive UI further down the road) for the purpose of modelling mutable state (read: your model properties over time) in a functional way?
I don't see anything wrong technically to use an ObservableCollection
in your model. After all, you need to track collection changes. You could do it on your own, but it looks like you can save yourself a lot of trouble reinventing the observable collection, unless you have a very specific reason to avoid the ObservableCollection
class.
Also, using MailboxProcessor
seems a bit overkill, since you could just use a Subject
(from Rx) to publish and expose it as an IObservable
to subscribe to 'messages':
type TheModel() =
let charactersCountSubject = new Subject()
let downloadDocument (* ... *) = async {
let! text = // ...
charactersCountSubject.OnNext(text.Length)
}
member val CharactersCount = charactersCountSubject.AsObservable() with get
type TheViewModel(model : TheModel) =
// ...
member val IsTooManyCharacters = model.CharactersCount.Select((>) 42)
Of course since we're talking about WPF, the view-model should implement INPC
. There are different approaches, but whichever one you take, the ReactiveUI
has a lot of convenient tools.
For example the CreateDerivedCollection
extension method that solves one of the problems you've mentioned:
documents.CreateDerivedCollection(fun x -> (* ... map Document to Control ... *))
This will take your documents
observable collection, and make another observable collection out of it (actually a ReactiveCollection
) that will have documents mapped to controls.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With