FSharp.ViewModule
我们的演示应用程序包含一个记分板。分数模型是不可变的记录。记分板事件包含在联合类型中。
namespace Score.Model
type Score = { ScoreA: int ; ScoreB: int }
type ScoringEvent = IncA | DecA | IncB | DecB | NewGame
通过侦听事件并相应地更新视图模型来传播更改。我们声明一个单独的模块来托管允许的操作,而不是像在 OOP 中那样向模型类型添加成员。
[<CompilationRepresentation(CompilationRepresentationFlags.ModuleSuffix)>]
module Score =
let zero = {ScoreA = 0; ScoreB = 0}
let update score event =
match event 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
我们的视图模型来自 EventViewModelBase<'a>
,它具有 IObservable<'a>
类型的属性 EventStream
。在这种情况下,我们要订阅的事件是 ScoringEvent
类型。
控制器以功能方式处理事件。它的标志性 Score -> ScoringEvent -> Score
告诉我们,每当事件发生时,模型的当前值将转换为新值。这允许我们的模型保持纯净,尽管我们的视图模型不是。
一个 eventHandler
负责改变视图的状态。继承 EventViewModelBase<'a>
,我们可以使用 EventValueCommand
和 EventValueCommandChecked
将事件连接到命令。
namespace Score.ViewModel
open Score.Model
open FSharp.ViewModule
type MainViewModel(controller : Score -> ScoringEvent -> Score) as self =
inherit EventViewModelBase<ScoringEvent>()
let score = self.Factory.Backing(<@ self.Score @>, Score.zero)
let eventHandler ev = score.Value <- controller score.Value ev
do
self.EventStream
|> Observable.add eventHandler
member this.IncA = this.Factory.EventValueCommand(IncA)
member this.DecA = this.Factory.EventValueCommandChecked(DecA, (fun _ -> this.Score.ScoreA > 0), [ <@@ this.Score @@> ])
member this.IncB = this.Factory.EventValueCommand(IncB)
member this.DecB = this.Factory.EventValueCommandChecked(DecB, (fun _ -> this.Score.ScoreB > 0), [ <@@ this.Score @@> ])
member this.NewGame = this.Factory.EventValueCommand(NewGame)
member __.Score = score.Value
文件后面的代码(* .xaml.fs)是将所有内容放在一起的地方,即在 MainViewModel
中注入更新函数(controller
)。
namespace Score.Views
open FsXaml
type MainView = XAML<"MainWindow.xaml">
type CompositionRoot() =
member __.ViewModel = Score.ViewModel.MainViewModel(Score.Model.Score.update)
CompositionRoot
类型用作 XAML 文件中引用的包装器。
<Window.Resources>
<ResourceDictionary>
<local:CompositionRoot x:Key="CompositionRoot"/>
</ResourceDictionary>
</Window.Resources>
<Window.DataContext>
<Binding Source="{StaticResource CompositionRoot}" Path="ViewModel" />
</Window.DataContext>