3 people like it.

ViewModelBase with strongly typed PropertyChanged subscription

A base class for your view models with methods that take a code quotation of a property to subscribe to, and makes use of IObservable for strongly typed subscriptions to PropertyChanged for this property. You might want to use System.Reactive and insert `Observable.DistinctUntilChanged` after `Observable.map`.

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
14: 
15: 
16: 
17: 
18: 
19: 
20: 
21: 
22: 
23: 
24: 
25: 
26: 
27: 
28: 
29: 
30: 
31: 
32: 
33: 
34: 
35: 
36: 
37: 
38: 
39: 
40: 
41: 
42: 
43: 
44: 
45: 
46: 
47: 
48: 
49: 
50: 
51: 
52: 
53: 
54: 
55: 
56: 
57: 
58: 
59: 
60: 
61: 
62: 
63: 
64: 
65: 
66: 
67: 
68: 
69: 
70: 
71: 
72: 
open System
open System.ComponentModel
open Microsoft.FSharp.Reflection
open Microsoft.FSharp.Quotations
open Microsoft.FSharp.Quotations.Patterns

type ViewModelBase() =

  /// Evaluates an expression. From http://www.fssnip.net/h1
  let rec eval = function
    | Value (v, _) -> v
    | Coerce (e, _) -> eval e
    | NewObject (ci, args) -> ci.Invoke (evalAll args)
    | NewArray (t, args) -> 
        let array = Array.CreateInstance (t, args.Length) 
        args |> List.iteri (fun i arg -> array.SetValue (eval arg, i))
        box array
    | NewUnionCase (case, args) -> FSharpValue.MakeUnion (case, evalAll args)
    | NewRecord (t, args) -> FSharpValue.MakeRecord (t, evalAll args)
    | NewTuple args ->
        let t = FSharpType.MakeTupleType [| for arg in args -> arg.Type |]
        FSharpValue.MakeTuple (evalAll args, t)
    | FieldGet (Some (Value (v, _)), fi) -> fi.GetValue v
    | PropertyGet (None, pi, args) -> pi.GetValue (null, evalAll args)
    | PropertyGet (Some x, pi, args) -> pi.GetValue (eval x, evalAll args)
    | Call (None, mi, args) -> mi.Invoke (null, evalAll args)
    | Call (Some x, mi, args) -> mi.Invoke (eval x, evalAll args)
    | x -> raise <| NotSupportedException(string x)
  and evalAll args = [| for arg in args -> eval arg |]

  let propertyChanged = new Event<_,_>()

  interface INotifyPropertyChanged with
    [<CLIEvent>]
    member __.PropertyChanged = propertyChanged.Publish

  member this.OnPropertyChanged(propertyName : string) =
    propertyChanged.Trigger(this, new PropertyChangedEventArgs(propertyName))

  /// Returns an observable that publishes the value of the quoted property each
  /// time INotifyPropertyChanged is raised for this property.
  member this.Observe (getProperty: Expr<'a>) : IObservable<'a> = 
    match getProperty with
    | PropertyGet (_, propInfo, _) ->
        (this :> INotifyPropertyChanged).PropertyChanged
        |> Observable.filter (fun args -> args.PropertyName = propInfo.Name)
        |> Observable.map (fun _ -> eval getProperty :?> 'a)
    | _ -> failwith "Expression must be a property getter"

  /// Calls the callback with the value of the expression every time
  /// INotifyPropertyChanged is raised for this property.
  member this.Subscribe (getProperty: Expr<'a>) (callback: 'a -> unit) : unit = 
    this.Observe getProperty |> Observable.add callback

  /// Calls the callback with the value of the expression every time
  /// INotifyPropertyChanged is raised for this property. Also calls the callback
  /// immediately with the current value of the expression.
  member this.SubscribeAndInit (getProperty: Expr<'a>) (callback: 'a -> unit) : unit = 
    this.Subscribe getProperty callback
    eval getProperty :?> 'a |> callback



/// Usage

type MyVm() =
  inherit ViewModelBase()
  member val MyInt = 0 with get, set

let vm = MyVm()

vm.SubscribeAndInit <@ vm.MyInt @> (fun i -> printfn "Value is %i" i)
namespace System
namespace System.ComponentModel
namespace Microsoft
namespace Microsoft.FSharp
namespace Microsoft.FSharp.Reflection
namespace Microsoft.FSharp.Quotations
module Patterns

from Microsoft.FSharp.Quotations
Multiple items
type ViewModelBase =
  interface INotifyPropertyChanged
  new : unit -> ViewModelBase
  member Observe : getProperty:Expr<'a> -> IObservable<'a>
  member OnPropertyChanged : propertyName:string -> unit
  member Subscribe : getProperty:Expr<'a> -> callback:('a -> unit) -> unit
  member SubscribeAndInit : getProperty:Expr<'a> -> callback:('a -> unit) -> unit

Full name: Script.ViewModelBase

--------------------
new : unit -> ViewModelBase
val eval : (Expr -> obj)


 Evaluates an expression. From http://www.fssnip.net/h1
active recognizer Value: Expr -> (obj * Type) option

Full name: Microsoft.FSharp.Quotations.Patterns.( |Value|_| )
val v : obj
active recognizer Coerce: Expr -> (Expr * Type) option

Full name: Microsoft.FSharp.Quotations.Patterns.( |Coerce|_| )
val e : Expr
active recognizer NewObject: Expr -> (Reflection.ConstructorInfo * Expr list) option

Full name: Microsoft.FSharp.Quotations.Patterns.( |NewObject|_| )
val ci : Reflection.ConstructorInfo
val args : Expr list
Reflection.ConstructorInfo.Invoke(parameters: obj []) : obj
Reflection.MethodBase.Invoke(obj: obj, parameters: obj []) : obj
Reflection.ConstructorInfo.Invoke(invokeAttr: Reflection.BindingFlags, binder: Reflection.Binder, parameters: obj [], culture: Globalization.CultureInfo) : obj
Reflection.MethodBase.Invoke(obj: obj, invokeAttr: Reflection.BindingFlags, binder: Reflection.Binder, parameters: obj [], culture: Globalization.CultureInfo) : obj
val evalAll : (Expr list -> obj [])
active recognizer NewArray: Expr -> (Type * Expr list) option

Full name: Microsoft.FSharp.Quotations.Patterns.( |NewArray|_| )
val t : Type
Multiple items
val array : Array

--------------------
type 'T array = 'T []

Full name: Microsoft.FSharp.Core.array<_>
type Array =
  member Clone : unit -> obj
  member CopyTo : array:Array * index:int -> unit + 1 overload
  member GetEnumerator : unit -> IEnumerator
  member GetLength : dimension:int -> int
  member GetLongLength : dimension:int -> int64
  member GetLowerBound : dimension:int -> int
  member GetUpperBound : dimension:int -> int
  member GetValue : [<ParamArray>] indices:int[] -> obj + 7 overloads
  member Initialize : unit -> unit
  member IsFixedSize : bool
  ...

Full name: System.Array
Array.CreateInstance(elementType: Type, [<ParamArray>] lengths: int64 []) : Array
Array.CreateInstance(elementType: Type, [<ParamArray>] lengths: int []) : Array
Array.CreateInstance(elementType: Type, length: int) : Array
Array.CreateInstance(elementType: Type, lengths: int [], lowerBounds: int []) : Array
Array.CreateInstance(elementType: Type, length1: int, length2: int) : Array
Array.CreateInstance(elementType: Type, length1: int, length2: int, length3: int) : Array
property List.Length: int
Multiple items
module List

from Microsoft.FSharp.Collections

--------------------
type List<'T> =
  | ( [] )
  | ( :: ) of Head: 'T * Tail: 'T list
  interface IEnumerable
  interface IEnumerable<'T>
  member GetSlice : startIndex:int option * endIndex:int option -> 'T list
  member Head : 'T
  member IsEmpty : bool
  member Item : index:int -> 'T with get
  member Length : int
  member Tail : 'T list
  static member Cons : head:'T * tail:'T list -> 'T list
  static member Empty : 'T list

Full name: Microsoft.FSharp.Collections.List<_>
val iteri : action:(int -> 'T -> unit) -> list:'T list -> unit

Full name: Microsoft.FSharp.Collections.List.iteri
val i : int
val arg : Expr
Array.SetValue(value: obj, [<ParamArray>] indices: int64 []) : unit
Array.SetValue(value: obj, index: int64) : unit
Array.SetValue(value: obj, [<ParamArray>] indices: int []) : unit
Array.SetValue(value: obj, index: int) : unit
Array.SetValue(value: obj, index1: int64, index2: int64) : unit
Array.SetValue(value: obj, index1: int, index2: int) : unit
Array.SetValue(value: obj, index1: int64, index2: int64, index3: int64) : unit
Array.SetValue(value: obj, index1: int, index2: int, index3: int) : unit
val box : value:'T -> obj

Full name: Microsoft.FSharp.Core.Operators.box
active recognizer NewUnionCase: Expr -> (UnionCaseInfo * Expr list) option

Full name: Microsoft.FSharp.Quotations.Patterns.( |NewUnionCase|_| )
val case : UnionCaseInfo
type FSharpValue =
  static member GetExceptionFields : exn:obj * ?bindingFlags:BindingFlags -> obj []
  static member GetRecordField : record:obj * info:PropertyInfo -> obj
  static member GetRecordFields : record:obj * ?bindingFlags:BindingFlags -> obj []
  static member GetTupleField : tuple:obj * index:int -> obj
  static member GetTupleFields : tuple:obj -> obj []
  static member GetUnionFields : value:obj * unionType:Type * ?bindingFlags:BindingFlags -> UnionCaseInfo * obj []
  static member MakeFunction : functionType:Type * implementation:(obj -> obj) -> obj
  static member MakeRecord : recordType:Type * values:obj [] * ?bindingFlags:BindingFlags -> obj
  static member MakeTuple : tupleElements:obj [] * tupleType:Type -> obj
  static member MakeUnion : unionCase:UnionCaseInfo * args:obj [] * ?bindingFlags:BindingFlags -> obj
  ...

Full name: Microsoft.FSharp.Reflection.FSharpValue
static member FSharpValue.MakeUnion : unionCase:UnionCaseInfo * args:obj [] * ?allowAccessToPrivateRepresentation:bool -> obj
static member FSharpValue.MakeUnion : unionCase:UnionCaseInfo * args:obj [] * ?bindingFlags:Reflection.BindingFlags -> obj
active recognizer NewRecord: Expr -> (Type * Expr list) option

Full name: Microsoft.FSharp.Quotations.Patterns.( |NewRecord|_| )
static member FSharpValue.MakeRecord : recordType:Type * values:obj [] * ?allowAccessToPrivateRepresentation:bool -> obj
static member FSharpValue.MakeRecord : recordType:Type * values:obj [] * ?bindingFlags:Reflection.BindingFlags -> obj
active recognizer NewTuple: Expr -> Expr list option

Full name: Microsoft.FSharp.Quotations.Patterns.( |NewTuple|_| )
type FSharpType =
  static member GetExceptionFields : exceptionType:Type * ?bindingFlags:BindingFlags -> PropertyInfo []
  static member GetFunctionElements : functionType:Type -> Type * Type
  static member GetRecordFields : recordType:Type * ?bindingFlags:BindingFlags -> PropertyInfo []
  static member GetTupleElements : tupleType:Type -> Type []
  static member GetUnionCases : unionType:Type * ?bindingFlags:BindingFlags -> UnionCaseInfo []
  static member IsExceptionRepresentation : exceptionType:Type * ?bindingFlags:BindingFlags -> bool
  static member IsFunction : typ:Type -> bool
  static member IsModule : typ:Type -> bool
  static member IsRecord : typ:Type * ?bindingFlags:BindingFlags -> bool
  static member IsTuple : typ:Type -> bool
  ...

Full name: Microsoft.FSharp.Reflection.FSharpType
static member FSharpType.MakeTupleType : types:Type [] -> Type
property Expr.Type: Type
static member FSharpValue.MakeTuple : tupleElements:obj [] * tupleType:Type -> obj
active recognizer FieldGet: Expr -> (Expr option * Reflection.FieldInfo) option

Full name: Microsoft.FSharp.Quotations.Patterns.( |FieldGet|_| )
union case Option.Some: Value: 'T -> Option<'T>
val fi : Reflection.FieldInfo
Reflection.FieldInfo.GetValue(obj: obj) : obj
active recognizer PropertyGet: Expr -> (Expr option * Reflection.PropertyInfo * Expr list) option

Full name: Microsoft.FSharp.Quotations.Patterns.( |PropertyGet|_| )
union case Option.None: Option<'T>
val pi : Reflection.PropertyInfo
Reflection.PropertyInfo.GetValue(obj: obj, index: obj []) : obj
Reflection.PropertyInfo.GetValue(obj: obj, invokeAttr: Reflection.BindingFlags, binder: Reflection.Binder, index: obj [], culture: Globalization.CultureInfo) : obj
val x : Expr
active recognizer Call: Expr -> (Expr option * Reflection.MethodInfo * Expr list) option

Full name: Microsoft.FSharp.Quotations.Patterns.( |Call|_| )
val mi : Reflection.MethodInfo
Reflection.MethodBase.Invoke(obj: obj, parameters: obj []) : obj
Reflection.MethodBase.Invoke(obj: obj, invokeAttr: Reflection.BindingFlags, binder: Reflection.Binder, parameters: obj [], culture: Globalization.CultureInfo) : obj
val raise : exn:Exception -> 'T

Full name: Microsoft.FSharp.Core.Operators.raise
Multiple items
type NotSupportedException =
  inherit SystemException
  new : unit -> NotSupportedException + 2 overloads

Full name: System.NotSupportedException

--------------------
NotSupportedException() : unit
NotSupportedException(message: string) : unit
NotSupportedException(message: string, innerException: exn) : unit
Multiple items
val string : value:'T -> string

Full name: Microsoft.FSharp.Core.Operators.string

--------------------
type string = String

Full name: Microsoft.FSharp.Core.string
val propertyChanged : Event<PropertyChangedEventHandler,PropertyChangedEventArgs>
Multiple items
module Event

from Microsoft.FSharp.Control

--------------------
type Event<'T> =
  new : unit -> Event<'T>
  member Trigger : arg:'T -> unit
  member Publish : IEvent<'T>

Full name: Microsoft.FSharp.Control.Event<_>

--------------------
type Event<'Delegate,'Args (requires delegate and 'Delegate :> Delegate)> =
  new : unit -> Event<'Delegate,'Args>
  member Trigger : sender:obj * args:'Args -> unit
  member Publish : IEvent<'Delegate,'Args>

Full name: Microsoft.FSharp.Control.Event<_,_>

--------------------
new : unit -> Event<'T>

--------------------
new : unit -> Event<'Delegate,'Args>
type INotifyPropertyChanged =
  event PropertyChanged : PropertyChangedEventHandler

Full name: System.ComponentModel.INotifyPropertyChanged
Multiple items
type CLIEventAttribute =
  inherit Attribute
  new : unit -> CLIEventAttribute

Full name: Microsoft.FSharp.Core.CLIEventAttribute

--------------------
new : unit -> CLIEventAttribute
val __ : ViewModelBase
override ViewModelBase.PropertyChanged : IEvent<PropertyChangedEventHandler,PropertyChangedEventArgs>

Full name: Script.ViewModelBase.PropertyChanged
property Event.Publish: IEvent<PropertyChangedEventHandler,PropertyChangedEventArgs>
val this : ViewModelBase
member ViewModelBase.OnPropertyChanged : propertyName:string -> unit

Full name: Script.ViewModelBase.OnPropertyChanged
val propertyName : string
member Event.Trigger : sender:obj * args:'Args -> unit
Multiple items
type PropertyChangedEventArgs =
  inherit EventArgs
  new : propertyName:string -> PropertyChangedEventArgs
  member PropertyName : string

Full name: System.ComponentModel.PropertyChangedEventArgs

--------------------
PropertyChangedEventArgs(propertyName: string) : unit
member ViewModelBase.Observe : getProperty:Expr<'a> -> IObservable<'a>

Full name: Script.ViewModelBase.Observe


 Returns an observable that publishes the value of the quoted property each
 time INotifyPropertyChanged is raised for this property.
val getProperty : Expr<'a>
Multiple items
type Expr =
  override Equals : obj:obj -> bool
  member GetFreeVars : unit -> seq<Var>
  member Substitute : substitution:(Var -> Expr option) -> Expr
  member ToString : full:bool -> string
  member CustomAttributes : Expr list
  member Type : Type
  static member AddressOf : target:Expr -> Expr
  static member AddressSet : target:Expr * value:Expr -> Expr
  static member Application : functionExpr:Expr * argument:Expr -> Expr
  static member Applications : functionExpr:Expr * arguments:Expr list list -> Expr
  ...

Full name: Microsoft.FSharp.Quotations.Expr

--------------------
type Expr<'T> =
  inherit Expr
  member Raw : Expr

Full name: Microsoft.FSharp.Quotations.Expr<_>
type IObservable<'T> =
  member Subscribe : observer:IObserver<'T> -> IDisposable

Full name: System.IObservable<_>
val propInfo : Reflection.PropertyInfo
module Observable

from Microsoft.FSharp.Control
val filter : predicate:('T -> bool) -> source:IObservable<'T> -> IObservable<'T>

Full name: Microsoft.FSharp.Control.Observable.filter
val args : PropertyChangedEventArgs
property PropertyChangedEventArgs.PropertyName: string
property Reflection.MemberInfo.Name: string
val map : mapping:('T -> 'U) -> source:IObservable<'T> -> IObservable<'U>

Full name: Microsoft.FSharp.Control.Observable.map
val failwith : message:string -> 'T

Full name: Microsoft.FSharp.Core.Operators.failwith
member ViewModelBase.Subscribe : getProperty:Expr<'a> -> callback:('a -> unit) -> unit

Full name: Script.ViewModelBase.Subscribe


 Calls the callback with the value of the expression every time
 INotifyPropertyChanged is raised for this property.
val callback : ('a -> unit)
type unit = Unit

Full name: Microsoft.FSharp.Core.unit
member ViewModelBase.Observe : getProperty:Expr<'a> -> IObservable<'a>


 Returns an observable that publishes the value of the quoted property each
 time INotifyPropertyChanged is raised for this property.
val add : callback:('T -> unit) -> source:IObservable<'T> -> unit

Full name: Microsoft.FSharp.Control.Observable.add
member ViewModelBase.SubscribeAndInit : getProperty:Expr<'a> -> callback:('a -> unit) -> unit

Full name: Script.ViewModelBase.SubscribeAndInit


 Calls the callback with the value of the expression every time
 INotifyPropertyChanged is raised for this property. Also calls the callback
 immediately with the current value of the expression.
member ViewModelBase.Subscribe : getProperty:Expr<'a> -> callback:('a -> unit) -> unit


 Calls the callback with the value of the expression every time
 INotifyPropertyChanged is raised for this property.
Multiple items
type MyVm =
  inherit ViewModelBase
  new : unit -> MyVm
  member MyInt : int
  member MyInt : int with set

Full name: Script.MyVm


 Usage


--------------------
new : unit -> MyVm
val set : elements:seq<'T> -> Set<'T> (requires comparison)

Full name: Microsoft.FSharp.Core.ExtraTopLevelOperators.set
val vm : MyVm

Full name: Script.vm
member ViewModelBase.SubscribeAndInit : getProperty:Expr<'a> -> callback:('a -> unit) -> unit


 Calls the callback with the value of the expression every time
 INotifyPropertyChanged is raised for this property. Also calls the callback
 immediately with the current value of the expression.
property MyVm.MyInt: int
val printfn : format:Printf.TextWriterFormat<'T> -> 'T

Full name: Microsoft.FSharp.Core.ExtraTopLevelOperators.printfn
Raw view Test code New version

More information

Link:http://fssnip.net/7U9
Posted:6 years ago
Author:Christer van der Meeren
Tags: inotifypropertychanged , iobservable