F# Winforms Charting Asynchronous Updating

273 views Asked by At

I'm trying to create a chart in winforms that databinds to a list in memory, and gets updated dynamically as the list changes. Here is my code:

open System
open System.Linq
open System.Collections
open System.Collections.Generic
open System.Drawing
open System.Windows.Forms
open System.Windows.Forms.DataVisualization
open System.Windows.Forms.DataVisualization.Charting

let link = new LinkedList<double>()
let rnd = new System.Random()
for i in 1 .. 10 do link.AddFirst(rnd.NextDouble()) |> ignore
let series = new Series()
let chart = new System.Windows.Forms.DataVisualization.Charting.Chart(Dock = DockStyle.Fill, Palette = ChartColorPalette.Pastel)

series.Points.DataBindY(link)

let form = new Form(Visible = true, Width = 700, Height = 500)
form.Controls.Add(chart)

let formloop = async {
    while not chart.IsDisposed do
        link.AddFirst((new System.Random()).NextDouble()) |> ignore
        link.RemoveLast()
}
do
    Async.StartImmediate(formloop)
    Application.Run(form)

Console.WriteLine("Done")
Console.ReadLine() |> ignore

The async seems to work, but the chart never shows anything. It just shows a blank window. What am I doing wrong?

2

There are 2 answers

0
Fyodor Soikin On

LinkedList<T> has no way to signal that it's been updated, so Chart has no way of knowing when to redraw itself.

In order for databinding to update the view, the source list must implement IBindingList and raise appropriate event when the contents change.

Separately, I must point out that it's dangerous to directly access UI properties/methods from non-UI threads (such as chart.IsDisposed in your code). In WinForms, this limitation is rarely actually enforced, so sometimes this might seem to work fine, only to crash later on a customer's machine with no way to attach a debugger.

0
kaefer On
  • You need to add the series to the SeriesCollection of the chart.

    chart.Series.Add series
    
  • You need to construct a chart area and add it to the ChartAreaCollection of the chart.

    let area = new ChartArea()
    chart.ChartAreas.Add area
    
  • You need to make sure that the data binding method is called after the chart and form are set up.

    ...
    form.Controls.Add chart
    series.Points.DataBindY link
    
  • And now there's no way to communicate changes of your bound collection to the DataPointCollection of the series, as mentioned in Fyodor Soikin's answer. I'm not quite sure that IBindingList is an appropriate response; while it's possible to hook into the ListChanged event, we could as well manipulate the series' DataPointCollection directly.

    let formloop = async{
        while not chart.IsDisposed do
            series.Points.RemoveAt 0
            series.Points.AddY(rnd.NextDouble()) |> ignore
            do! Async.Sleep 100 }
    
  • Finally I'd like to point out this contribution by John Atwood which adresses both points raised by Fyodor; the data binding issue (by not using it) and the UI-thread safety issue.