R – INotifyPropertyChanged and INotifyCollectionChanged in F# using WPF

%fdata-bindingdatagridinotifypropertychangedwpf

I have an array of sample information that is constantly refreshed in a background thread.

Currently I am constantly assigning this array to a datagrid's ItemsSource property using a DispatcherTimer. That works but it resets any visual locations, for instance if the user places his cursor in the middle of the datagrid the execution timer will undo such position.

Is it possible to use a INotifyPropertyChanged or INotifyCollectionChanged event for this instead to prevent such situations to occur? If so, how does this work with F#?

I suppose I have to execute some function notifying the datagrid every time when there is an update of the array. The updating of the array is not in the STAThread section.

I am running VS2010 with the latest WPF toolkit containing the WPF datagrid.

Best Answer

You can use an ObservableCollection which will implements INotifyCollectionChanged for you. The F# looks something like this:

open System
open System.Collections.ObjectModel
open System.Windows
open System.Windows.Controls
open System.Windows.Threading

[<EntryPoint; STAThread>]
let Main args =

    let data    = ObservableCollection [0 .. 9]
    let list    = ListBox(ItemsSource = data)    
    let win     = Window(Content = list, Visibility = Visibility.Visible)    
    let rnd     = Random()

    let callback = 
        EventHandler(fun sender args -> 
            let idx = rnd.Next(0, 10)
            data.[idx] <- rnd.Next(0, 10) 
            )

    let ts  = TimeSpan(1000000L)
    let dp  = DispatcherPriority.Send
    let cd  = Dispatcher.CurrentDispatcher   

    let timer   = DispatcherTimer(ts, dp, callback, cd) in timer.Start()    
    let app     = Application() in app.Run(win)

Unfortunately Reflector shows that System.Windows.Controls.ItemsControl.OnItemCollectionChanged method removes the selection when it is called, so you may need to work around this default behaviour.

You can also implement INotifyPropertyChanged like so:

open System.ComponentModel

type MyObservable() =

    let mutable propval = 0.0
    let evt             = Event<_,_>()   

    interface INotifyPropertyChanged with

        [<CLIEvent>]
        member this.PropertyChanged = evt.Publish

    member this.MyProperty

        with get()  = propval
        and  set(v) = propval <- v
                      evt.Trigger(this, PropertyChangedEventArgs("MyProperty"))

Implementing INotifyCollectionChanged would work similarly.

best of luck,

Danny