Elm --- 階層化
下記記事にてElmを用いてカウンタを実装しました。
Elm --- Model、View、Update - 何でもプログラミング
今回はこのカウンタを再利用して、複数のカウンタを配置してみます。
内容はElmのTutorialにあるものとほとんど同じです。
作成するアプリケーション
カウンタが2つあり、一番下に合計値が出力されるアプリケーションになります。
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)
リフレクション 逆引き (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
WPFにのみ提供されるBuild Actionです。
対象のリソースを実行ファイルに埋め込みます。
リソースを読み込む際は、GetResourceStreamを利用します。
Application.GetResourceStream(Uri("Resource.xaml", UriKind.Relative)).Stream
Xamlファイルを読み込む際は、LoadComponentが利用できます。
Application.LoadComponent(new Uri("Resource.xaml", UriKind.Relative))
Content
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
対象のファイルを実行ファイルに埋め込みます。
リソースを読み込む際は、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)
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# --- 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プロジェクトを作成します。
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でも取得することも可能です。
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) ] }
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>
アプリケーションコード
<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をバインドして利用しました。
これは判別共用体のパラメータがプロパティとして現状解釈されているためであり、今後仕様が変わってバインディングに利用できなくなるかもしれません。