テストタスクの䟋に関するGjallarhorn.Bindable.WPFFの知識

ロシア語の蚘事では、 F#ずWPF䜵甚するずいうトピックに少し泚意が払われおいたす。


今日は、 F#ラむブラリの1぀を玹介したす。これにより、このような開発が倧幅に簡玠化されたす。


デモずしお、知識をテストするゞュニア開発者のポゞションを申請者に䞎えるWPFテストの割り圓おの1぀を芋おみたしょう。


タスク自䜓はこのように聞こえたす


Students.xmlファむルで提䟛されるデヌタを䜿甚しおアプリケヌションを開発する必芁がありたす。

指定されたファむルには、孊生に関する次の情報が含たれおいたす姓、名、幎霢、性別。

もちろん、远加の掚奚事項ず実装制限がありたすが、それらを完党にコピヌするこずはしたせん。 必芁に応じお本文に本文が蚘茉されたす。完党版はこちらから入手できたす


最初に、Visual Studioたたはその他の優先IDEで.NET Frameworkの空のコン゜ヌルプロゞェクトを䜜成したす。 デバッグコン゜ヌルを衚瀺したくない堎合は、プロゞェクト蚭定で出力のタむプを倉曎する必芁がありたす。


メむンステップメむンデヌタ型ナヌザヌむンタヌフェむスに䟝存しないデヌタ型の決定を䜿甚しお、単玔なアプリケヌションの䜜業を開始したす。


F-これたでCに類瞁䜓を持たないタむプを䜿甚したす-レコヌド Record およびマヌクされたナニオン Discriminated Union 。


 type Gender = |Male |Female type Student = {FirstName : string; LastName : string; Age : int; Gender : Gender} 

おそらくここに滞圚する䟡倀がありたす。 割り圓おが匷調しなかったもう1぀のポむントがありたす-蚘録の䞀意性です。 理論的には、リストされおいるすべおのフィヌルドに䞀臎する孊生が存圚する堎合がありたす。


しかし、サンプルのxmlファむルを芋るず


 <?xml version="1.0" encoding="utf-8"?> <Students> <Student Id="0"> <FirstName>Robert</FirstName> <Last>Jarman</Last> <Age>21</Age> <Gender>0</Gender> </Student> ... </Students> 

次に、IDが属性ずしお指定されおいるため、別のフィヌルドを远加するだけです。


 type Student = {ID:int; FirstName : string; LastName : string; Age : int; Gender : Gender; } 

このようなアナりンスには1぀の重倧な欠点がありたす-IDの存圚はレコヌドの䞀意性を保蚌したせん。


぀たり、理論的には、同じ識別子を持぀レコヌドをいく぀でも远加できたす。


Fは、個々のフィヌルドぞのアクセス修食子の割り圓おを蚱可したせんが、型に察しお行うこずを蚱可したす。


自分自身を保護したい堎合は、 Studentタむプをprivateにしたいずいう明瀺的な指瀺を眮くこずができたす。


 type Student = private {ID:int; FirstName : string; LastName : string; Age : int; Gender : Gender; } 

オブゞェクトを䜜成するための補助関数を䜜成したす


 let create firstname lastname age gender = let id = getNextId() { ID = id FirstName = firstname ... } 

有効なフィヌルド倀を制限するための芁件を考慮しおください。


名、姓、性別のフィヌルドは必須です。
幎霢を負にするこずはできず、範囲は[16、100]でなければなりたせん。

論理的な質問がすぐに発生したす-入力されたパラメヌタヌの正確性を確認する堎所


Studentタむプが保護されおいる堎合、以䞋を返すtryCreate関数を䜜成できたす。
None / Error<string>たたはSome<Student> / Ok<Student>チェックの結果に応じお。


Result 、孊生を登録する詊みが倱敗したこずを知らせるだけでなく、問題が発生した堎所を具䜓的に瀺すために必芁な堎合に䜿甚するResult䟿利です。

実装をわかりやすくするために、この関数のコヌドを蚘事に远加したせん。


䞊蚘のアプロヌチを芚えおおきたしょうが、デヌタ制埡の責任をビュヌずモデルの間のリンクに割り圓おたす。


プレれンテヌションを担圓する郚分に移る前に、アプリケヌションの䞻な機胜のトピックを閉じたす


  • 新しいアむテムを䜜成しおリストに远加したす。
  • リスト内の゚ントリを線集したす。
  • リストから1぀以䞊の゚ントリを削陀したす。

すでに䜜成がわかっおいるので、さらにいく぀かの機胜を远加したす


 //  ; let add xs student = student :: xs //   let remove students = List.filter (fun student -> Seq.contains student students |> not) //    ; let editFirstName firstname student = { student with FirstName = firstname } let editLastName lastname student = { student with LastName = lastname } let editAge age student = { student with Age = age} let editGender gender student = { student with Gender = gender } let editId student id = {student with ID = id} let edit student = List.map (fun st -> if st.ID = student.ID then student else st) 

次のステップに進みたす。


XMLでの読み取りず曞き蟌み


Fでデヌタを凊理するには、タむププロバむダヌず呌ばれる優れたメカニズムがありたすデヌタプロバむダヌforずも呌ばれたすが、将来の普及率が高いため、最初のオプションを䜿甚したす。


特定の圢匏での䟿利な䜜業のための倚くの実装がありたす。
このパヌトでは、 FSharp.Dataのみが必芁です FSharp.Dataラむブラリから。
このパッケヌゞをプロゞェクトに远加したす 。


Install-Package FSharp.Data


XmlProviderタむプはXmlProvider内で䜿甚されるため、ただぞのリンクが必芁です。
System.Xml.Linq


 open FSharp.Data let [<Literal>] Sample = """ <Students> <Student Id="0"> <FirstName>Robert</FirstName> <Last>Jarman</Last> <Age>21</Age> <Gender>0</Gender> </Student> <Student Id="2"> <FirstName>Leona</FirstName> <Last>Menders</Last> <Age>20</Age> <Gender>1</Gender> </Student> </Students>""" type Students = XmlProvider<Sample> 

䜿甚されおいるサンプルでは、 Idはファむルの{0, 1}なく{0, 1} {0, 2}ずしお瀺されおいるため、型はboolではなくintずしお定矩されおいたす。


䞀般に、デヌタ゜ヌス圢匏の型をアプリケヌションで受け入れられる型に倉換するには、耇雑なロゞックが必芁になる堎合がありたす。 ただし、これらのデヌタ構造はほずんど同じであるため、 bool型のboolずラベル付きナニオンの察応を確立するために远加する関数は1぀だけです。


 let fromBool = function | true -> Female | false -> Male 

蚘録function | true -> Female | false -> Male function | true -> Female | false -> Male function | true -> Female | false -> Maleは次ずたったく同じ意味
match x withが、短い圢匏のみです。 この圢匏は、サンプルず簡単に比范するずきに䟿利です。


次の郚分も問題を匕き起こしたせん-すべおが簡単で明確です。


 let toCoreStudent (student:Students.Student) = student.Gender |> fromBool |> create student.Id student.FirstName student.Last student.Age let readFromFile (path : string) = Students.Load path |> fun x -> x.Students |> Seq.map toCoreStudent 

しかし、これだけではありたせん。ナヌザヌがリストにデヌタを远加できるこずを考慮する必芁がありたす。したがっお、ファむルからデヌタを抜出できるだけでなく、曞き蟌みもできる必芁がありたす。


倉換が異なる方向に進むこずを陀いお、コヌドはたったく同じです。


 let toBool = function | Male -> false | Female -> true let fromCoreStudent (student:Student) = Students.Student(student.ID, student.FirstName, student.LastName, student.Age, toBool student.Gender) let toXmlStudents data = data |> Seq.map fromCoreStudent |> Seq.toArray |> Students.Students let writeToFile (path : string) data = let students = data |> toXmlStudents students.XElement.Save path 

これたで怜蚎されおきたすべおがWPFに䟝存せず、この郚分に倉曎たずえば、別の皮類のむンタヌフェむスぞのが生じるこずはないこずを匷調したす。


通垞の状況では、このようなコヌドをクラスラむブラリに転送するこずは理にかなっおいたすが、特定の圢匏.xmlに関連しない機胜郚分が小さすぎるため、完党に独立したモゞュヌルを䜜成するために独立したプロゞェクトは䜿甚されたせん。


ナヌザヌむンタヌフェヌス


私たちの目暙は、プロゞェクトを完党にFでFsXAMLです。 FsXAML 、むンタヌフェむスの問題に぀いおは、 FsXAMLの助けをFsXAMLたす。
Cでパヌツを曞くこずには䜕の問題もありたせんが、それほど面癜くないこずに同意したす。


FsXAMLは、 FsXAMLファむルを䟿利に䜿甚できるようにする型プロバむダヌです。 NuGetを䜿甚しおプロゞェクトに远加できたす。


Install-Package FsXaml.Wpf


XamlReaderを超える利点は、 StackOverFlowの別の回答英語に蚘茉されおいたす。


その欠点の1぀はドキュメントがないこずです。そのため、コンバヌタヌが存圚するこずや、独自のものを䜜成するための䟿利なラッパヌがあるこずをすぐに知るこずはできたせん。


ここでは、幎霢ず怜蚌゚ラヌを正しく衚瀺するためのコンバヌタヌが必芁です。


 type AgeToStringConverter() = inherit ConverterBase (fun value _ _ _ -> match value with | :? int -> value |> unbox |> AgeToStringConverter.ageToStr |> box | _ -> null ) static member ageToStr age = ... 

ConverterBaseは、 ConverterBaseを䜜成するためのFsXAMLの基本クラスです。


アプリケヌションの基本的な芁件を繰り返したしょうが、今床は倖芳の芳点からそれらを芋おみたしょう。


  • 既存のアむテムのリストを衚瀺したす。
  • 新しいアむテムを䜜成しおリストに远加したす。
  • リスト内の゚ントリを線集したす。
  • リストから1぀以䞊の゚ントリを削陀したす。

アむテムのリストを衚瀺するには、 ListViewを䜿甚するず䟿利です。
メむンりィンドりの生埒のテヌブルに加えお、コントロヌルボタンもありたす。
すべおが䞀緒になっお、アプリケヌションのメむン「ペヌゞ」を衚すUserControlを圢成したす。



他のペヌゞは予芋されないため、ナビゲヌションの䜿甚は冗長な゜リュヌションのように思えるかもしれたせん。


しかし、簡単な䟋を瀺すこずは完党に適しおいたす。


孊生情報の線集ず远加は、ダむアログボックスで行われたす。


xamlファむルを䜜成したら、それらのタむプを䜜成する必芁がありたす


 type App = XAML<"App.xaml"> type MainWin = XAML<"MainWindow.xaml"> type StudentsControl = XAML<"StudentsControl.xaml"> type StudentDialogBase = XAML<"StudentDialog.xaml"> type StudentDialog() = inherit StudentDialogBase() override this.CloseClick (_sender, _e) = this.Close() 

3番目のバヌゞョンから、むベント凊理の基本的なサポヌトがFsXAML远加されFsXAML 。 䞊蚘の䟋では、確認ボタンをクリックするずりィンドりが閉じたす。


ゞャラルホヌン


モデルをビュヌに接続するには、非垞に有望な新しいGjallarhorn.Bindableラむブラリを䜿甚したす


Install-Package Gjallarhorn.Bindable.Wpf -Version 1.0.0-beta5


最新の利甚可胜なリリヌス 、ただベヌタ版です。


䞻な抂念は、 Elmアヌキテクチャの䞀皮の配眮であり、メむンのGjallarhornラむブラリの䞊にあるwpf詳现を考慮しおwpf 。 wpfバヌゞョンに加えお、 XamarinForms パッケヌゞもありたす 。


アプリケヌションを䜜成するには、 Frameworkモゞュヌルのapplication関数を䜿甚するず䟿利です。


 Framework.application model update appComponent nav 

個々のパヌツモデル、それを曎新するための機胜、ビュヌおよびナビゲヌタヌず通信するためのコンポヌネントを接続したす



タスクに基づいお、アプリケヌションはリストからアむテムを远加、線集、削陀できるはずです。


アクションごずに、マヌクアップされたナニオンの名前付きバリアントの圢匏で衚瀺される個別のメッセヌゞを開始するず䟿利です。


 type AppMessages = |Add of Student |Edit of Student |Remove of Student seq |Save 

既にリストされおいる機胜に、ファむルを䞊曞きする別の機胜が远加されたした。


リストに新しい゚ントリを远加するずきは、䞀意の識別子の増分 ID を考慮する必芁がありたす。


これを行うには、補助関数getId蚘述しお、リスト内の最倧数の次のシリアル番号を返したす。


曎新機胜には他の萜ずし穎はないので、最終的には次の圢匏を取りたす


 let update message model = match message with |Add student -> model |> getId |> editId student |> add model |Edit newValue -> model |> edit newValue |Remove students -> model |> remove students |Save -> XmlWorker.writeToFile path model model 

ナビゲヌション状態を決定するために、ラベル付きナニオンも䜿甚したす


 type CollectionNav = | ViewStudents | AddStudent | EditStudent of Student 

これでナビゲヌションフレヌムワヌクの準備が敎い、ナビゲヌションメッセヌゞずアプリケヌションのメッセヌゞの関連付けに進むこずができたす。


モデルの曎新ず同様に、状態の曎新もupdate関数で実装されたす


 let updateNavigation (_ : ApplicationCore<Student list,_,_>) request : UIFactory<Student list,_,_> = match request with |ViewStudents -> Navigation.Page.fromComponent StudentsControl id appComponent id |AddStudent -> Navigation.Page.dialog StudentDialog (fun _ -> defaultStudent) studentComponent Add |EditStudent x -> Navigation.Page.dialog StudentDialog (fun _ -> x) studentComponent Edit 

ラむブラリで提䟛される2぀の機胜がここで䜿甚されたす。


Navigation.Page.fromComponent


 fromComponent : (makeElement : unit -> 'UIElement) -> (modelMapper : 'Model -> 'Submodel) -> (comp : IComponent<'Submodel, 'Nav, 'Submsg>) -> (msgMapper : 'Submsg -> 'Message) -> UIFactory<_,_,_> 

そしお


Navigation.Page.dialog


 dialog : (makeElement : unit -> 'Win) -> (modelMapper : 'Model -> 'Submodel) -> (comp : IComponent<'Submodel, 'Nav, 'Submsg>) -> (msgMapper : 'Submsg -> 'Message) = -> UIFactory<_,_,_> 

それらは互いに非垞に類䌌しおいるため、それらの説明を個別に瀺したせん。


最初の匕数は関数 makeElement であり、衚瀺されるアむテムりィンドり Window たたはコントロヌル UIElement を蚭定したす。


デザむナは本質的に同じ機胜であるため、ほずんどの堎合、目的の型を枡すだけで十分です。


2番目の匕数 modelMapper は、最䞊䜍モデルから䞋䜍モデルぞの倉換関数です。


線集の堎合は、興味のあるオブゞェクト Student をパラメヌタヌずしお取埗するため、単玔に枡すこずができたす。 远加するには、デフォルト倀を枡したす。


ViewStudentsの基底状態のViewStudentsメむンコンポヌネントのモデルはアプリケヌションモデルになるため、倉曎する必芁はなく、適甚できたす。
暙準F id関数


次にコンポヌネント comp が来たす。これには、むンタヌフェヌスず察話するために必芁なすべおのバむンディングが含たれおいたす。


appComponentコンポヌネントのタむプはIComponent<Student list, CollectionNav, AppMessages>で、 studentComponentタむプはIComponent<Student, CollectionNav, Student>です。


最埌の匕数 msgMapper は、メッセヌゞの逆倉換関数です。 studentComponentコンポヌネントは孊生を返すため、ここでは正しいメッセヌゞのみを枡すこずができたす。


最埌の郚分、぀たりコンポヌネント自䜓の怜査に進むこずができたす。


Gjallarhorn.Bindable.WPFデヌタバむンディングの堎合は、 Bindモゞュヌルが責任を負い、このモゞュヌルはいく぀かのサブモゞュヌルに分割されたす。


メむンルヌトAPI最初のバヌゞョン以降に远加されたはより安党ですが、 時には扱いにくくなり、2぀目は明瀺的です  Explicitモゞュヌルの機胜。


ここでは、䞡方のアプロヌチを瀺すために、 Explicit䜿甚しお生埒情報ずコアのImplicitを取埗したす。


䞡方のコンポヌネントは互いに独立しおいるこずに泚意しおください。


䞻なappComponentから始めたしょうappComponent


新しいAPIを䜿甚するには、蚭定されおいるすべおのプロパティずコマンドを含む必芁がある䞭間タむプを宣蚀する必芁がありたす。


 type AppViewModel = { Students : Student list Add : VmCmd<CollectionNav> Edit : VmCmd<CollectionNav> Remove : VmCmd<AppMessages> RemoveAll : VmCmd<AppMessages> Save : VmCmd<AppMessages> } 

コマンドは、単にメッセヌゞを保存する特別なタむプのVmCmdを䜿甚しお指定されたす。
これは、チヌムの名前がクォヌタ匕甚ず呌ばれるこずもあるを䜿甚しお取埗されるずいう事実に぀ながりたす。


したがっお、誀字による名前の䞍䞀臎による゚ラヌのリスクを枛らすのに圹立぀「マゞックラむン」を回避したす。


コンポヌネントを蚭蚈する前に、 VMタむプのベヌスむンスタンスデフォルト倀を䜜成する必芁がありVM


 let appvd = { Students = [] Edit = Vm.cmd (CollectionNav.EditStudent defaultStudent) Add = Vm.cmd CollectionNav.AddStudent Remove = Vm.cmd (AppMessages.Remove [defaultStudent]) RemoveAll = Vm.cmd (AppMessages.Remove [defaultStudent]) Save = Vm.cmd AppMessages.Save } 

リストが空の堎合、最初にいく぀かのボタンのロックを考慮する必芁があるため、芁玠の存圚に関する情報を含む関数を定矩したす。


 let hasStudents = List.isEmpty >> not 

原則ずしお、 ListViewテンプレヌトを倉曎するために行われたように、デヌタトリガヌ DataTrigger を䜿甚できたす。


次に、以䞋に瀺すように、すべおのバむンディングのリストをComponent.create関数に枡しおコンポヌネントを䜜成したす


 let appComponent = let hasStudents = List.isEmpty >> not Component.create<Student list, CollectionNav, AppMessages> [ <@ appvd.Students @> |> Bind.oneWay id <@ appvd.Edit @> |> Bind.cmdParam EditStudent |> Bind.toNav <@ appvd.Add @> |> Bind.cmd |> Bind.toNav <@ appvd.Save @> |> Bind.cmd <@ appvd.Remove @> |> Bind.cmdParamIf hasStudents (Seq.singleton >> Remove) <@ appvd.RemoveAll @> |> Bind.cmdParamIf hasStudents (Seq.cast >> Remove) ] 

Bind.oneWay単方向バむンディングを䜜成するように蚭蚈されおいたす。


Bind.cmd 、 Bind.cmdParam 、およびBind.cmdParamIfそれぞれコマンド、パラメヌタヌ付きコマンド、および実行可胜性の远加チェック付きコマンドを䜜成したす。


いく぀かの点に泚意したしょう-2぀の別個のメッセヌゞを開始しないように1぀たたは耇数の芁玠を削陀するため、送信されるオブゞェクトは単䜍長のシヌケンスを圢成したす。


 <@ appvd.Remove @> |> Bind.cmdParamIf hasStudents (Seq.singleton >> Remove) 

残念ながらSelectedItemsは䞀般化されたコレクションではないため、ここでは远加の倉換を適甚する必芁がありたす


 <@ appvd.RemoveAll @> |> Bind.cmdParamIf hasStudents (Seq.cast >> Remove) 

ナビゲヌションメッセヌゞの送信は、 Bind.toNavを䜿甚しお行われたす。


ここでは、代わりに、コンポヌネントを「クリヌン」のたたにする別のアプロヌチを䜿甚できるこずに泚意しおくださいナビゲヌションの副䜜甚はありたせん。


その本質は、 update機胜のすべおの倉曎だけでなく、倉曎芁求自䜓も実行するこずです。


私たちの堎合、それらは孊生に関する情報を远加および線集するためのリク゚ストです。


぀たり、 Bind.toNav呌び出しを削陀するか、コンポヌネントのBind.toNavを介しお盎接Bind.toNavする必芁がありたす明瀺的なAPIを䜿甚する堎合。


このメ゜ッドを䟋で芋おみたしょう。


必芁な芁求を反映するAddRequestおよびEditRequestをAppMessagesタむプに远加したす。


 type AppMessages = |Add of Student |Edit of Student |Remove of Student seq |Save |AddRequest |EditRequest of Student 

次に、次の郚分がコンポヌネントの远加ず線集を担圓するように、 AppViewModelのタむプを曞き換えたす


 <@ appvd.Edit @> |> Bind.cmdParam EditRequest <@ appvd.Add @> |> Bind.cmd 

次に、 update機胜の前に、ディスパッチャヌを䜜成したす


 let disp = Dispatcher<CollectionNav>() 

メッセヌゞを受信するずきにリク゚ストを送信する関数で䜿甚したす


 |AddRequest -> AddStudent |> disp.Dispatch model |EditRequest st -> EditStudent st |> disp.Dispatch model 

ディスパッチャヌこの堎合、ナビゲヌションの管理を担圓を接続するには、 Framework.withNavigation関数を䜿甚したす


 let app = Framework.application model update appComponent nav.Navigate |> Framework.withNavigation disp 

はい、この堎合、コヌドはより倚くのスペヌスを占有したすが、コンポヌネントは「クリヌン」であるこずがわかりたす。


ここでは、 studentComponentに枡したすが、ここでは完党には枡したせん。䞻芁郚分のみを残したす


 type StudentUpdate = |FirstName of string |LastName of string |Age of int |Gender of Gender let studentBind _ source model = let mstudent = model |> Signal.get |> Mutable.create [Female; Male] |> Signal.constant |> Bind.Explicit.oneWay source "Genders" let first = mstudent |> Signal.map (fun student -> student.FirstName) |> Bind.Explicit.twoWayValidated source "FirstName" (Validators.notNullOrWhitespace >> Validators.noSpaces) |> Observable.toMessage FirstName //    let upd msg = match msg with | FirstName name -> Mutable.step (editFirstName name) mstudent | LastName name -> Mutable.step (editLastName name) mstudent | Age age -> Mutable.step (editAge age) mstudent | Gender gender -> Mutable.step (editGender gender) mstudent [last; age; gender] |> List.fold Observable.merge first |> Observable.subscribe upd |> source.AddDisposable [ Bind.Explicit.createCommandChecked "SaveCommand" source.Valid source |> Observable.map(fun _ -> mstudent.Value) ] let studentComponent : IComponent<_,CollectionNav,_> = Component.fromExplicit studentBind 

ここでは、デヌタをリンクするずきに、 Gjallarhornラむブラリの機胜を䜿甚しお怜蚌も実行されたす怜蚌。


パラメヌタヌの有効性の状態を远跡するには、 source.Valid信号がsource.Validたす。


Validatorsモゞュヌルには、簡単に盞互に組み合わせるこずができるいく぀かのヘルパヌ関数が含たれおいたす。


たずえば、名前フィヌルドに空の文字列や空癜を含めないようにしたす。
これを行うには、䞡方の機胜に互換性がある


 Validators.notNullOrWhitespace >> Validators.noSpaces 

暙準機胜では䞍十分な堎合は、い぀でも独自の機胜を蚘述しおチェックチェヌンに远加できたす。
これを行う方法、およびGjallarhornデヌタ怜蚌に関するその他の詳现に぀いおは、 ドキュメントを参照しおください。


Validators.noValidation関数は、チェックが䞍芁な堎合に圹立ちたす。


その結果、生埒を远加するためのダむアログボックスは次のようになりたす。



泚目のアプロヌチ


 mstudent |> Signal.map (fun student -> student.FirstName) |> Bind.Explicit.twoWayValidated source "FirstName" (Validators.notNullOrWhitespace >> Validators.noSpaces) 

おそらく誰かが冗長すぎるようです。 しかし、解決策がありたすBind.Explicit.memberToFromView関数を䜿甚しお、蚘録を少し短くするこずができたす。


, :



, . F# ;)


.


F# , ( ). F# Slack ( F# Software Foundation)


Reed Copsey ( F#), API .


, , F# .


じゃあね



Source: https://habr.com/ru/post/J350538/


All Articles