fslaborg / FSharp.Charting

Charting library suitable for interactive F# scripting
http://fslab.org/FSharp.Charting/
Other
216 stars 67 forks source link

Feature: expose creation of a form used by ShowChart() for more use cases #122

Closed nightroman closed 5 years ago

nightroman commented 7 years ago

I am trying to use FSharp.Charting in an application which does not have its Windows forms event loop. ShowChart() is not supposed to work because it calls the form Show(), not suitable in such a case. So far so good, FSharp.Charting was not designed for this.

For tasks like showing charts it is still possible to use forms with ShowDialog(), it does not require the event loop. So I tried to create my ShowChartDialog() similar to ShowChart() with Show replaced with ShowDialog. It turns out to be not easy outside of FSharp.Charting because ShowChart() uses some internal stuff.

Would it be possible to add a new member which creates a form with a chart control and returns it without showing?

Here is what I tried, successfully so far. ShowChart is replaced with the new CreateChartForm and the modified ShowChart which uses the new method:

    /// Create the chart in a new ChartControl in a new Form()
    member ch.CreateChartForm () =
        let frm = new Form(Width = 700, Height = 500)
        let ctl = new ChartControl(ch, Dock = DockStyle.Fill)
        frm.Text <- ProvideTitle ch
        frm.Controls.Add(ctl)
        frm.Load.Add (fun _ ->
            ch.CustomizationFunctions |> List.rev |> List.map (fun fn -> fn ch.Chart) |> ignore
            ctl.Focus() |> ignore
            frm.Activate()
        )
        frm

    /// Display the chart in a new ChartControl in a new Form()
    member ch.ShowChart () =
        let frm = ch.CreateChartForm()
        frm.Visible <- true
        frm.TopMost <- true
        frm.Show()
        frm

CreateChartForm creates a very basic form with nothing but the default width and height set. The rest is supposed to be configured by callers. For example, ShowChart sets extra properties and then calls Show. It is almost the same as before, though some code was moved to frm.Load. I also added frm.Activate() because I noticed that the form was not always active without this.

With the new method CreateChartForm my ShowDialog scenario becomes possible. Here is the helper function showChart:

    let private startSTA action =
        let thread = Thread (ThreadStart action)
        thread.SetApartmentState ApartmentState.STA
        thread.Start ()

    let showChart (chart: ChartTypes.GenericChart) =
        startSTA (fun () ->
            let form = chart.CreateChartForm()
            form.ShowDialog() |> ignore
        )

Some notes. Use of a separate thread solves two issues:

  1. The modal ShowDialog does not block the application and users can create and view several charts simultaneously.
  2. Use of ApartmentState.STA solves the problem of provided clipboard operations in chart forms, they require STA.

If you find the suggestion reasonable I can make a PR (amended and retested, if you want me to change something).

Perhaps there is a way to achieve the same results without changing the library. Then I would like to know it. I could not find an easy one on my own.

nightroman commented 7 years ago

My above showChart with startSTA are not suggested for the library. But they could be added, too, as a ready to use convenient method ShowChartDialog().

nightroman commented 7 years ago

After reading the manuals and sources I found the way that works out of the box. Chart.Show uses the form ShowDialog and basically works in my scenario without forms event loop.

But this way is less attractive. The dialog does not have a title. There is no way to customize the form before ShowDialog. In particular I cannot call the important bit frm.Activate(). The dialog does not use CustomizationFunctions.

I also noticed that Chart.Show and ShowChart use different form sizes, (800, 600) and (700, 500).

In other words, even with found Chart.Show the requested feature may be useful.

simra commented 7 years ago

Thanks, this is interesting. Would it be useful if Chart.Show allowed you to pass a callback to do things like frm.Activate? I think your idea's good, just trying to educate myself about what would be the most flexible + simultaneously easy for noobs. Let me try to take a little time with the code to see how the two options (yours vs status quo) compare. @tpetricek, any thoughts?

nightroman commented 7 years ago

Thank you for the promising replay. Please take your time and use the design most suitable from various points of view. To me, personally, a particular design is not that important if the flexibility is provided.

tpetricek commented 7 years ago

Sorry for the slow ping - I missed this while on holidays. I'm happy to go with anything that works for you @nightroman. I'm quite confused about this whole event loop business!

nightroman commented 5 years ago

ChartTypes.ChartControl gives me all I need, I can create any form I like and add it. Nothing else is needed from the package. Thus, I close the issue.