खोज…


परिचय

यह विषय दिखाता है कि WPF एप्लिकेशन में कार्यात्मक प्रोग्रामिंग का फायदा कैसे उठाया जाए। पहला उदाहरण Māris Krivtežs (संदर्भ निचले भाग में अनुभाग टिप्पणियां) द्वारा एक पोस्ट से आता है। इस परियोजना को दोबारा शुरू करने का कारण दुगना है:

1 \

2 \ _ Gjallarhorn कार्यान्वयन के लिए एक आसान संक्रमण के लिए जैसा दिखता है।

टिप्पणियों

लाइब्रेरी डेमो प्रोजेक्ट्स @GitHub

मैरिस क्रिवेत्स ने इस विषय पर दो महान पोस्ट लिखे:

मुझे लगता है कि इन XAML एप्लिकेशन शैलियों में से कोई भी कार्यात्मक प्रोग्रामिंग से बहुत लाभ नहीं उठाता है। मैं कल्पना करता हूं कि आदर्श अनुप्रयोग में वह दृश्य शामिल होगा जो घटनाओं और घटनाओं को वर्तमान दृश्य स्थिति को उत्पन्न करता है। सभी एप्लिकेशन लॉजिक को इवेंट्स और व्यू मॉडल को फ़िल्टर और मैनिपुलेट करके हैंडल किया जाना चाहिए, और आउटपुट में इसे एक नया व्यू मॉडल तैयार करना चाहिए जो कि व्यू पर वापस आ जाए।

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> , जिसमें IObservable<'a> प्रकार की एक प्रॉपर्टी EventStream है। इस मामले में हम जिन घटनाओं की सदस्यता लेना चाहते हैं, वे ScoringEvent

नियंत्रक एक कार्यात्मक तरीके से घटनाओं को संभालता है। इसका हस्ताक्षर Score -> ScoringEvent -> Score हमें दिखाता है कि जब भी कोई घटना होती है, तो मॉडल का वर्तमान मूल्य एक नए मूल्य में बदल जाता है। यह हमारे मॉडल को शुद्ध रहने की अनुमति देता है, हालांकि हमारा दृश्य मॉडल नहीं है।

एक eventHandler दृश्य की स्थिति को बदलने के लिए प्रभारी है। EventViewModelBase<'a> से EventViewModelBase<'a> हम EventValueCommand और EventValueCommandChecked का उपयोग EventValueCommand घटनाओं को आदेशों तक ले जा सकते हैं।

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 ) इंजेक्ट किया 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> को कार्यान्वित करते हैं, जो कार्यान्वयन को परिचित बना देगा (FSharp.ViewModule उदाहरण से EventStream संपत्ति याद रखें)। हमारे मॉडल का एकमात्र वास्तविक परिवर्तन अपडेट फ़ंक्शन के तर्कों का क्रम है। साथ ही, अब हम ईवेंट के बजाय संदेश शब्द का उपयोग करते हैं।

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

अतिरिक्त जानकारी और अधिक विस्तृत उदाहरण के लिए यह मुख्य अवधारणाओं को प्रस्तुत करता है, कृपया रीड कोपसे के पद को देखें। क्रिसमस ट्रीज़ परियोजना इस दृष्टिकोण के कुछ लाभों पर प्रकाश डालती है:

  • प्रभावी रूप से हमें (मैन्युअल रूप से) मॉडल के कस्टम संग्रह में मॉडल की प्रतिलिपि बनाने, उन्हें प्रबंधित करने और मैन्युअल रूप से परिणामों से वापस मॉडल का निर्माण करने की आवश्यकता है।
  • एक शुद्ध मॉडल को बनाए रखते हुए, संग्रह के भीतर अपडेट पारदर्शी तरीके से किए जाते हैं।
  • तर्क और दृष्टिकोण को दो अलग-अलग परियोजनाओं द्वारा होस्ट किया जाता है, चिंताओं को अलग करने पर जोर दिया जाता है।


Modified text is an extract of the original Stack Overflow Documentation
के तहत लाइसेंस प्राप्त है CC BY-SA 3.0
से संबद्ध नहीं है Stack Overflow