Multi-column lists in WPF with rich cell contents - wpf

I'm trying to write an automated test harness using WPF and F#. I'd like to display aggregate test results as rows in a multi-column list. Ideally, I would like to present rich content in each cell, e.g. highlighting issues with specific tests using color or making details available via tooltips. However, I cannot figure out how to put anything richer than a plain string into a multi-column ListView.
Furthermore, I'm not a fan of XAML or data binding and prefer to write vanilla F# code. The simplest program I have been able to write that displays a multi-column WPF ListView is:
open System.Windows
let gridView = Controls.GridView()
let listView = Controls.ListView(View=gridView)
type SystemParams = { Name: string; Value: obj }
[<System.STAThread>]
do
let column header binding =
let binding = Data.Binding binding
Controls.GridViewColumn(Header=header, DisplayMemberBinding=binding)
for header, binding in ["Name", "Name"; "Value", "Value"] do
column header binding
|> gridView.Columns.Add
for prop in typeof<SystemParameters>.GetProperties() do
if prop.PropertyType <> typeof<ResourceKey> then
{ Name = prop.Name; Value = prop.GetValue(null, null) }
|> listView.Items.Add
|> ignore
Application().Run(Window(Content=listView)) |> ignore
Although this works, I don't like the way it requires the field names to be duplicated both in the type definition and as strings that are fed to WPF which presumably then uses reflection to resolve them at run-time (yuk!). Ideally, I would like to Add an obj array giving the WPF controls for each cell.
Is ListView capable of this? If so, how do you write a function that accepts a 2D array of controls and returns a ListView that visualizes them?
If not, I will probably use a Grid instead. I have tried DataGrid before and it is just a world of pain in comparison...
EDIT:
Thanks to the answers below, I have been able to come up with a solution. The multiColumnList function in the program below creates list of controls with the given headers and content with selectable rows:
open System.Windows
let multiColumnList columns contents onSelection =
let gridView = Controls.GridView()
let list = Controls.ListView(View=gridView)
let column index header =
let binding = Data.Binding(sprintf "[%d]" index)
Controls.GridViewColumn(Header=header, DisplayMemberBinding=binding)
|> gridView.Columns.Add
Seq.iteri column columns
list.ItemsSource <-
[|for row in contents ->
[|for elt in row ->
box elt|]|]
list.SelectionChanged.Add onSelection
list
[<System.STAThread>]
do
let columns = ["Name"; "Value"]
let contents =
[ for prop in typeof<SystemParameters>.GetProperties() do
if prop.PropertyType <> typeof<ResourceKey> then
yield [box prop.Name; prop.GetValue(null, null)] ]
Application().Run(Window(Content=multiColumnList columns contents ignore))
|> ignore

Yes, it possible but it's a little tricky, but once you've mastered the approach it's quite flexible. WFP has a flexible templating system that this available both though code and XAML, except there are far less examples of how to do this in code.
It basically involves working how to to use the FrameworkElementFactory to override the list box default template and show the UI elements you want. Then using the Binding class to specify how the controls should be bound to the data.
I wrote a twitter client in WPF and F# and I use this approach to display the columns of tweets in list boxes. Take a look at how the createTweetContainerTemplate function works.
https://github.com/robertpi/TwitMemento/blob/master/Strangelights.TwitMemento.WPF/View.fs
Then again unless you really need a high level of control over how each row in the list box should be laid out, it maybe simpler to use a datagrid.

Your specific problem of duplicating the field names could be avoided by using an index based binding, instead of member name. See the changes to your example here:
open System
open System.Windows
let gridView = Controls.GridView()
let listView = Controls.ListView(View=gridView)
[<System.STAThread>]
do
let column index header =
let binding = Data.Binding(sprintf "[%d]" index)
Controls.GridViewColumn(Header=header, DisplayMemberBinding=binding)
["Name"; "Value"]
|> List.mapi column
|> List.iter gridView.Columns.Add
for prop in typeof<SystemParameters>.GetProperties() do
if prop.PropertyType <> typeof<ResourceKey> then
([| prop.Name; prop.GetValue(null, null) |] : Object array)
|> listView.Items.Add
|> ignore
Application().Run(Window(Content=listView)) |> ignore
Regarding giving the ListView a sequence of sequences of controls, that is somewhat lower level than ListView is intended to be used. ListView and DataGrid both assume that you have some roughly homogeneous collection of objects that you want show (generally as rows) and some idea of what information you want to show about those objects (the column definitions). Both controls will help in that situation, although I do agree that their general assumption that you want to use reflection over the members of a type can be annoying.
If you want to be able to specify a grid of any controls, then as you mention the Grid panel layout is probably more suitable.

I made a simple combinator library to build WPF UI thru code, I use this pattern in my pit project for creating HTML elements.
namespace FSharp.WPF
open System
open System.Windows
open System.Windows.Controls
[<AutoOpen>]
module Combinator =
type XDef =
| Attr of string * obj
| Tag of Type * XDef list
//| Element of FrameworkElement
[<AutoOpen>]
module Operators =
let (#=) (p:string) (v:obj) : XDef = Attr(p,v)
module internal Helpers =
let createEl (ty:Type) = new FrameworkElementFactory(ty)
let tag name attr = Tag(name,attr)
//let el dom = Element(dom)
let rec build (tag:XDef) =
match tag with
| Tag(ty,defs) ->
let attrs = defs |> List.choose(fun t -> match t with | Attr(k,v) -> Some(k,v) | _ -> None)
let tags = defs |> List.choose(fun t -> match t with | Tag(k,v) -> Some(t) | _ -> None)
/// create the element and set attributes
let el = Helpers.createEl(ty)
let rec setProps (d:(string*obj) list) =
match d with
| [] -> ()
| (p,v) :: t ->
let dp = System.ComponentModel.DependencyPropertyDescriptor.FromName(p, el.Type,el.Type)
el.SetValue(dp.DependencyProperty,v)
setProps attrs
let rec gen (d:XDef list) =
match d with
| [] -> ()
| h::t ->
let childEl = build(h)
el.AppendChild(childEl)
gen(t)
gen tags
el
//| Element(el) -> el
| Attr(key,value) -> failwith "Unrecognized sequence"
let make xdef =
let fEl = build xdef
let contentEl = new ContentControl()
contentEl.ContentTemplate <- new DataTemplate(VisualTree=fEl)
contentEl :> FrameworkElement
Its very low profile now, just create objects, but it could be extended to do much more with databinding and other things etc., and a bit of type checking should find errors in object creation.
Usage:
module Test =
open System.Windows.Controls
let create() =
tag typeof<System.Windows.Controls.Button> ["Content"#="Hello World"]
|> Combinator.make
let create2() =
tag typeof<StackPanel> [
tag typeof<Button> ["Content"#="Button 1"]
tag typeof<Button> ["Content"#="Button 2"]
]
|> Combinator.make
[<STAThread>]
[<EntryPoint>]
let main(_) =
let el = Test.create2() // Test.create1()
let window = new Window(Content = el, Height = 600.0, Width = 800.0, Title = "WpfApplication1")
let app = new Application()
app.Run(window)
As you see, nesting elements means Panel elements, but there could some extra leverage that allows the type to identify panel elements or content elements. But you get the idea, this could be useful. What do you think?
-Fahad

Related

Export Struct-like objects for arrays

I have been bashing my head about this and can't seem to figure it out. In another engine, I could make a struct, then make an array of that struct that I could then edit in the inspector. There seems to be no way of doing this that I can find in Godot.
I want to have a Resource that holds the starting Value and Type of multiple faces on a dice. For example, one side could have "2 Damage" while another has "Heal 3." (this is a first-time godot experiment inspired by Slice&Dice). Every tutorial I watch however makes it seem like, if I want to do so, I'd have to make a completely new Resource for each combination of Value and Type (Damage 1 Resource, Damage 2 Resource, etc.)
class_name DiceResource extends Resource
class DiceFaceData:
export var BaseValue = 0
export(Resource) var Type = preload("Resources/DiceFaceTypes/Damage.tres")
func _init():
Type = 2
BaseValue = preload("Resources/DiceFaceTypes/Damage.tres")
export(Array) var Faces = [DiceFaceData.new()]
I cannot get DiceFaceData to show up in the Inspector's array, or be on the list of object types for an array. Extending Object doesn't work. Extending Node means I have to instantiate it, which I don't want to do for an editor-only Resource.
I find it hard to imagine Godot doesn't have anything like this available. Is there anything I can load in the inspector as just data and not have to instantiate it? Another option is create two arrays, one with int and another Resource, but that seems inconvenient to fill out. Or should I just give up with Resources and make everything a Node attached to a Node attached to a Node? Thanks!
Godot version 3.4.3
EDIT: If you're someone coming from Unity or Unreal, what you're looking for is Resource. While compared to ScriptableObjects or DataAssets from those other engines, that's not the complete answer. You would think, because of the way those game engines handle it, you can only create custom SO or DA as assets in the filesystem/content browser, but you can also use Resources as instanced classes. Instead of creating a new Resource in the filesystem, you can use
export(Resource) var n = preload("res://MyResourceScript.gd").new()
In the inspector, you can choose from the list New MyResourceScript and create it. You won't be referencing an externally made Reference file, you'll be creating a custom one right there. And look at the below answer as well on good tips for using Resources in cool ways.
First of all, I want to say that I sympathize. Custom resources and the inspector do not work well. There is a solution on the work… However that does not mean that the only thing we can do is keep Waiting For Godot.
Observations on your code
About your code, I want to point out that DiceFaceData is not a resource type. You could write it like this:
class DiceFaceData extends Resource:
export var BaseValue = 0
export(Resource) var Type = preload("Resources/DiceFaceTypes/Damage.tres")
func _init():
Type = 2
BaseValue = preload("Resources/DiceFaceTypes/Damage.tres")
And… That solves nothing.
And, also, by the way, I remind you can put it on its own file:
class_name DiceFaceData
extends Resource:
export var BaseValue = 0
export(Resource) var Type = preload("Resources/DiceFaceTypes/Damage.tres")
func _init():
Type = 2
BaseValue = preload("Resources/DiceFaceTypes/Damage.tres")
And… That is not the solution either.
Something else I want to point out is that GDScript has types. See Static typing in GDScript. Use them. To illustrate…
This is a Variant with an ìnt value
var BaseValue = 0
This is an int, typed explicitly:
var BaseValue:int = 0
And this is an int, typed implicitly with type inference:
var BaseValue := 0
And if you were using types Godot would tell you that this is an error:
BaseValue = preload("Resources/DiceFaceTypes/Damage.tres")
Because BaseValue is an int, and you setting a resource to it.
The Array of Resources problem
First of all, this is a Variant that happens to have an Array value, and it is exported as an Array:
export(Array) var Faces = []
Let us type it as an Array:
export(Array) var Faces := []
And sadly we cannot specify the type of the elements of the arrays in Godot 3.x (we need Godot 4.0 for that feature). However we can specify how we export it.
So, this is an Array exported as an Array of Resource:
export(Array, Resource) var Faces := []
See Exporting arrays.
Before you could not get your custom resource type to show up. And now you have the opposite problem: all the resource types show up. And this includes your custom resource type, if it in its own file.
You would guess that we need to specify the resource type we want:
export(Array, DiceFaceData) var Faces = []
And that would be correct if it were a build-in resource type. But it is a custom one. We are expecting this to be fixed in a future version. Meanwhile we will have to leave it with export(Array, Resource).
Mitigating the problem with an addon
To alleviate the pain of having all the possible resource types, consider using the addon "Improved resource picker" by MakovWait. You can find it on itch, or on github.
A proper solution
Anyway, we can do better. But you are going to need to make your script a tool script (you do that by putting tool on the top of the script, and it means that the code from the script can and will run on the editor).
We are going to define a setter with setget, and in there we are going to make sure the elements are of the correct type:
export(Array, Resource) var Faces = [] setget set_faces
func set_faces(new_value:Array) -> void:
Faces = []
for element in new_value:
element = element as DiceFaceData
if element == null:
element = DiceFaceData.new()
Faces.append(element)
Now, in the inspector panel when you increase the size of the array, Godot will insert a new null element to the array, which makes the setter we defined run, which will find that null and convert it to a new instance of your custom resource type, so you don't have to pick the resource type in the inspector panel at all.
A "hacky" solution
As you know, this does not work:
export(Array, DiceFaceData) var Faces = []
However, we can replace an export with _get_property_list. What happens is that Godot asks the object what properties it has to show up in the inspector panel. Godot does this by calling get_property_list And it will statically report the ones it found while parsing (the ones with export). However, Godot also defines a function _get_property_list where we can add more at run time.
See also Advanced exports.
Which begs the question, could we possibly make it work with _get_property_list? Kind of. The The code like this:
var Faces := []
func _get_property_list() -> Array:
return [
{
name = "Faces",
type = TYPE_ARRAY,
hint = 24,
hint_string = "17/17:DiceFaceData"
}
]
It will show up on the inspector as an array where the elements can only be of your custom resource type.
The issue is that it causes some error spam. Which you might or might not be OK with. It is your project, so it is up to you.
I know it looks like voodoo magic in part because we are using some undocumented stuff. If you want an explanation of that 24 and that 17/17: see How to add Array with hint and hint_string?.
About the sub-resources
Every tutorial I watch however makes it seem like, if I want to do so, I'd have to make a completely new Resource for each combination of Value and Type (Damage 1 Resource, Damage 2 Resource, etc.)
I'm not sure what you are getting to with "a completely new Resource", but yes. A resource is an instance of a resource type. And each of those combination would be a resource.
Perhaps "Damage", "Heal" and so on are resources too. Let us see… I'm guessing that is what the Type is for:
export(Resource) var Type = preload("Resources/DiceFaceTypes/Damage.tres")
Godot would be showing all the resource types it is aware of, which is a pain. I'm going to suggest a different approach than those above for this: Make an String enumeration.
export(String, "Damage", "Heal") var Type:String
That will show up as a drop down list on the inspector panel, with the options you specified.
Why String and not int? Ah, because you can then do this if you so desire:
var type_resource := load("Resources/DiceFaceTypes/" + Type + ".tres")
I'm assuming that those have the code that actually does damage or heal or whatever.
Alright, but when you add a new type of dice face, you would have to come here and update it… Or do you? With the power of tool scripts we are going to update that list to reflect the files that actually exist!
First of all, we are not going to use export, so it will be just:
var Type:String
And now we can export it from _get_property_list. There we can query the files. But before we do that, so we are clear what we have to do, the following code is equivalent to the export we had before:
func _get_property_list() -> Array:
return [
{
name = "Type",
type = TYPE_STRING,
hint = PROPERTY_HINT_ENUM,
hint_string = "Damage,Heal"
}
]
No undocumented stuff here.
Our task is to build that hint_string with the names of the files. And that looks like this:
const path := "res://"
func _get_property_list() -> Array:
var hint_string := ""
var directory := Directory.new()
if OK != directory.open(path) or OK != directory.list_dir_begin(true):
push_error("Unable to read path: " + path)
return []
var file_name := directory.get_next()
while file_name != "":
if not directory.current_is_dir() and file_name.get_extension() == "tres":
if hint_string != "":
hint_string += ","
hint_string += file_name
file_name = directory.get_next()
directory.list_dir_end()
return [
{
name = "Type",
type = TYPE_STRING,
hint = PROPERTY_HINT_ENUM,
hint_string = hint_string
}
]
Ah, yes, set the path constant to the path of the folder where the resources types you have are.
Addendum post edit
I want to elaborate on this example:
export(Resource) var n = preload("res://MyResourceScript.gd").new()
Here we are exporting a variable n as a Resource, which will appear in the Inspector panel. The variable is currently a Variant, we could type it Resource:
export(Resource) var n:Resource = preload("res://MyResourceScript.gd").new()
And then we don't need to tell Godot to export it as a Resource, because it is a Resource:
export var n:Resource = preload("res://MyResourceScript.gd").new()
Something else we can do is preload into a const. To be clear, preloads are resolved at parse time. Like this:
const MyResourceScript := preload("res://MyResourceScript.gd")
export var n:Resource = MyResourceScript.new()
This way, if you need to use the same script in multiple places, you don't need to repeat the path.
However, you might not need the path at all. If in the script res://MyResourceScript.gd we add a class_name (at the top of the script):
class_name MyResourceScript
Then we don't need to use preload at all. That name will be available everywhere, and you can just use it:
export var n:Resource = MyResourceScript.new()
Where is that resource stored?
Potentially nowhere. Above we are telling Godot to create a new one when our it initializes our object (e.g. which could be a Node, or another Resource - because, yes, Resources can have Resources) and those would only exist in RAM.
However, if you modify the Resource from the Inspector panel, Godot needs to store those changes somewhere. Now, if you are editing a Node, by default they go to the scene file. If you are editing another Resource, then it goes to wherever that Resource is stored. To be clear, scenes are resources too (PackedScene). And, yes, that means a file can have multiple Resources (A main resurce and sub-resources). You could also tell Godot to store the Resource in its own file from the Inspector panel. The advantage of giving a file to a Resource is in reusing it in multiple places (multiple scenes, for example).
So, a Resource could be stored in a file, or not stored at all. And a resource file could have a Resource alone, or it could also have sub-resources as well.
I'll take a moment to remind you that scenes can have instances of other scenes inside. So, there is no line between scenes and the so called "prefabs" in Godot.
… Did you know?
You can save the resources you created in runtime, using ResourceSaver. Which could be a way to save player progress, for example. You can also load them using load or ResourceLoader (in fact, load is a shorthand for ResourceLoader.load).
In fact, if you can use load or preload on something, it is a Resource. Wait a minute, we did this above:
const MyResourceScript := preload("res://MyResourceScript.gd")
Yep. The Script is a Resource. And yes, you can create that kind of resources in runtime too. Create a GDScript object (GDScript.new()), set its source_code, and reload it. Then you can attach it to an Object (e.g. a Node) with set_script. You can now start thinking of meta-programming, or modding support.

How to find a parent visual in WPF using VisualTreeHelper in F#?

(Newbie question). I have need to climb the visual tree of a wpf xaml until finding a parent Grid of x:Name "FindMe". Assuming I have a visual child of "FindMe" called "grid", also a grid, how would I use the VisualTreeHelper in F# ?
let parent = VisualTreeHelper.GetParent(grid) as Grid <-- clearly wrong!
Thanks in advance for any suggestions!
Just call VisualTreeHelper.GetParent() recursively until you find it.
let rec getParent dp =
match VisualTreeHelper.GetParent dp with
| null -> None
| :? Grid as grid when grid.Name = "FindMe" -> Some grid
| parent -> getParent parent
Obviously this function could be made more general by passing in the type and name of the control you're looking for.
There is no as operator in F# but you could try this:
let parent = VisualTreeHelper.GetParent(grid)
if parent :? Grid then
...
else
...
You could introduce a few type extensions to DependencyObject for climbing the visual tree parent hierarchy, then use them to search for a parent Grid with a specified name.
First define:
let inline isNull (x:^T when ^T : not struct) = obj.ReferenceEquals (x, null)
type DependencyObject with
member t.GetVisualParents() =
t |> Seq.unfold (fun state -> if isNull state then None else Some(state, VisualTreeHelper.GetParent(state)))
|> Seq.skip(1)
member t.GetVisualParents<'T when 'T :> DependencyObject>() =
System.Linq.Enumerable.OfType<'T>(t.GetVisualParents())
member t.GetVisualParentByName<'T when 'T :> FrameworkElement>(name) =
t.GetVisualParents<'T> ()|> Seq.filter (fun x -> x.Name = name) |> Seq.tryHead
And then you can do:
let name = "FindMe"
let parent = grid.GetVisualParentByName<Grid>(name)
Notes:
There is no Seq.ofType so I just used Enumerable.OfType<'T>(). For other options see System.Linq.Enumerable.OfType<T> - is there a F# way? and F# Equivalent to Enumerable.OfType<'a>.
Seq.tryHead was introduced in f# 4.0. If you are using an earlier version, you could use Enumerable.FirstOrDefault<'T> from System.Linq.

FSharp Charting and Winforms

I'm having some trouble combining F# Charting with Winforms.
I have a chart showing in the form fine initially, however I can't get it to "update" when the underlying data changes based on the GUI interaction.
recalcb.MouseClick
|> Event.add (fun evArgs ->
let chart1 = DataUpdate 1 |> Chart.BoxPlotFromData
let chart2 = DataUpdate 2 |> Chart.BoxPlotFromData
let urControl = new ChartControl(chart1, Dock=DockStyle.Fill, AutoSize=true)
let lrControl = new ChartControl(chart2, Dock=DockStyle.Fill, AutoSize=true)
urpanel.Controls.Add(urControl)
lrpanel.Controls.Add(lrControl)
printfn "tried to update"
)
The data is updating correctly (i.e. chart1 and chart2 show their updated values), but the chart isn't updated in the actual form.
I tried defining the control outside the event with initial data and calling Refresh() within the event but it doesn't help.
Here is a minimal example showing the issue (ideally the graph would update on each button press):
[<EntryPoint>]
[<STAThread>]
let main args =
let form = new Form(Visible = true, Width = 1310, Height = 730,FormBorderStyle=FormBorderStyle.FixedSingle,MaximizeBox = false)
let recalcb = new Button(AutoSize=true,Text="Recalculate")
form.Controls.Add(recalcb)
recalcb.MouseClick
|> Event.add (fun evArgs -> let nm = System.Random().Next(100)
let urControl = new ChartControl(Chart.Point[1 .. nm], Dock=DockStyle.Fill, AutoSize=true)
form.Controls.Add(urControl)
urControl.Refresh()
printfn "%A" nm
)
do Application.Run(form) |> ignore
0
UPDATE
Making the Chart a child of another control (i.e. Panel) and calling Clear() on the parent before readding will get this to work.

F# winform handling click events

Hi I am doing a simple 2d drawing program in f#. What i want to do is add a form click event where i will add a Type DrawObject to a list of DrawObject. But the event need to return a unit but when i try to add the DrawObject to my list it will return a Drawobject list, how can i solve this.
let DrawObjectList: DrawObject list = []
let mouseClick = new System.EventHandler(fun _ _ -> (new DrawObject(...))::DrawObjectList)
form.Click.AddHandler(mouseClick);
This is what i have done so far.

Drag and Drop in Silverlight with F# and Asynchronous Workflows

I'm trying to implement drag and drop in Silverlight using F# and asynchronous workflows.
I'm simply trying to drag around a rectangle on the canvas, using two loops for the the two states (waiting and dragging), an idea I got from Tomas Petricek's book "Real-world Functional Programming", but I ran into a problem:
Unlike WPF or WinForms, Silverlight's MouseEventArgs do not carry information about the button state, so I can't return from the drag-loop by checking if the left mouse button is no longer pressed. I only managed to solve this by introducing a mutable flag.
Would anyone have a solution for this, that does not involve mutable state?
Here's the relevant code part (please excuse the sloppy dragging code, which snaps the rectangle to the mouse pointer):
type MainPage() as this =
inherit UserControl()
do
Application.LoadComponent(this, new System.Uri("/SilverlightApplication1;component/Page.xaml", System.UriKind.Relative))
let layoutRoot : Canvas = downcast this.FindName("LayoutRoot")
let rectangle1 : Rectangle = downcast this.FindName("Rectangle1")
let mutable isDragged = false
do
rectangle1.MouseLeftButtonUp.Add(fun _ -> isDragged <- false)
let rec drag() = async {
let! args = layoutRoot.MouseMove |> Async.AwaitEvent
if (isDragged) then
Canvas.SetLeft(rectangle1, args.GetPosition(layoutRoot).X)
Canvas.SetTop(rectangle1, args.GetPosition(layoutRoot).Y)
return! drag()
else
return()
}
let wait() = async {
while true do
let! args = Async.AwaitEvent rectangle1.MouseLeftButtonDown
isDragged <- true
do! drag()
}
Async.StartImmediate(wait())
()
Thank you very much for your time!
The way to solve this issue is to use an overloaded AwaitEvent that allows you to wait for two events. Instead of just waiting for MouseMove, you can also wait for the MouseUp event - in the first case, you can continue moving and in the second case, you can return from the loop and stop drag&drop (this is actually discussed later in the book in section 16.4.5).
Here is the code - it actually uses AwaitObservable variant of the method (see below), which is a better choice in general, because it works with Observable.map and similar combinators (in case you wanted to use these).
let! args = Async.AwaitObservable(layoutRoot.MouseMove, layoutRoot.MouseUp)
match args with
| Choice1Of2(args) ->
// Handle the 'MouseMove' event with 'args' here
Canvas.SetLeft(rectangle1, args.GetPosition(layoutRoot).X)
Canvas.SetTop(rectangle1, args.GetPosition(layoutRoot).Y)
return! drag()
| Choice2Of2(_) ->
// Handle the 'MouseUp' event here
return()
As far as I know, the overloaded AwaitObservable method is not available in the F# libraries (yet), but you can get it from the book's web site, or you can use the following code:
// Adds 'AwaitObservable' that takes two observables and returns
// Choice<'a, 'b> containing either Choice1Of2 or Choice2Of2 depending
// on which of the observables occurred first
type Microsoft.FSharp.Control.Async with
static member AwaitObservable(ev1:IObservable<'a>, ev2:IObservable<'b>) =
Async.FromContinuations((fun (cont,econt,ccont) ->
let rec callback1 = (fun value ->
remover1.Dispose()
remover2.Dispose()
cont(Choice1Of2(value)) )
and callback2 = (fun value ->
remover1.Dispose()
remover2.Dispose()
cont(Choice2Of2(value)) )
// Attach handlers to both observables
and remover1 : IDisposable = ev1.Subscribe(callback1)
and remover2 : IDisposable = ev2.Subscribe(callback2)
() ))

Resources