I’m trying to see how far I can go in implementing a WPF application purely in F# (and I don’t mean that third party or framework libraries must be F#, just my code). The application isn’t going to be massive or probably very complex, I’m just interested in finding the “pain points” of using F# and WPF.
In previous posts I’ve created the code to start-up an application and load the main window along with how I might assign a view model to the DataContext of the main window.
I now want to take care of the usual mundane tasks such as binding to a view model, handling property change events on the INotifyPropertyChanged interface and implementing commands using the ICommand interface.
Handling property changes and INotifyPropertyChanged
In my C# WPF projects I use a extension methods that both assign values (if a property has changed) and raises PropertyChanged events using Expression objects as opposed to “magic strings”, i.e.
public string Name
{
get { return name; }
set { this.RaiseAndSetIfChanged(x => x.Name, ref name, value); }
}
I wanted to have something similar in F#. Instead of extension methods, I went the base class route
type ViewModelBase() =
let propertyChanged = Event<_, _>()
interface INotifyPropertyChanged with
[<CLIEvent>]
member this.PropertyChanged = propertyChanged.Publish
member private this.OnPropertyChanged p = propertyChanged.Trigger(this, PropertyChangedEventArgs(p))
member this.RaisePropertyChanged (p : obj) =
match p with
| :? string as s ->
this.OnPropertyChanged s
| :? Expr as e ->
match propertyName e with
| Some(pi) ->
this.OnPropertyChanged pi
| None -> ()
| :? (string array) as a ->
a
|> Array.iter (fun propertyName -> this.RaisePropertyChanged propertyName)
| :? (Expr array) as a ->
a
|> Array.iter (fun propertyExpression -> this.RaisePropertyChanged propertyExpression)
| null ->
this.OnPropertyChanged null
| _ -> ()
member this.RaiseAndSetIfChanged ((p : obj), (backingField : 'b byref), newValue) =
assert (p <> null)
match EqualityComparer.Default.Equals(backingField, newValue) with
| true -> false
| false ->
backingField <- newValue
this.RaisePropertyChanged p
true
Where the propertyName functions is defined elsewhere as
let rec propertyName quotation =
match quotation with
| PropertyGet (_,propertyInfo,_) -> Some(propertyInfo.Name)
| Lambda (_,expr) -> propertyName expr
| _ -> None
Note: This function is documented Getting a Property Name as a String in F#
You’ll notice that whilst we can overload methods in an F# type, I’ve gone with pattern matching against types passed into the RaisePropertyChanged method. This just seemed tidier and allowed me to use the same function for passing a null as well (so binding on all properties should update).
This view model base class allows me to raise a property against a “magic string”, against a Expr or against an array of either (useful when I wanted to force multiple readonly properties to update).
The RaiseAndSetIfChanged function expects a non-null property p which can be a string or an Expr.
There might be a better way to do this in F#, but this is what I’ve come up with thus far.
So to use the above code in our view model we would write something like
type MyViewModel() =
inherit ViewModelBase()
let mutable name = ""
member this.Name with get() = name and
set(value) =
this.RaiseAndSetIfChanged (<@ fun (v : MyViewModel) -> v.Name @>, &name, value) |> ignore
The use of the Code Quotations in F# don’t look quite as good (or terse) as C# and I’m not sure if there’s a better way, but they’re still better than “magic strings”.
Commands
Next up, we need to handle ICommand. I want something akin to ActionCommand, so implemented
type Command(execute, canExecute) =
let canExecuteChanged = Event<_, _>()
interface ICommand with
[<CLIEvent>]
member this.CanExecuteChanged = canExecuteChanged.Publish
member this.CanExecute param = canExecute param
member this.Execute param = execute param
new(execute) =
Command(execute, (fun p -> true))
member this.RaiseCanExecuteChanged p = canExecuteChanged.Trigger(this, EventArgs.Empty)
and in use with have
type MyViewModel() =
inherit ViewModelBase()
let mutable name = ""
member this.Name with get() = name and
set(value) =
this.RaiseAndSetIfChanged (<@ fun (v : MyViewModel) -> v.Name @>, &name, value) |> ignore
member this.PressMe = Command(fun p -> this.Name <- "Hello " + this.Name)