人類的號角

Gjallarhorn 庫中的核心型別實現 IObservable<'a>,這將使實現看起來很熟悉(記住 FSharp.ViewModule 示例中的 EventStream 屬性)。對我們的模型唯一真正的改變是更新函式的引數的順序。此外,我們現在使用術語 Message 而不是 Event

namespace ScoreLogic.Model

type Score = { ScoreA: int ; ScoreB: int }    
type ScoreMessage = IncA | DecA | IncB | DecB | NewGame

// Module showing allowed operations
[<CompilationRepresentation(CompilationRepresentationFlags.ModuleSuffix)>]
module Score =
    let zero = {ScoreA = 0; ScoreB = 0}
    let update msg score =
        match msg with
        | IncA -> {score with ScoreA = score.ScoreA + 1}
        | DecA -> {score with ScoreA = max (score.ScoreA - 1) 0}
        | IncB -> {score with ScoreB = score.ScoreB + 1}
        | DecB -> {score with ScoreB = max (score.ScoreB - 1) 0}
        | NewGame -> zero

為了使用 Gjallarhorn 構建 UI,我們建立了簡單的函式,稱為 Component,而不是使類支援資料繫結。在它們的建構函式中,第一個引數 source 的型別為 BindingSource(在 Gjallarhorn.Bindable 中定義),用於將模型對映到檢視,將檢視中的事件對映回訊息。

namespace ScoreLogic.Model

open Gjallarhorn
open Gjallarhorn.Bindable

module Program =

    // Create binding for entire application.
    let scoreComponent source (model : ISignal<Score>) =
        // Bind the score to the view
        model |> Binding.toView source "Score"

        [
            // Create commands that turn into ScoreMessages
            source |> Binding.createMessage "NewGame" NewGame 
            source |> Binding.createMessage "IncA" IncA
            source |> Binding.createMessage "DecA" DecA
            source |> Binding.createMessage "IncB" IncB
            source |> Binding.createMessage "DecB" DecB
        ]

當前實現與 FSharp.ViewModule 版本的不同之處在於,兩個命令尚未正確實現 CanExecute。還列出了應用程式的管道。

namespace ScoreLogic.Model

open Gjallarhorn
open Gjallarhorn.Bindable

module Program =

    // Create binding for entire application.
    let scoreComponent source (model : ISignal<Score>) =
        let aScored = Mutable.create false
        let bScored = Mutable.create false

        // Bind the score itself to the view
        model |> Binding.toView source "Score"

        // Subscribe to changes of the score
        model |> Signal.Subscription.create 
            (fun currentValue -> 
                aScored.Value <- currentValue.ScoreA > 0
                bScored.Value <- currentValue.ScoreB > 0) 
            |> ignore

        [
            // Create commands that turn into ScoreMessages
            source |> Binding.createMessage "NewGame" NewGame 
            source |> Binding.createMessage "IncA" IncA
            source |> Binding.createMessageChecked "DecA" aScored DecA
            source |> Binding.createMessage "IncB" IncB
            source |> Binding.createMessageChecked "DecB" bScored DecB
        ]

    let application = 
        // Create our score, wrapped in a mutable with an atomic update function
        let score = new AsyncMutable<_>(Score.zero)

        // Create our 3 functions for the application framework

        // Start with the function to create our model (as an ISignal<'a>)
        let createModel () : ISignal<_> = score :> _

        // Create a function that updates our state given a message
        // Note that we're just taking the message, passing it directly to our model's update function,
        // then using that to update our core "Mutable" type.
        let update (msg : ScoreMessage) : unit = Score.update msg |> score.Update |> ignore

        // An init function that occurs once everything's created, but before it starts
        let init () : unit = ()

        // Start our application
        Framework.application createModel init update scoreComponent

留下設定解耦檢視,結合 MainWindow 型別和邏輯應用程式。

namespace ScoreBoard.Views

open System

open FsXaml
open ScoreLogic.Model

// Create our Window
type MainWindow = XAML<"MainWindow.xaml"> 

module Main =    
    [<STAThread>]
    [<EntryPoint>]
    let main _ =  
        // Run using the WPF wrappers around the basic application framework    
        Gjallarhorn.Wpf.Framework.runApplication System.Windows.Application MainWindow Program.application

這總結了核心概念,有關其他資訊和更詳細的示例,請參閱 Reed Copsey 的帖子 。該聖誕樹專案凸顯了幾個這種方法的好處:

  • 有效贖回我們從需要(手動)模式複製到檢視模型的自定義集合,管理它們,並手動構建模型與結果反饋。
  • 集合內的更新以透明的方式完成,同時保持純模型。
  • 邏輯和檢視由兩個不同的專案託管,強調關注點的分離。