FSharp.Qualia


WPF Collections tutorial

The resulting app will contain a listbox, an 'Add Item' button, and label displaying the current selection and will delete the selected item when pressing the Delete key.

First, a small helper used for the ListBox.SelectedItem:

1: 
2: 
3: 
4: 
let cast<'a> (x:obj) : 'a option =
    match x with
    | :? 'a as y -> Some y
    | _ -> None

The app event type - adding, removing and selecting an item

1: 
type Events = Add | Remove | SelectionChanged of ItemModel option

ItemModel itself is defined here, containing a string property. As it won't change, this is a plain property, not a ReactiveProperty.

We also override ToString() to avoid defining a template for the sake of the tutorial.

1: 
2: 
3: 
and ItemModel(s) =
    member val Text = s
    override x.ToString() = x.Text

The concrete WPF window type - this should be replaced by a .xaml loaded by FsXaml in the real world.

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
14: 
type ListViewWindow() as x =
    inherit Window()
    let label = Label()
    let button = Button(Content="Add Item")
    let list = ListBox()
    do
        let sp = StackPanel()
        sp.Children.Add button |> ignore
        sp.Children.Add label |> ignore
        sp.Children.Add list |> ignore
        x.Content <- sp
    member val Label = label
    member val Button = button
    member val List = list

Item Qualia view: no template here, just a Label. That's why we overrode ToString(). In SetBindings, we just set once the label content - this is equivalent to a binding mode Once.

1: 
2: 
3: 
4: 
type ItemView(m) =
    inherit View<Events, Label, ItemModel>(Label(), m)
     override x.EventStreams = []
     override x.SetBindings m = x.Root.Content <- m.Text

List model : the items collection and a reactive property containing the selected item. As it can be null, this is an ItemModel option.

1: 
2: 
3: 
type ListModel() =
    member val Items = new ObservableCollection<ItemModel>()
    member val SelectedItem = new ReactiveProperty<ItemModel option>(None)

The view inherits DerivedCollectionSourceView, but this is a convenience class providing one helper method at the moment, linkCollection. You could just do the plumbing by hand.

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
14: 
15: 
16: 
type ListView(elt, m) =
    inherit DerivedCollectionSourceView<Events, ListViewWindow, ListModel>(elt, m)

    override x.EventStreams = [
        (** Add an item when clicking the Add button *)
        elt.Button.Click --> Add
        (** This one just fetch the selected item, tries to cast it to ItemView, then select the view's Model as an option. *)
        elt.List.SelectionChanged |> Observable.map (fun _ -> SelectionChanged((cast<ItemView> elt.List.SelectedItem |> Option.map(fun v -> v.Model))))
        (** Send a remove event, only when <Del> is pressed *)
        elt.List.KeyDown |> Observable.filter (fun (e:Input.KeyEventArgs) -> e.Key = Input.Key.Delete) |> Observable.mapTo Remove ]
    override x.SetBindings m =
        (** That's all the collection plumbing: which WPF list, how to create a view for each item model, and which model collection to monitor.
            We could use the returned CollectionView to do some filtering/grouping/sorting/... *)
        let collview = x.linkCollection elt.List (fun i -> ItemView(i)) m.Items
        m.SelectedItem |> Observable.add (fun i -> elt.Label.Content <- sprintf "Press <DEL> to delete the selection item. Current Selection: %A" i)
        ()

Typical dispatcher -

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
14: 
15: 
type ListController() =
    interface IDispatcher<Events,ListModel> with
        member this.InitModel m = ()
        member this.Dispatcher = 
            function
//            | Add -> Async (fun m -> async { do m.Items.Add (ItemModel(sprintf "#%i" m.Items.Count)) })
            | Add ->
                let a = (fun (m:ListModel) -> async {
                    do! Async.Sleep 1000
                    do m.Items.Add (ItemModel(sprintf "#%i" m.Items.Count))
                })
                Async a
//                Sync (Async.Start a)
            | Remove -> Sync (fun m -> m.SelectedItem.Value |> Option.iter (m.Items.Remove >> ignore))
            | SelectionChanged item -> printfn "%A" item; Sync (fun m -> m.SelectedItem.Value <- item)
1: 
2: 
3: 
4: 
5: 
6: 
7: 
8: 
let app = Application()
let lm = ListModel()
let v = ListView(new ListViewWindow(),lm)
let c = ListController()
let loop = EventLoop(v, c)
loop.Start()
app.Run(v.Root)
app.Shutdown()
namespace System
namespace System.Windows
namespace FSharp
namespace FSharp.Qualia
namespace System.Collections
namespace System.Collections.ObjectModel
namespace System.Windows.Controls
namespace FSharp.Qualia.WPF
val cast : x:obj -> 'a option

Full name: Tutorial.wpf.cast
val x : obj
type obj = System.Object

Full name: Microsoft.FSharp.Core.obj
type 'T option = Option<'T>

Full name: Microsoft.FSharp.Core.option<_>
val y : 'a
union case Option.Some: Value: 'T -> Option<'T>
union case Option.None: Option<'T>
type Events =
  | Add
  | Remove
  | SelectionChanged of ItemModel option

Full name: Tutorial.wpf.Events
union case Events.Add: Events
union case Events.Remove: Events
union case Events.SelectionChanged: ItemModel option -> Events
Multiple items
type ItemModel =
  new : s:string -> ItemModel
  override ToString : unit -> string
  member Text : string

Full name: Tutorial.wpf.ItemModel

--------------------
new : s:string -> ItemModel
val s : string
val x : ItemModel
override ItemModel.ToString : unit -> string

Full name: Tutorial.wpf.ItemModel.ToString
property ItemModel.Text: string
Multiple items
type ListViewWindow =
  inherit Window
  new : unit -> ListViewWindow
  member Button : Button
  member Label : Label
  member List : ListBox

Full name: Tutorial.wpf.ListViewWindow

--------------------
new : unit -> ListViewWindow
val x : ListViewWindow
Multiple items
type Window =
  inherit ContentControl
  new : unit -> Window
  member Activate : unit -> bool
  member AllowsTransparency : bool with get, set
  member Close : unit -> unit
  member DialogResult : Nullable<bool> with get, set
  member DragMove : unit -> unit
  member Hide : unit -> unit
  member Icon : ImageSource with get, set
  member IsActive : bool
  member Left : float with get, set
  ...

Full name: System.Windows.Window

--------------------
Window() : unit
val label : Label
Multiple items
type Label =
  inherit ContentControl
  new : unit -> Label
  member Target : UIElement with get, set
  static val TargetProperty : DependencyProperty

Full name: System.Windows.Controls.Label

--------------------
Label() : unit
val button : Button
Multiple items
type Button =
  inherit ButtonBase
  new : unit -> Button
  member IsCancel : bool with get, set
  member IsDefault : bool with get, set
  member IsDefaulted : bool
  static val IsDefaultProperty : DependencyProperty
  static val IsCancelProperty : DependencyProperty
  static val IsDefaultedProperty : DependencyProperty

Full name: System.Windows.Controls.Button

--------------------
Button() : unit
Multiple items
val list : ListBox

--------------------
type 'T list = List<'T>

Full name: Microsoft.FSharp.Collections.list<_>
Multiple items
type ListBox =
  inherit Selector
  new : unit -> ListBox
  member ScrollIntoView : item:obj -> unit
  member SelectAll : unit -> unit
  member SelectedItems : IList
  member SelectionMode : SelectionMode with get, set
  member UnselectAll : unit -> unit
  static val SelectionModeProperty : DependencyProperty
  static val SelectedItemsProperty : DependencyProperty

Full name: System.Windows.Controls.ListBox

--------------------
ListBox() : unit
val sp : StackPanel
Multiple items
type StackPanel =
  inherit Panel
  new : unit -> StackPanel
  member CanHorizontallyScroll : bool with get, set
  member CanVerticallyScroll : bool with get, set
  member ExtentHeight : float
  member ExtentWidth : float
  member HorizontalOffset : float
  member LineDown : unit -> unit
  member LineLeft : unit -> unit
  member LineRight : unit -> unit
  member LineUp : unit -> unit
  ...

Full name: System.Windows.Controls.StackPanel

--------------------
StackPanel() : unit
property Panel.Children: UIElementCollection
UIElementCollection.Add(element: UIElement) : int
val ignore : value:'T -> unit

Full name: Microsoft.FSharp.Core.Operators.ignore
property ContentControl.Content: obj
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<_>
Multiple items
type ItemView =
  inherit View<Events,Label,ItemModel>
  new : m:ItemModel -> ItemView
  override SetBindings : m:ItemModel -> unit
  override EventStreams : IObservable<Events> list

Full name: Tutorial.wpf.ItemView

--------------------
new : m:ItemModel -> ItemView
val m : ItemModel
Multiple items
type View<'Event,'Element,'Model> =
  inherit IViewWithModel<'Event,'Model>
  new : elt:'Element * m:'Model -> View<'Event,'Element,'Model>
  member Root : 'Element

Full name: FSharp.Qualia.View<_,_,_>

--------------------
new : elt:'Element * m:'Model -> View<'Event,'Element,'Model>
val x : ItemView
override ItemView.EventStreams : System.IObservable<Events> list

Full name: Tutorial.wpf.ItemView.EventStreams
override ItemView.SetBindings : m:ItemModel -> unit

Full name: Tutorial.wpf.ItemView.SetBindings
property View.Root: Label
Multiple items
type ListModel =
  new : unit -> ListModel
  member Items : ObservableCollection<ItemModel>
  member SelectedItem : ReactiveProperty<ItemModel option>

Full name: Tutorial.wpf.ListModel

--------------------
new : unit -> ListModel
Multiple items
type ObservableCollection<'T> =
  inherit Collection<'T>
  new : unit -> ObservableCollection<'T> + 2 overloads
  member Move : oldIndex:int * newIndex:int -> unit
  event CollectionChanged : NotifyCollectionChangedEventHandler

Full name: System.Collections.ObjectModel.ObservableCollection<_>

--------------------
ObservableCollection() : unit
ObservableCollection(list: System.Collections.Generic.List<'T>) : unit
ObservableCollection(collection: System.Collections.Generic.IEnumerable<'T>) : unit
Multiple items
type ReactiveProperty<'a> =
  interface IObservable<'a>
  new : init:'a -> ReactiveProperty<'a>
  new : source:IObservable<'a> * init:'a -> ReactiveProperty<'a>
  override ToString : unit -> string
  member Value : 'a
  member private sub : BehaviorSubject<'a>
  member Value : 'a with set

Full name: FSharp.Qualia.ReactiveProperty<_>

--------------------
new : init:'a -> ReactiveProperty<'a>
new : source:System.IObservable<'a> * init:'a -> ReactiveProperty<'a>
Multiple items
type ListView =
  inherit DerivedCollectionSourceView<Events,ListViewWindow,ListModel>
  new : elt:ListViewWindow * m:ListModel -> ListView
  override SetBindings : m:ListModel -> unit
  override EventStreams : IObservable<Events> list

Full name: Tutorial.wpf.ListView

--------------------
new : elt:ListViewWindow * m:ListModel -> ListView
val elt : ListViewWindow
val m : ListModel
Multiple items
type DerivedCollectionSourceView<'Event,'Element,'Model (requires 'Element :> FrameworkElement)> =
  inherit View<'Event,'Element,'Model>
  new : elt:'Element * m:'Model -> DerivedCollectionSourceView<'Event,'Element,'Model>
  member linkCollection : itemsControl:ItemsControl -> creator:('ItemModel -> 'ItemView) -> coll:ObservableCollection<'ItemModel> -> ICollectionView (requires equality and equality and 'ItemView :> IViewWithModel<'Event,'ItemModel>)

Full name: FSharp.Qualia.WPF.DerivedCollectionSourceView<_,_,_>

--------------------
new : elt:'Element * m:'Model -> DerivedCollectionSourceView<'Event,'Element,'Model>
val x : ListView
override ListView.EventStreams : System.IObservable<Events> list

Full name: Tutorial.wpf.ListView.EventStreams
property ListViewWindow.Button: Button
event Primitives.ButtonBase.Click: IEvent<RoutedEventHandler,RoutedEventArgs>
property ListViewWindow.List: ListBox
event Primitives.Selector.SelectionChanged: IEvent<SelectionChangedEventHandler,SelectionChangedEventArgs>
Multiple items
module Observable

from FSharp.Qualia

--------------------
module Observable

from Microsoft.FSharp.Control
val map : mapping:('T -> 'U) -> source:System.IObservable<'T> -> System.IObservable<'U>

Full name: Microsoft.FSharp.Control.Observable.map
property Primitives.Selector.SelectedItem: obj
module Option

from Microsoft.FSharp.Core
val map : mapping:('T -> 'U) -> option:'T option -> 'U option

Full name: Microsoft.FSharp.Core.Option.map
val v : ItemView
property IViewWithModel.Model: ItemModel
event UIElement.KeyDown: IEvent<Input.KeyEventHandler,Input.KeyEventArgs>
val filter : predicate:('T -> bool) -> source:System.IObservable<'T> -> System.IObservable<'T>

Full name: Microsoft.FSharp.Control.Observable.filter
val e : Input.KeyEventArgs
namespace System.Windows.Input
Multiple items
type KeyEventArgs =
  inherit KeyboardEventArgs
  new : keyboard:KeyboardDevice * inputSource:PresentationSource * timestamp:int * key:Key -> KeyEventArgs
  member DeadCharProcessedKey : Key
  member ImeProcessedKey : Key
  member InputSource : PresentationSource
  member IsDown : bool
  member IsRepeat : bool
  member IsToggled : bool
  member IsUp : bool
  member Key : Key
  member KeyStates : KeyStates
  ...

Full name: System.Windows.Input.KeyEventArgs

--------------------
Input.KeyEventArgs(keyboard: Input.KeyboardDevice, inputSource: PresentationSource, timestamp: int, key: Input.Key) : unit
property Input.KeyEventArgs.Key: Input.Key
type Key =
  | None = 0
  | Cancel = 1
  | Back = 2
  | Tab = 3
  | LineFeed = 4
  | Clear = 5
  | Return = 6
  | Enter = 6
  | Pause = 7
  | Capital = 8
  ...

Full name: System.Windows.Input.Key
field Input.Key.Delete = 32
val mapTo : value:'a -> (System.IObservable<'b> -> System.IObservable<'a>)

Full name: FSharp.Qualia.Observable.mapTo
override ListView.SetBindings : m:ListModel -> unit

Full name: Tutorial.wpf.ListView.SetBindings
val collview : System.ComponentModel.ICollectionView
member DerivedCollectionSourceView.linkCollection : itemsControl:ItemsControl -> creator:('ItemModel -> 'ItemView) -> coll:ObservableCollection<'ItemModel> -> System.ComponentModel.ICollectionView (requires equality and equality and 'ItemView :> IViewWithModel<'Event,'ItemModel>)
val i : ItemModel
property ListModel.Items: ObservableCollection<ItemModel>
property ListModel.SelectedItem: ReactiveProperty<ItemModel option>
val add : callback:('T -> unit) -> source:System.IObservable<'T> -> unit

Full name: Microsoft.FSharp.Control.Observable.add
val i : ItemModel option
property ListViewWindow.Label: Label
val sprintf : format:Printf.StringFormat<'T> -> 'T

Full name: Microsoft.FSharp.Core.ExtraTopLevelOperators.sprintf
Multiple items
type ListController =
  interface IDispatcher<Events,ListModel>
  new : unit -> ListController

Full name: Tutorial.wpf.ListController

--------------------
new : unit -> ListController
type IDispatcher<'Event,'Model> =
  interface
    abstract member InitModel : 'Model -> unit
    abstract member Dispatcher : ('Event -> EventHandler<'Model>)
  end

Full name: FSharp.Qualia.IDispatcher<_,_>
val this : ListController
override ListController.InitModel : m:ListModel -> unit

Full name: Tutorial.wpf.ListController.InitModel
override ListController.Dispatcher : (Events -> EventHandler<ListModel>)

Full name: Tutorial.wpf.ListController.Dispatcher
val a : (ListModel -> Async<unit>)
val async : AsyncBuilder

Full name: Microsoft.FSharp.Core.ExtraTopLevelOperators.async
Multiple items
union case EventHandler.Async: ('Model -> Async<unit>) -> EventHandler<'Model>

--------------------
type Async
static member AsBeginEnd : computation:('Arg -> Async<'T>) -> ('Arg * AsyncCallback * obj -> IAsyncResult) * (IAsyncResult -> 'T) * (IAsyncResult -> unit)
static member AwaitEvent : event:IEvent<'Del,'T> * ?cancelAction:(unit -> unit) -> Async<'T> (requires delegate and 'Del :> Delegate)
static member AwaitIAsyncResult : iar:IAsyncResult * ?millisecondsTimeout:int -> Async<bool>
static member AwaitTask : task:Task -> Async<unit>
static member AwaitTask : task:Task<'T> -> Async<'T>
static member AwaitWaitHandle : waitHandle:WaitHandle * ?millisecondsTimeout:int -> Async<bool>
static member CancelDefaultToken : unit -> unit
static member Catch : computation:Async<'T> -> Async<Choice<'T,exn>>
static member FromBeginEnd : beginAction:(AsyncCallback * obj -> IAsyncResult) * endAction:(IAsyncResult -> 'T) * ?cancelAction:(unit -> unit) -> Async<'T>
static member FromBeginEnd : arg:'Arg1 * beginAction:('Arg1 * AsyncCallback * obj -> IAsyncResult) * endAction:(IAsyncResult -> 'T) * ?cancelAction:(unit -> unit) -> Async<'T>
static member FromBeginEnd : arg1:'Arg1 * arg2:'Arg2 * beginAction:('Arg1 * 'Arg2 * AsyncCallback * obj -> IAsyncResult) * endAction:(IAsyncResult -> 'T) * ?cancelAction:(unit -> unit) -> Async<'T>
static member FromBeginEnd : arg1:'Arg1 * arg2:'Arg2 * arg3:'Arg3 * beginAction:('Arg1 * 'Arg2 * 'Arg3 * AsyncCallback * obj -> IAsyncResult) * endAction:(IAsyncResult -> 'T) * ?cancelAction:(unit -> unit) -> Async<'T>
static member FromContinuations : callback:(('T -> unit) * (exn -> unit) * (OperationCanceledException -> unit) -> unit) -> Async<'T>
static member Ignore : computation:Async<'T> -> Async<unit>
static member OnCancel : interruption:(unit -> unit) -> Async<IDisposable>
static member Parallel : computations:seq<Async<'T>> -> Async<'T []>
static member RunSynchronously : computation:Async<'T> * ?timeout:int * ?cancellationToken:CancellationToken -> 'T
static member Sleep : millisecondsDueTime:int -> Async<unit>
static member Start : computation:Async<unit> * ?cancellationToken:CancellationToken -> unit
static member StartAsTask : computation:Async<'T> * ?taskCreationOptions:TaskCreationOptions * ?cancellationToken:CancellationToken -> Task<'T>
static member StartChild : computation:Async<'T> * ?millisecondsTimeout:int -> Async<Async<'T>>
static member StartChildAsTask : computation:Async<'T> * ?taskCreationOptions:TaskCreationOptions -> Async<Task<'T>>
static member StartImmediate : computation:Async<unit> * ?cancellationToken:CancellationToken -> unit
static member StartWithContinuations : computation:Async<'T> * continuation:('T -> unit) * exceptionContinuation:(exn -> unit) * cancellationContinuation:(OperationCanceledException -> unit) * ?cancellationToken:CancellationToken -> unit
static member SwitchToContext : syncContext:SynchronizationContext -> Async<unit>
static member SwitchToNewThread : unit -> Async<unit>
static member SwitchToThreadPool : unit -> Async<unit>
static member TryCancelled : computation:Async<'T> * compensation:(OperationCanceledException -> unit) -> Async<'T>
static member CancellationToken : Async<CancellationToken>
static member DefaultCancellationToken : CancellationToken

Full name: Microsoft.FSharp.Control.Async

--------------------
type Async<'T>

Full name: Microsoft.FSharp.Control.Async<_>
static member Async.Sleep : millisecondsDueTime:int -> Async<unit>
Collection.Add(item: ItemModel) : unit
property Collection.Count: int
union case EventHandler.Sync: ('Model -> unit) -> EventHandler<'Model>
property ReactiveProperty.Value: ItemModel option
val iter : action:('T -> unit) -> option:'T option -> unit

Full name: Microsoft.FSharp.Core.Option.iter
Collection.Remove(item: ItemModel) : bool
val item : ItemModel option
val printfn : format:Printf.TextWriterFormat<'T> -> 'T

Full name: Microsoft.FSharp.Core.ExtraTopLevelOperators.printfn
val app : Application

Full name: Tutorial.wpf.app
Multiple items
type Application =
  inherit DispatcherObject
  new : unit -> Application
  member FindResource : resourceKey:obj -> obj
  member MainWindow : Window with get, set
  member Properties : IDictionary
  member Resources : ResourceDictionary with get, set
  member Run : unit -> int + 1 overload
  member Shutdown : unit -> unit + 1 overload
  member ShutdownMode : ShutdownMode with get, set
  member StartupUri : Uri with get, set
  member TryFindResource : resourceKey:obj -> obj
  ...

Full name: System.Windows.Application

--------------------
Application() : unit
val lm : ListModel

Full name: Tutorial.wpf.lm
val v : ListView

Full name: Tutorial.wpf.v
val c : ListController

Full name: Tutorial.wpf.c
val loop : EventLoop<ListModel,Events,ListViewWindow>

Full name: Tutorial.wpf.loop
Multiple items
type EventLoop<'Model,'Event,'Element> =
  new : v:View<'Event,'Element,'Model> * c:IDispatcher<'Event,'Model> -> EventLoop<'Model,'Event,'Element>
  member Inject : e:'Event -> unit
  member Start : unit -> IDisposable
  member StartWithScheduler : f:((unit -> unit) -> unit) -> IDisposable

Full name: FSharp.Qualia.EventLoop<_,_,_>

--------------------
new : v:View<'Event,'Element,'Model> * c:IDispatcher<'Event,'Model> -> EventLoop<'Model,'Event,'Element>
member EventLoop.Start : unit -> System.IDisposable
Application.Run() : int
Application.Run(window: Window) : int
property View.Root: ListViewWindow
Application.Shutdown() : unit
Application.Shutdown(exitCode: int) : unit
Fork me on GitHub