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找到