Elm --- 階層化

下記記事にてElmを用いてカウンタを実装しました。
Elm --- Model、View、Update - 何でもプログラミング

今回はこのカウンタを再利用して、複数のカウンタを配置してみます。

内容はElmのTutorialにあるものとほとんど同じです。

作成するアプリケーション

カウンタが2つあり、一番下に合計値が出力されるアプリケーションになります。
f:id:any-programming:20170302193737p:plain

Counter.elm

新たにCounter.elmファイルを作成し、下記コードを記述します。

内容は上記記事のものとほぼ同じです。

VisualStudioCodeを利用しているのですが、関数の型宣言をしないと警告が出るようになっていました。

module Counter exposing (..)

import Html exposing (Html, div, button, text)
import Html.Events exposing (onClick)

type alias Model = { count : Int }

initialModel : Model
initialModel = { count = 0 }

type Msg 
    = Increment 
    | Decrement

update : Msg -> Model -> Model
update msg model =
  case msg of
    Increment ->
      { model | count = model.count + 1 }

    Decrement ->
      { model | count = model.count - 1 }

view : Model -> Html Msg
view model =
  div []
    [ button [ onClick Decrement ] [ text "-" ]
    , div [] [ text (toString model.count) ]
    , button [ onClick Increment ] [ text "+" ]
    ]


Main.elm

CounterのModel、update、viewをそのまま利用しています。

Html.App.mapを利用して、CounterのメッセージをMainの方に伝搬しています。

beginnerProgramがいつの間にかHtml.Appに移動していました。

import Html exposing (Html, div, text)
import Html.App exposing (beginnerProgram, map)
import Counter

type alias Model = 
    { counter1 : Counter.Model 
    , counter2 : Counter.Model
    }

initialModel : Model 
initialModel =
    { counter1 = Counter.initialModel
    , counter2 = Counter.initialModel
    }

type Msg 
    = Counter1Msg Counter.Msg 
    | Counter2Msg Counter.Msg

update : Msg -> Model -> Model
update msg model =
  case msg of
    Counter1Msg x ->
      { model | counter1 = Counter.update x model.counter1 }

    Counter2Msg x ->
      { model | counter2 = Counter.update x model.counter2 }

view : Model -> Html Msg
view model =
  div []
    [ map Counter1Msg (Counter.view model.counter1)
    , map Counter2Msg (Counter.view model.counter2)
    , div [] [ text (toString (model.counter1.count + model.counter2.count)) ]
    ]

main : Program Never
main =
  beginnerProgram 
  { model = initialModel
  , view = view
  , update = update 
  }


主要部分

// CounterのModelを保持
type alias Model = 
    { counter1 : Counter.Model 

// メッセージの一つをCounterのMsg型に
type Msg 
    = Counter1Msg Counter.Msg 

// CounterのMsg型が来たらCounterのupdateを実行
update msg model =
  case msg of
    Counter1Msg x ->
      { model | counter1 = Counter.update x model.counter1 }

// Counterのviewを利用し、mapにてメッセージを受け取り
view model =
  div []
    [ map Counter1Msg (Counter.view model.counter1)


階層化よりコントロール

公式でもあまりネストさせるのは推奨しておりません。

Mainの方で子要素のupdateやmsgを操作するよりは、Htmlのコントロール化(今回であればHtml.counterみたいなのを準備)したほうがフラットになってよい気がします。





リフレクション 逆引き (F#)

F#でリフレクションを利用するときに、やり方を忘れていることがよくあるため、ここを備忘録にしたいと思います。

今後適宜追加していこうと思います。

Type取得

let t = typeof<int>


TypeDefinition取得

let t = typedefof<List<_>>


TypeDefinitionからType作成

let t = typedefof<List<_>>.MakeGenericType(typeof<int>) // List<int>


TypeからTypeDefinition取得

typeof<List<int>>.GetGenericTypeDefinition() = typedefof<List<_>> // true


Cast可能かどうか

typeof<IEnumerable<int>>.IsAssignableFrom(typeof<List<int>>) // true


static classかどうか

let t = typeof<Enumerable>
t.IsAbstract && t.IsSealed // true


ロードされているAssembly全て取得

let assemblies = System.AppDomain.CurrentDomain.GetAssemblies()


オーバーロードかつジェネリックな関数を取得

現状GetMethods()して絞り込む方法しかなさそうです。

下記はSystem.Linq.Enumerable.Selectの一つを取得しています。

let selectMethod = 
    typeof<Enumerable>.GetMethods(BindingFlags.Static ||| BindingFlags.Public)
    |> Array.find (fun x -> 
        let parameters = 
            x.GetParameters()
            |> Array.map (fun x -> x.ParameterType.GetGenericTypeDefinition())
            |> Array.toList           
        x.Name = "Select" && parameters = [ typedefof<IEnumerable<_>>; typedefof<Func<_, _>> ])


拡張メソッドかどうか

let method = typeof<Enumerable>.GetMethod("All")
method.IsDefined(typeof<ExtensionAttribute>, true) // true


Cast可能な型一覧取得

ベースクラスはBaseTypeで、インターフェースはGetInterfaces()で取得します。

インターフェースの継承関係はツリー構造になるため、flatTreeを用意しています。

let assignableTypes (type_ : Type) = 
    let flatTree getChildren root =
        [ root ]
        |> List.unfold (function | []   -> None
                                 | h::t -> Some (h, t @ getChildren h)) 
    type_
    |> List.unfold (Option.ofObj >> Option.map (fun x -> x, x.BaseType))
    |> List.collect (flatTree (fun x -> x.GetInterfaces() |> Array.toList))
    |> List.distinct 


UnionCaseの名前取得

let caseName (x : obj) =
    FSharpValue.GetUnionFields(x, x.GetType()) |> fst |> (fun x -> x.Name)






F#でWPF --- Resource、Content、EmbeddedResource

F#でWPFプログラミングをする際に、XamlファイルのBuild Actionをいくつか選ぶことができます。

今回はResource、Content、EmbeddedResource各々でのXamlの読み込み方を記述していきます。

Resource

f:id:any-programming:20170228161625p:plain

WPFにのみ提供されるBuild Actionです。

対象のリソースを実行ファイルに埋め込みます。

リソースを読み込む際は、GetResourceStreamを利用します。

Application.GetResourceStream(Uri("Resource.xaml", UriKind.Relative)).Stream

Xamlファイルを読み込む際は、LoadComponentが利用できます。

Application.LoadComponent(new Uri("Resource.xaml", UriKind.Relative))


Content

f:id:any-programming:20170228161954p:plain

WPFにのみ提供されるBuild Actionです。

対象のリソースを外部ファイルのまま利用します。

"Copy to Output Directory"を"Copy always"か"Copy if newer"にしておくと、ビルドの際に出力ディレクトリにコピーしてくれます。

F#で利用する際には、AssemblyInfo.fsに対象ファイル分、下記を追加する必要があります。(C#では勝手に追加してくれるようです。)

open System.Windows.Resources
[<assembly: AssemblyAssociatedContentFile("Content.xaml")>]

リソースを読み込む際は、GetContentStreamを利用します。

Application.GetContentStream(Uri("Content.xaml", UriKind.Relative)).Stream

Xamlファイルを読み込む際は、LoadComponentが利用できます。

Application.LoadComponent(new Uri("Content.xaml", UriKind.Relative))


EmbeddedResource

f:id:any-programming:20170228162813p:plain

対象のファイルを実行ファイルに埋め込みます。

リソースを読み込む際は、GetManifestResourceStreamを利用します。

Assembly.GetExecutingAssembly().GetManifestResourceStream("Embedded.xaml")

Xamlファイルを読み込む際は、LoadComponentが使えないため、XamlReader.Loadを利用します。

XamlReader.Loadを利用するため、System.Xmlを参照に追加します。

open System.Windows.Markup

let stream = Assembly.GetExecutingAssembly().GetManifestResourceStream("Embedded.xaml");
XamlReader.Load(stream)


格納場所

GetManifestResourceNamesを利用することで、アセンブリ内のリソース名一覧が取得できます。

EmbeddedResourceでビルドされたものは、この中に格納されています。

Assembly.GetExecutingAssembly().GetManifestResourceNames() 

このリソースの中に、"アセンブリ名.g.resources"という名前のものがあります。

Resourceでビルドされたものは、さらにこの中に格納されています。

let asm = Assembly.GetExecutingAssembly()
let resources = asm.GetManifestResourceStream(asm.GetName().Name + ".g.resources")
let names = 
    new ResourceReader(resources)
    |> Seq.cast<DictionaryEntry> 
    |> Seq.iter (fun x -> x.Key)


ResourceとEmbeddedResource

WPFで利用するならResourceが基本でいいと思います。

ただ、UserControl内でLoadComponentしたものを、ほかのXaml内で利用しようとすると、Resourceだとデザイナが動作しなくなるため、xamlはEmbeddedResourceのほうがいいかもしれません。

Page

Build ActionにPageというものがあり、これはXamlをBaml(コンパイルされたXaml)に変換するものなのですが、F#では動作させる方法がわかりませんでした。





Visual Studioと通信(F#)

通常、VisualStudioに機能を追加する際は、VSIXを作成してインストールします。

今回は外部からVisualStudioと通信してみます。

VisualStudioを見つける(DTEの取得)

RunningObjectTableからDTEのオブジェクトを取得しています。

DTEはIDE以外のも取れることがあります。

open System
open System.Runtime.InteropServices
open System.Runtime.InteropServices.ComTypes
open EnvDTE

[<DllImport("ole32.dll")>]
extern int GetRunningObjectTable(int reserved, IRunningObjectTable& pprot)

let mutable runningObjectTable = null
if GetRunningObjectTable(0, &runningObjectTable) <> 0 then
    failwith "GetRunningObjectTable failed"

let mutable enumMoniker = null
runningObjectTable.EnumRunning(&enumMoniker)

let monikers = 
    [ let moniker = [| null |]
        while enumMoniker.Next(1, moniker, IntPtr.Zero) = 0 do
        yield moniker.[0] ]

let getDte (moniker : IMoniker) =
    let mutable dte = null
    if runningObjectTable.GetObject(moniker, &dte) <> 0 then
        failwith "GetObject failed"
    match dte with
    | :? DTE as x -> Some x
    | _           -> None

let dtes = monikers |> List.choose getDte


テキストを送ってみる

"Target"ソリューションの現在のカーソルの位置に"hello"を埋め込んでいます。

DTEのリストから対象のIDEを取得する方法は自由に変更してください。

let dte = dtes |> List.find (fun x -> IO.Path.GetFileNameWithoutExtension(x.Solution.FileName) = "Target")

let document = dte.ActiveDocument.Object() :?> TextDocument

document.Selection.ActivePoint.CreateEditPoint().Insert("hello")
f:id:any-programming:20170227180520p:plain f:id:any-programming:20170227180530p:plain






F# --- TypeProvider --- DependencyProperty自動実装

下記記事にてTypeProviderを作成する方法を記述しました。
F# --- TypeProvider --- クイックスタート - 何でもプログラミング

今回は応用としてDependencyPropertyを自動実装するTypeProviderを作成してみます。

結果としてあまり実用的ではないものであるため、TypeProviderの実装方法の参考に利用してください。

作成するTypeProvider

パラメータは、ベースクラス名とプロパティ定義の二つの文字列になります。

プロパティ定義はスペース区切りで型と名前を記述します。

Typeを直接渡す方法がわからなかったため、文字列でのやり取りをしています。

もしTypeを渡す方法がわかりましたら追記いたします。

type MyControlBase = DependencyProperty.Base<"System.Windows.Controls.UserControl", 
                                             "System.Int32  Value1 
                                              System.Double Value2">


パラメータを受け取るTypeProviderの実装

今回は実際の型が必要なので、生成型のTypeProviderを作成します。

ProvidedTypeDefinitionのIsErasedをfalseに設定します。

let asm = Assembly.GetExecutingAssembly()
let ns = "DependencyProperty"
let defType = ProvidedTypeDefinition(asm, ns, "Base", None, IsErased = false)

do  defType.DefineStaticParameters(
        [ ProvidedStaticParameter("baseClass",  typeof<string>) 
            ProvidedStaticParameter("properties", typeof<string>) ],
        (fun typeName args -> // 型定義))

    this.AddNamespace(ns, [ defType ])


型の作成

こちらもIsErasedをfalseに設定します。

また型をdllに出力する必要があるため、ProvidedAssemblyで一時dllを指定します。(一つの型につき一つのdllを利用するため、利用する際はGetTempPath等を利用してください。)

文字列からの型検索は、現状5つのdllから行うようになっています。(この型検索は力技なので、よりよい方法が見つかれば追記いたします。)

let findType name = 
    [ typeof<System.Object>.Assembly                 // mscorlib
      typeof<System.Windows.Input.ICommand>.Assembly // System
      typeof<System.Windows.Point>.Assembly          // WindowsBase
      typeof<System.Windows.Media.Color>.Assembly    // PresentationCore
      typeof<FrameworkElement>.Assembly ]            // PresentationFramework
    |> Seq.map (fun x -> x.GetType(name))
    |> Seq.tryFind ((<>) null)
    |> function | Some x -> x
                | None   -> failwith (name + " doesn't exist")

let baseClass = findType (args.[0] :?> string)

let t = ProvidedTypeDefinition(asm, ns, typeName, Some baseClass, IsErased = false)
ProvidedAssembly("c:/src/temp.dll").AddTypes([ t ])


DependencyPropertyの定義

ProvidedFieldのIsStaticが読み取り専用のため、SetFieldAttributesでstaticに設定しています。

let properties =
    args.[1] :?> string
    |> fun x -> x.Split(Array.empty<char>, StringSplitOptions.RemoveEmptyEntries)
    |> Array.chunkBySize 2
    |> Array.map (fun x -> findType x.[0], x.[1])

let fields =
    properties
    |> Array.map (fun (_, name) -> 
        let field = ProvidedField(name + "Property", typeof<DependencyProperty>)
        field.SetFieldAttributes(FieldAttributes.Public ||| FieldAttributes.Static ||| FieldAttributes.InitOnly)               
        field)
t.AddMembers(fields |> Array.toList)


コンストラクタ定義

staticコンストラクタでDependencyPropertyを初期化します。

Expr.Sequentialを利用して逐次処理を表現します。

let staticCtor = ProvidedConstructor([], IsTypeInitializer = true)
staticCtor.InvokeCode <- fun _ -> 
    Array.zip properties fields
    |> Array.map (fun ((ty, name), field) -> Expr.FieldSet(field, <@@ DependencyProperty.Register(name, ty, t) @@>))
    |> Array.fold (fun s x -> Expr.Sequential(s, x)) <@@ () @@>
t.AddMember(staticCtor)

let ctor = ProvidedConstructor([], InvokeCode = fun _ -> <@@ () @@>)
t.AddMember(ctor);


プロパティ定義

Typeクラスでの型変換はExpr.Coerceを用います。

Array.zip properties fields
|> Array.iter (fun ((ty, name), field) -> 
    let property = ProvidedProperty(name, ty)
    property.GetterCode <- fun args ->
        Expr.Coerce(
            Expr.Call(
                Expr.Coerce(args.[0], typeof<DependencyObject>),
                typeof<DependencyObject>.GetMethod("GetValue"),
                [ Expr.FieldGet(field) ]),
            ty)
    property.SetterCode <- fun args ->
        Expr.Call(
            Expr.Coerce(args.[0], typeof<DependencyObject>),
            typeof<DependencyObject>.GetMethod("SetValue", [| typeof<DependencyProperty>; typeof<obj> |]),
            [ Expr.FieldGet(field); args.[1] ])
    t.AddMember(property) )


コード全体

namespace DependencyPropertyTypeProvider

open System
open System.Windows
open System.Reflection
open Microsoft.FSharp.Core.CompilerServices
open Microsoft.FSharp.Quotations
open ProviderImplementation.ProvidedTypes

[<TypeProvider>]
type DependencyPropertyTypeProvider() as this =
    inherit TypeProviderForNamespaces()
    let asm = Assembly.GetExecutingAssembly()
    let ns = "DependencyProperty"
    let defType = ProvidedTypeDefinition(asm, ns, "Base", None, IsErased = false)

    do  defType.DefineStaticParameters(
            [ ProvidedStaticParameter("baseClass",  typeof<string>) 
              ProvidedStaticParameter("properties", typeof<string>) ],
            (fun typeName args ->

                let findType name = 
                    [ typeof<System.Object>.Assembly                 // mscorlib
                      typeof<System.Windows.Input.ICommand>.Assembly // System
                      typeof<System.Windows.Point>.Assembly          // WindowsBase
                      typeof<System.Windows.Media.Color>.Assembly    // PresentationCore
                      typeof<FrameworkElement>.Assembly ]            // PresentationFramework
                    |> Seq.map (fun x -> x.GetType(name))
                    |> Seq.tryFind ((<>) null)
                    |> function | Some x -> x
                                | None   -> failwith (name + " doesn't exist")

                let baseClass = findType (args.[0] :?> string)

                let t = ProvidedTypeDefinition(asm, ns, typeName, Some baseClass, IsErased = false)
                ProvidedAssembly("c:/src/temp.dll").AddTypes([ t ])

                let properties =
                    args.[1] :?> string
                    |> fun x -> x.Split(Array.empty<char>, StringSplitOptions.RemoveEmptyEntries)
                    |> Array.chunkBySize 2
                    |> Array.map (fun x -> findType x.[0], x.[1])

                let fields =
                    properties
                    |> Array.map (fun (_, name) -> 
                        let field = ProvidedField(name + "Property", typeof<DependencyProperty>)
                        field.SetFieldAttributes(FieldAttributes.Public ||| FieldAttributes.Static ||| FieldAttributes.InitOnly)               
                        field)
                t.AddMembers(fields |> Array.toList)

                let staticCtor = ProvidedConstructor([], IsTypeInitializer = true)
                staticCtor.InvokeCode <- fun _ -> 
                    Array.zip properties fields
                    |> Array.map (fun ((ty, name), field) -> Expr.FieldSet(field, <@@ DependencyProperty.Register(name, ty, t) @@>))
                    |> Array.fold (fun s x -> Expr.Sequential(s, x)) <@@ () @@>
                t.AddMember(staticCtor)

                let ctor = ProvidedConstructor([], InvokeCode = fun _ -> <@@ () @@>)
                t.AddMember(ctor);

                Array.zip properties fields
                |> Array.iter (fun ((ty, name), field) -> 
                    let property = ProvidedProperty(name, ty)
                    property.GetterCode <- fun args ->
                        Expr.Coerce(
                            Expr.Call(
                                Expr.Coerce(args.[0], typeof<DependencyObject>),
                                typeof<DependencyObject>.GetMethod("GetValue"),
                                [ Expr.FieldGet(field) ]),
                            ty)
                    property.SetterCode <- fun args ->
                        Expr.Call(
                            Expr.Coerce(args.[0], typeof<DependencyObject>),
                            typeof<DependencyObject>.GetMethod("SetValue", [| typeof<DependencyProperty>; typeof<obj> |]),
                            [ Expr.FieldGet(field); args.[1] ])
                    t.AddMember(property) )

                t))

        this.AddNamespace(ns, [ defType ])

[<assembly:TypeProviderAssembly>]
do ()






F# --- TypeProvider --- クイックスタート

F#にはコンパイル時に型を生成するTypeProviderという仕組みがあります。
(実際にはインテリセンスを働かせるため、コンパイル時以外に逐次生成されています。)

今回は独自のTypeProviderを作成していきます。

作成するTypeProvider

指定した次数のベクトル型を作成するTypeProviderを実装していきます。

type Vector2 = Vectors.Vector<2>
let v = Vector2(1.0, 1.0)
let x = v.X1
let y = v.X2


dllプロジェクト作成

TypeProviderはdllとして作成しますので、F#のLibraryプロジェクトを作成します。

f:id:any-programming:20170225215642p:plain

ProvidedTypes.fs

ProvidedTypes.fsを利用することにより、一から実装する必要がなくなります。

GitHubにて"FSharp.TypeProviders.StarterPack"として公開されていますので、そこからsrcの中のProvidedTypes.fsをコピーしてきます。
GitHub - fsprojects/FSharp.TypeProviders.StarterPack: The ProvidedTypes SDK for creating F# type providers

またNuGetでも取得することも可能です。

f:id:any-programming:20170225215800p:plain

TypeProviderの定義

クラスにはTypeProvider属性を付与し、dllにはTypeProviderAssembly属性を付与します。

[<TypeProvider>]
type VectorTypeProvider() as this =
    inherit TypeProviderForNamespaces()
    // 実装

[<assembly:TypeProviderAssembly>]
do ()


パラメータを受け取るTypeProviderの実装

intを受け取るVectors.Vectorを定義しています。

ProvidedStaticParameterを増やせば、渡せるパラメータも増やせます。

let asm = Assembly.GetExecutingAssembly()
let ns = "Vectors"
let vectorType = ProvidedTypeDefinition(asm, ns, "Vector", None)

do  vectorType.DefineStaticParameters(
        [ ProvidedStaticParameter("dimensions", typeof<int>) ],
        (fun typeName args -> // 実装 ))
        
    this.AddNamespace(ns, [ vectorType ])


型の作成

double[]と定義しているのは、Vectorの要素の値を保存する場所を確保するためです。

TypeProviderには消去型と生成型の2パターンあり、今回は消去型を利用しています。

消去型では実際に新しい型が生成されるわけではなく、指定した型(ここでのdouble[])とオペレータが作成されます。(外側からは、あたかも新しい型ができたように見えます。しかしリフレクションなどでは期待した挙動とは異なります。)

let t = ProvidedTypeDefinition(asm, ns, typeName, Some typeof<double[]>)


コンストラクタ作成

ProvidedParameterで引数を定義し、ProvidedConstructorのInvokeCodeにコンストラクタの挙動を定義します。

InvokeCodeには入力からExprを生成する関数を設定します。ここではコンストラクタのパラメータからdouble配列を作成しています。

let dimensions = args.[0] :?> int

let ctorParameters = 
    [ 1..dimensions ]
    |> List.map (fun x -> ProvidedParameter(sprintf "X%d" x, typeof<double>))
let ctor = 
    ProvidedConstructor(ctorParameters,
        InvokeCode = (fun args -> Quotations.Expr.NewArray(typeof<double>, args)))
t.AddMember(ctor)


プロパティ作成

double配列から値を取り出すプロパティを定義します。

挙動はGetterCodeに記述します。<@@ @@>で囲まれた区間はExprとして解釈されます。

%%をExprの先頭につけると値の様に扱うことができます。

ちなみに<@ @>や%はExpr<_>に用いることができます。

[ 1..dimensions ]
|> List.map (fun x -> 
    let property = ProvidedProperty(sprintf "X%d" x, typeof<double>)
    property.GetterCode <- (fun args -> <@@ (%%args.[0] : double[]).[x - 1] @@>)
    property)
|> List.iter t.AddMember


コード全体

namespace VectorTypeProvider

open Microsoft.FSharp.Core.CompilerServices
open ProviderImplementation.ProvidedTypes
open System.Reflection

[<TypeProvider>]
type VectorTypeProvider() as this =
    inherit TypeProviderForNamespaces()
    let asm = Assembly.GetExecutingAssembly()
    let ns = "Vectors"
    let vectorType = ProvidedTypeDefinition(asm, ns, "Vector", None)

    do  vectorType.DefineStaticParameters(
            [ ProvidedStaticParameter("dimensions", typeof<int>) ],
            (fun typeName args ->
                let dimensions = args.[0] :?> int
                let t = ProvidedTypeDefinition(asm, ns, typeName, Some typeof<double[]>)

                let ctorParameters = 
                    [ 1..dimensions ]
                    |> List.map (fun x -> ProvidedParameter(sprintf "X%d" x, typeof<double>))
                let ctor = 
                    ProvidedConstructor(ctorParameters,
                        InvokeCode = (fun args -> Quotations.Expr.NewArray(typeof<double>, args)))
                t.AddMember(ctor)

                [ 1..dimensions ]
                |> List.map (fun x -> 
                    let property = ProvidedProperty(sprintf "X%d" x, typeof<double>)
                    property.GetterCode <- (fun args -> <@@ (%%args.[0] : double[]).[x - 1] @@>)
                    property)
                |> List.iter t.AddMember

                t))
        
        this.AddNamespace(ns, [ vectorType ])

[<assembly:TypeProviderAssembly>]
do ()


利用側

このTypeProviderを利用するクラスはdllを参照に追加します。

しかし実行の際にはこのdllは必要ありません。





F#でWPF --- 可変個のコントロール --- 独自の条件で生成するコントロールを変更

下記記事にて、型でコントロールを分岐する実装をしました。
F#でWPF --- 可変個のコントロール --- 型で生成するコントロールを変更 - 何でもプログラミング

今回はユーザーが独自に定義した条件で分岐するよう実装します。

作成するアプリケーション

F#の判別共用体はXAMLのx:Typeで認識されないため、型による分岐ができません。

そこで今回は、判別共用体をバインドして図形を描画するアプリケーションを作成してみます。

type Shape =
    | Circle of Size : double
    | Square of Size : double

window.DataContext <- { Shapes = 
    [ Square(10.0); Circle(20.0); Square(30.0) ] }

f:id:any-programming:20170224222408p:plain

DataTemplateSelector

独自の条件で分岐するにはDataTemplateSelectorを利用します。

SelectTemplateを実装し、ItemTemplateSelectorに設定します。

type MyDataTemplateSelector() =
    inherit DataTemplateSelector()
    override this.SelectTemplate(item, container) = // DataTemplate
<ItemsControl ItemsSource="{Binding Items}">
    <ItemsControl.ItemTemplateSelector>
        <local:MyDataTemplateSelector />
    </ItemsControl.ItemTemplateSelector>
    <ItemsControl.Resources>
        <DataTemplate x:Key="template1">
            <!-- Control -->
        </DataTemplate>
        <DataTemplate x:Key="template2">
            <!-- Control -->
        </DataTemplate>
    </ItemsControl.Resources>
</ItemsControl>


アプリケーションコード

XAML

<Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:DataTemplateSelectors;assembly=ShapeDataTemplateSelector"
        Title="MainWindow" Height="100" Width="140">
    <Grid>
        <ItemsControl ItemsSource="{Binding Shapes}">
            <ItemsControl.ItemTemplateSelector>
                <local:ShapeDataTemplateSelector />
            </ItemsControl.ItemTemplateSelector>
            <ItemsControl.ItemsPanel>
                <ItemsPanelTemplate>
                    <StackPanel Orientation="Horizontal" />
                </ItemsPanelTemplate>
            </ItemsControl.ItemsPanel>
            <ItemsControl.Resources>
                <DataTemplate x:Key="circleTemplate">
                    <Ellipse Width="{Binding Size}" Height="{Binding Size}" Fill="Orange" Margin="10" />
                </DataTemplate>
                <DataTemplate x:Key="squareTemplate">
                    <Rectangle Width="{Binding Size}" Height="{Binding Size}" Fill="Orange" Margin="10" />
                </DataTemplate>
            </ItemsControl.Resources>
        </ItemsControl>
    </Grid>
</Window>

DataTemplateSelectors.fs

namespace DataTemplateSelectors

open System.Windows
open System.Windows.Controls

type Shape =
    | Circle of Size : double
    | Square of Size : double

type ShapeDataTemplateSelector() =
    inherit DataTemplateSelector()
    override this.SelectTemplate(item, container) = 
        let templateName = 
            match item with
            | :? Shape as shape -> 
                match shape with
                | Circle _ -> "circleTemplate"
                | Square _ -> "squareTemplate" 
            | _ -> ""
        (container :?> FrameworkElement).FindResource(templateName) :?> DataTemplate

Program.fs

open System
open System.Windows
open DataTemplateSelectors

type ViewModel = { Shapes : Shape list }

[<STAThread>]
[<EntryPoint>]
let main argv = 
    let window = Application.LoadComponent(Uri("MainWindow.xaml", UriKind.Relative)) :?> Window
    window.DataContext <- { Shapes = 
        [ Square(10.0)
          Circle(20.0)
          Square(30.0) ] }
    Application().Run(window) |> ignore
    0


判別共用体のプロパティ

今回はCircleやRectangleのSizeをバインドして利用しました。

これは判別共用体のパラメータがプロパティとして現状解釈されているためであり、今後仕様が変わってバインディングに利用できなくなるかもしれません。