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>,我们可以使用 EventValueCommandEventValueCommandChecked 将事件连接到命令。

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>

我不会深入研究 XAML 文件,因为它是基本的 WPF 内容,整个项目可以在 GitHub找到