2

What is the accepted way in Elmish.WPF to have a Binding.cmd calling an async computation expression (CE) allow the delayed result of the async CE change the shared top-level model?

I want to do this without causing the UI thread to hang or starve (though having it somehow show busy is fine).

I tried having a part of the Model be mutable and mutating just that part of the record inside the async CE. This failed probably because the async CE is operating on its own copy of the Model.

Is there a way to pass a message with the delayed new value up to the top-level update function and update the global shared Model?

The functioning test code is in a repo here: make 'FandCo_SingleCounter` the Startup Project to run and test in VS2022

The important bits of my code is:

MainWindow.XAML fragment:

...begin of <Window>...
    <StackPanel Orientation="Horizontal" HorizontalAlignment="Center" VerticalAlignment="Top" Margin="0,10,0,0">
        <TextBlock Text="{Binding DISPLAY_state}" Width="110" Margin="0,5,10,5" />
        <Button Command="{Binding CMD_get_state}" Content="DO IT!" Width="50" Margin="0,5,10,5" />
    </StackPanel>
...to end of </Window>...

Program.fs fragment(s):

type Model =
  { mutable DISPLAY_state: string
   }

type Msg =
  | CMD_get_state

let init =
  { DISPLAY_state = "foo"
   }

let ASYNC_get_state (m: Model) :Model =
  async {
      Console.WriteLine( "async 0: "
                          + System.DateTime.Now.Millisecond.ToString()
                          + " - "
                          + m.DISPLAY_url_state)
      m.DISPLAY_url_state <- "bar"
      Console.WriteLine( "async +: "
                          + System.DateTime.Now.Millisecond.ToString()
                          + " - "
                          + m.DISPLAY_state)
      Threading.Thread.Sleep(5000)
      Console.WriteLine( "async ++: "
                          + System.DateTime.Now.Millisecond.ToString()
                          + " - "
                          + m.DISPLAY_state)
     }
     |> Async.StartImmediate
  Console.WriteLine( "m.Display_url_state 0: "
                 + System.DateTime.Now.Millisecond.ToString()
                 + " - "
                 + m.DISPLAY_state)
  Console.WriteLine( "m.Display_state 1: "
                 + System.DateTime.Now.Millisecond.ToString()
                 + " - "
                 + m.DISPLAY_state)
  { m with DISPLAY_state = "baz" }

let update msg m =
  | CMD_get_state => ASYNC_get_state m

let bindings () : Binding<Model, Msg> list = [
    "DISPLAY_url_state" |> Binding.oneWay (fun m -> m.DISPLAY_url_state)
    "CMD_get_url" |> Binding.cmd CMD_get_url
  ]
...etc...to end of Elmish.WPF Core F# file.

The result of this is:

async 0: 275 - foo
async +: 591 - bar
async ++: 160 - True
m.Display_state 0: 164 - True
m.Display_state 1: 167 - True
[13:26:18 VRB] New message: CMD_get_state
Updated state:
{ DISPLAY_url_state = "baz" }
[13:26:18 VRB] [main] PropertyChanged DISPLAY_state
[13:26:18 VRB] [main] TryGetMember DISPLAY_state

At the end DISPLAY_state binding in the MainWindow.XAML updates to the value baz and not the desired value bar.

How is this supposed to be done?

rfreytag
  • 955
  • 1
  • 8
  • 24
  • 1
    I am not going to look into this, but have a look at the DemoAsync project in my repo [here](https://github.com/BentTranberg/ElmishXmas). I think it likely is what you're looking for. – Bent Tranberg Apr 14 '22 at 19:42
  • 1
    I am looking through your ElmishXmas (thank you), and am struck (and I am not at all speaking about *your* work!) that so much of Elmish.WPF just works so well that I expected the answer to my question to be more evident in the abundant documentation. – rfreytag Apr 15 '22 at 01:50
  • 1
    Very understandable. I believe the documentation for [Elmish.WPF](https://github.com/elmish/Elmish.WPF), so far at least, has had to be limited to the specifics of this particular implementation of [Elmish](https://elmish.github.io/elmish/) for the most part, or else the scope would be far too big. There are other implementations, such as [Bolero](https://fsbolero.io/) and the [SAFE](https://safe-stack.github.io/) stack with Fable that also has interesting documentation. By far the most interesting documentation I've found is [The Elmish Book](https://zaid-ajaj.github.io/the-elmish-book). – Bent Tranberg Apr 15 '22 at 04:28
  • You can also find a lot of interesting stuff in the open and the closed issues of the Elmish.WPF repo and other repos such as Bolero, though it's a bit time-consuming to dig out information that way. It was my intention to create more demos and learning material, but my full-time job requires too much attention, so it has been precious little so far. – Bent Tranberg Apr 15 '22 at 04:37
  • Googling code I found in your code led me to [The Elmish Book - Commands section](https://zaid-ajaj.github.io/the-elmish-book/#/chapters/commands/commands). That helped me finally make sense of [Elmish.WPF - Command Bindings](https://github.com/elmish/Elmish.WPF/blob/master/TUTORIAL.md#command-bindings). Would you like some edits to your Docs in case a rookie perspective would help others? Thank you for pointing me in the right direction Brent! – rfreytag Apr 18 '22 at 17:08

1 Answers1

3

I am the maintainer of Elmish.WPF.

Is there a way to pass a message with the delayed new value up to the top-level update function and update the global shared Model?

This is not exactly a question specific to Elmish.WPF in the sense that all derivates of Elmish (and in fact all MVU architectures) have to provide a way to appropriately execute async code that returns a message.

There are two ways to do this with Elimsh (and thus Elmish.WPF):

  1. A subscription. See the SubModel sample and especially this line.
  2. An (Elmish) command. See the FileDialogs sample and especially these lines.

The Elmish.WPF binding named cmd has nothing to do with this. This binding is named after the WPF interface ICommand.

Tyson Williams
  • 1,630
  • 15
  • 35