GUI update patterns in F#

Viewed 64

I'm looking for the best way to propagate model updates to the GUI, using a "classic" (as in: non reactive functional) GUI toolkit: Terminal.GUI. Currently I have this code (simplified):

    type Tui(state: StateManager) =
        let state = state

        let window = Window(bla bla bla)

        let lblPath = Label(bla bla bla)

        let lstView =
            { new ListView(bla bla bla) with
                  member this.ProcessKey(k: KeyEvent) =

                      let updateViews() =
                          Application.MainLoop.Invoke(fun () ->
                            this.SetSource model.CurrentState.LstData
                            lblPath.Text <- ustr model.CurrentState.CurrPath)

                      match k.Key with
                      | Key.CursorRight -> 
                            state.changeTheState()
                            updateViews()
                            true
                      | _ -> true }
        do
            Application.Init()
            // add all GUI components: window.add(lblPath), etc
            Application.Run()
            // XXX repetition of updateViews() above!
            Application.MainLoop.Invoke(fun () ->
                lstView.SetSource model.CurrentState.LstData
                lblPath.Text <- ustr model.CurrentState.CurrPath)

The issue here is that the code to update the view components is duplicated. I believe this is because:

  • in that ProcessKey method of the ListView object expression I cannot access any external method of the Tui class (this is probably also because F# compiler is one-pass only (?))
  • outside that method I cannot access the updateView function

Is there a better way of doing this that would avoid code repetition? Am I using the wrong pattern GUI update pattern?

(Complete code is here)

1 Answers

Of course, it doesn't need to be too complicated - refactor your update to just take in a listview parameter:

let updateViews (lstView: ListView) =
      Application.MainLoop.Invoke(fun () ->
          lstView.SetSource state.CurrentState.LstData
          ...
      )

And inside the member definition, call:

updateViews(this)

And below, you can use updateViews lstView.

When you use an object expression, the type of the expression becomes the type you specify in new <type>, so any type augmentations you do inside won't make it outside. For a more OOP approach, declare an intermediate type:

[<AbstractClass>] 
type UpdateableList() = 
    inherit ListView([||]) 
    abstract member Update: unit -> unit

implement your update logic:

  { new UpdateableList(X = Pos.At(0), Y = Pos.At(2), ...) with
            member this.Update() =
                ...

and in your setup, you have access to a public method:

lstView.Update()
Related