Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

F# Display a WPF window asynchronously

Tags:

wpf

f#

This problem has been driving me mad. Here's the general gist:

I have two projects in a solution: the first is an F# console application and the second is a C# library with a C#+XAML class called DisplayWindow inheriting from the WPF Window. DisplayWindow has a method public void SetMessage(string s) {...} that makes the window display the text passed to it in big shiny letters that will probably also flash and spin arround and do everything else WPF is good at.

The problem is: From my F# program I need to make a function let openAWindow text = ??? so that it will open a new DisplayWindow asynchronously every time its called with the text. What is the best way to do this? Using async {} or System.Threading.Thread? Thanks for the help :)

Edit: I found this blog post http://deanchalk.com/2010/10/08/f-interacting-with-wpf-dispatcher-via-f-interactive-window/ that works but can sometimes (?) cause an ArgumentException with the error text "An entry with the same key already exists." so I have no idea whats going on there :(

like image 497
Ed Ayers Avatar asked Jan 14 '12 18:01

Ed Ayers


1 Answers

I did this for our F# for Visualization library and then described the technique I used in my book Visual F# 2010 for Technical Computing.

Firstly, I wrote a lazy thunk that initializes WPF (including an STA UI thread and Application) when its evaluation is forced:

> let ui =
    let mk() =
      let wh = new ManualResetEvent(false)
      let application = ref null
      let start() =
        let app = Application()
        application := app
        ignore(wh.Set())
        app.Run() |> ignore
    let thread = Thread start
    thread.IsBackground <- true
    thread.SetApartmentState ApartmentState.STA
    thread.Start()
    ignore(wh.WaitOne())
    !application, thread
  lazy(mk());;
val ui : Lazy<Application * Thread> = <unevaluated>

Then I wrote a spawn function that dispatches the application of a function f to an argument x such that it is run on the UI thread:

> let spawn : ('a -> 'b) -> 'a -> 'b =
    fun f x ->
      let app, thread = ui.Force()
      let f _ =
        try
          let f_x = f x
          fun () -> f_x
        with e ->
          fun () -> raise e
      let t = app.Dispatcher.Invoke(DispatcherPriority.Send, System.Func<_, _>(f), null)
      (t :?> unit -> 'b)();;
val spawn : ('a -> 'b) -> 'a -> 'b

Now it is just a case of invoking your openAWindow function on the UI thread with:

let openAWindow text =
  DisplayWindow().SetMessage text

spawn openAWindow text
like image 155
J D Avatar answered Oct 04 '22 03:10

J D