人类的号角
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 的帖子 。该圣诞树项目凸显了几个这种方法的好处:
- 有效赎回我们从需要(手动)模式复制到视图模型的自定义集合,管理它们,并手动构建模型与结果反馈。
- 集合内的更新以透明的方式完成,同时保持纯模型。
- 逻辑和视图由两个不同的项目托管,强调关注点的分离。