F#でWPF --- XAMLデザイナでサンプルデータ利用

VisualStudioに備え付けのXAMLエディタでデザインをする際、実行時に決定される可変個のコントロールを扱うのは大変です。

そこで今回は、デザイン時にサンプルのViewModelを利用する方法を書いていきます。

DesignInstance

下記のようにXAMLを記述することにより、デザインビュー上のDataContextにインスタンスを設定できます。

ここではnamespace SamplesにあるSampleVMクラスを参照しています。

尚、DesignInstanceに指定する型は、引数なしコンストラクタを公開している必要があります。

<Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:local="clr-namespace:Samples;assembly=DesignTimeSample"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        mc:Ignorable="d"
        d:DataContext="{d:DesignInstance local:SampleVM, IsDesignTimeCreatable=True}">
</Window>


レコードからサンプルViewModelの自動生成

F#のレコードには引数なしコンストラクタがありません。

そこでレコードと同じプロパティを持ち、引数なしコンストラクタを持つクラスを作ります。

今回はDynamicObjectを用いて自動的にクラスを作成するようにします。

open System.Dynamic
open System.Runtime.InteropServices

[<AbstractClass>]
type DesignTimeViewModel(instance : obj) =
    inherit DynamicObject()
    let properties = 
        instance.GetType().GetProperties()
        |> Array.map (fun x -> x.Name, x)
        |> dict
    override this.TryGetMember(binder : GetMemberBinder, [<Out>] result : obj byref) = 
        result <- properties.Item(binder.Name).GetValue(instance)
        true


DesignInstanceなし

DesignInstanceなしの場合、Bindingの中身が実行時にならないとわからないため、デザイナには何も表示されません。

<Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        Title="MainWindow" Height="130" Width="130">
    <Grid>
        <TextBlock Text="{Binding Text}" HorizontalAlignment="Left" Height="23" Margin="10,10,0,0" TextWrapping="Wrap" VerticalAlignment="Top" Width="120"/>
        <Polyline Margin="0,30,0,0" Points="{Binding Points}" Stroke="Red" StrokeThickness="10" />
    </Grid>
</Window>
type ViewModel = 
    { Text   : string
      Points : PointCollection }

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

DesignInstance利用

デザイナにSampleVMの内容が反映されていることが確認できます。

<Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:local="clr-namespace:Samples;assembly=DesignTimeViewModel"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        mc:Ignorable="d"
        d:DataContext="{d:DesignInstance local:SampleVM, IsDesignTimeCreatable=True}"        
        Title="MainWindow" Height="130" Width="130">
    <Grid>
        <TextBlock Text="{Binding Text}" HorizontalAlignment="Left" Height="23" Margin="10,10,0,0" TextWrapping="Wrap" VerticalAlignment="Top" Width="120"/>
        <Polyline Margin="0,30,0,0" Points="{Binding Points}" Stroke="Red" StrokeThickness="10" />
    </Grid>
</Window>
namespace Samples

type SampleVM() =
    inherit DesignTimeViewModel(
        { Text = "Hello"
          Points = [ Point(10.0, 40.0)
                     Point(60.0, 10.0) 
                     Point(40.0, 50.0)
                     Point(100.0, 10.0) ] 
                   |> PointCollection })

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

XAMLデザイナのインテリセンス

現時点において(Visual Studio 2015 Update 3)XAMLでインテリセンスがサポートされています。

例えばBinding記述時に、DesignInstanceに設定したクラスのプロパティが候補に出てきます。

しかし今回はDynamicObjectを利用しているため、このインテリセンスは利用できません。

TypeProvidersを利用すると解決できるかもしれません。