F#
Введение в WPF в F #
Поиск…
Вступление
В этом разделе показано, как использовать функциональное программирование в приложении WPF . Первый пример - это сообщение от Māris Krivtežs (см. Раздел « Примечания » внизу). Причина пересмотра этого проекта двоякая:
1 \ Конструкция поддерживает разделение проблем, в то время как модель поддерживается чистой, а изменения распространяются функционально.
2 \ Сходство приведет к легкому переходу на реализацию Gjallarhorn.
замечания
Демо-проекты библиотеки @GitHub
- FSharp.ViewModule (под FsXaml)
- Gjallarhorn (ref Образцы)
Марис Кривтеж написал две большие посты на эту тему:
- Приложение F # XAML - MVVM и MVC, где выделены плюсы и минусы обоих подходов.
Я чувствую, что ни один из этих стилей приложений XAML не выигрывает от функционального программирования. Я предполагаю, что идеальное приложение будет состоять из представления, которое создает события и события, имеющие текущее состояние представления. Вся прикладная логика должна обрабатываться путем фильтрации и манипулирования событиями и моделью представления, а на выходе она должна создать новую модель представления, которая привязана к представлению.
- F # XAML - MVVM с ведомым событием, рассмотренным выше.
FSharp.ViewModule
Наше демо-приложение состоит из табло. Модель оценки - неизменная запись. События табло содержатся в типе Союза.
namespace Score.Model
type Score = { ScoreA: int ; ScoreB: int }
type ScoringEvent = IncA | DecA | IncB | DecB | NewGame
Изменения распространяются путем прослушивания событий и соответственно изменения модели представления. Вместо добавления членов в тип модели, как и в ООП, мы объявляем отдельный модуль для размещения разрешенных операций.
[<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>
из EventViewModelBase<'a>
, которая имеет свойство EventStream
типа IObservable<'a>
. В этом случае события, которые мы хотим подписаться, имеют тип 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) - это место, где все сведено вместе, т.е. функция обновления ( controller
) вводится в MainViewModel
.
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 .
Gjallarhorn
Основные типы библиотеки Gjallarhorn реализуют IObservable<'a>
, что сделает реализацию знакомой (помните свойство EventStream
из примера FSharp.ViewModule). Единственное реальное изменение нашей модели - это порядок аргументов функции обновления. Кроме того, теперь мы используем термин « Сообщение вместо события» .
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, вместо того, чтобы создавать классы для поддержки привязки данных, мы создаем простые функции, называемые 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
Это подводит итог основным концепциям, дополнительной информации и более сложному примеру, пожалуйста, обратитесь к сообщению Рида Копси . Проект « Рождественские деревья » подчеркивает пару преимуществ такого подхода:
- Эффективно избавляя нас от необходимости (вручную) копировать модель в пользовательскую коллекцию моделей взглядов, управлять ими и вручную конструировать модель из результатов.
- Обновления в коллекциях выполняются прозрачным образом, сохраняя чистую модель.
- Логика и представление организованы двумя разными проектами, что подчеркивает разделение проблем.