My ViewModel in F#
I'm trying to use F# instead of C# to implement my ViewModel. I'm following this article (btw, is there something newer or any better suggestion?).
So let's say that I have my view model base implementation (MVVM.ViewModel
, it's in C# but I can reference it from F#) and a simple Status
property.
namespace FuncViewModel
open MVVM.ViewModel
open System
type MyFuncViewModel() =
inherit ViewModelBase()
let mutable status=""
member this.RunSetStatus() =
status <- "Reset @" + DateTime.Now.ToString "yy.MM.dd hh:mm:ss"
base.OnPropertyChanged("Status")
member this.SetStatus = new DelegateCommand(fun _ -> this.RunSetStatus() )
member this.Status
with get() =
status
and set(value) =
status <- value
base.OnPropertyChanged(fun () -> this.Status)
Everything works as expected, so far so good (but let me know if you spot any conceptual error or if you find a more idiomatic version for the above code)
Introducing the async/await pattern
This is where I'm going wrong: I know how to do that in C# but I'm not good at it in F#.
I've tried with the following.
member this.RunSetStatus() =
status <- "Start resetting @" + DateTime.Now.ToString "yy.MM.dd hh:mm:ss"
base.OnPropertyChanged("Status")
let task = async {
do! Async.Sleep (30 * 1000)
}
Async.StartImmediate(task)
status <- "Reset done @" + DateTime.Now.ToString "yy.MM.dd hh:mm:ss"
base.OnPropertyChanged("Status")
The problem is that - when I run the full WPF application - I can't see the expected delay: the final status goes straight to the output.
If I change the above Async.StartImmediate(task)
to Async.RunSynchronously(task)
, of course I see the delay in progress, but the application gets freezed, so this is not what I want.
If I rearrange it as
member this.RunSetStatus() =
status <- "Start resetting @" + DateTime.Now.ToString "yy.MM.dd hh:mm:ss"
base.OnPropertyChanged("Status")
let task = async {
do! Async.Sleep (30 * 1000)
status <- "Reset done @" + DateTime.Now.ToString "yy.MM.dd hh:mm:ss"
base.OnPropertyChanged("Status")
}
Async.StartImmediate(task)
I get an error
The member or object constructor 'OnPropertyChanged' is not accessible. Private members may only be accessed from within the declaring type. Protected members may only be accessed from an extending type and cannot be accessed from inner lambda expressions.
Edit (Continuation)
Finally, I've also tried this
member this.RunSetStatus() =
status <- "Start resetting @" + DateTime.Now.ToString "yy.MM.dd hh:mm:ss"
base.OnPropertyChanged("Status")
let task = async {
do! Async.Sleep (30 * 1000)
}
Async.StartWithContinuations(task,
(fun _ -> this.Status <- "Reset done @" + DateTime.Now.ToString "yy.MM.dd hh:mm:ss"),
(fun _ -> this.Status <- "Operation failed."),
(fun _ -> this.Status <- "Operation canceled."))
but the application crashes with an ArgumentException
Stack Trace
Application: MyFuncWPF.exe Framework Version: v4.0.30319 Description: The process was terminated due to an unhandled exception. Exception Info: System.ArgumentException Stack: at MVVM.ViewModel.ViewModelBase.OnPropertyChanged[System.__Canon, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089] at FuncViewModel.MyFuncViewModel.set_Status(System.String) at [email protected](Microsoft.FSharp.Core.Unit) at Microsoft.FSharp.Control.CancellationTokenOps+StartWithContinuations@1274[[System.__Canon, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]].Invoke(System.__Canon) at .$Control.loop@430-52(Microsoft.FSharp.Control.Trampoline, Microsoft.FSharp.Core.FSharpFunc
2<Microsoft.FSharp.Core.Unit,Microsoft.FSharp.Control.FakeUnitValue>) at Microsoft.FSharp.Control.Trampoline.ExecuteAction(Microsoft.FSharp.Core.FSharpFunc
2) at [email protected](System.Object) at System.Windows.Threading.ExceptionWrapper.InternalRealCall(System.Delegate, System.Object
Edit 2 - found the issue
I had to use the following - more simple - overload of OnPropertyChanged
(they are both implemented and working in C# as per this source code)
member this.Status
with get() =
status
and set(value) =
status <- value
base.OnPropertyChanged("Status")
The reason of the exception is that the function of F# cannot be represented as
MemberExpression
:In the debugger you'll see that the exception you get is actually - "The body must be a member expression".
Your first code :
works since you are not using the setter of property
Status
.So you need to use a different overload.