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)