XAMLを書いてて、よく出てくる{StaticResource ...}や{DynamicResource ...}や{x:Type ...}のような{}で囲まれた部分。
これってどうやって作るんだろう?と思って調べてみた。
結論としてはMarkupExtentionクラスを継承して作ればOKらしい。
ということで、早速作ってみようと思う。
何事もHelloWorldからということでHelloWorldExtensionを作ってみよう。
HelloWorldExtensionという名前のクラスをさくっとつくる。
MarkupExtensionを継承すると、ProvideValueメソッドをオーバーライドしないといけない。
このメソッドで返した値が、実際にXAMLに書いたときに使われるみたいだ。ということでHello worldを返すように実装してみた。
using System;
using System.Windows.Markup;
namespace WpfMarkupExtension
{
public class HelloWorldExtension : MarkupExtension
{
public override object ProvideValue(IServiceProvider serviceProvider)
{
return "Hello world";
}
}
}
早速XAMLで使ってみよう。
<Window x:Class="WpfMarkupExtension.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:WpfMarkupExtension="clr-namespace:WpfMarkupExtension"
Title="Window1" Height="300" Width="300">
<Grid>
<!-- 使ってみたよ -->
<TextBlock Text="{WpfMarkupExtension:HelloWorld}" />
</Grid>
</Window>
xmlns:WpfMarkupExtension="clr-namespace:WpfMarkupExtension"で名前空間を指定してTextBlockのTextプロパティで実際に使ってみてる。これを実行するとHello worldが表示される!
これだけじゃつまらないのでHelloWorldExtensionにプロパティもつけてみようと思う。プロパティは、{Binding Source=..., Path=....}みたいな形でXAML内から指定できる。
これは特別なことをしなくてもよくて、普通にプロパティにすればOKみたいだ。
というわけで、HelloWorldExtensionに、Prefixというプロパティをつけてみた。
using System;
using System.Windows.Markup;
namespace WpfMarkupExtension
{
public class HelloWorldExtension : MarkupExtension
{
public string Prefix { get; set; }
public override object ProvideValue(IServiceProvider serviceProvider)
{
return Prefix + "Hello world";
}
}
}
XAMLで使うときには↓のような感じで使える。
<Window x:Class="WpfMarkupExtension.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:WpfMarkupExtension="clr-namespace:WpfMarkupExtension"
Title="Window1" Height="300" Width="300">
<Grid>
<!-- Prefixプロパティ!! -->
<TextBlock Text="{WpfMarkupExtension:HelloWorld Prefix=へろー}" />
</Grid>
</Window>
実行するとちゃんと効いてるのがわかる。
最後に、なんていうのかは知らないけどBindingのPathプロパティみたいに{Binding Text}って書くと{Binding Path=Text}と同じように扱われるみたいなのを作ってみようと思う。
これは、引数のあるコンストラクタを定義すればいいみたいだ。お作法的には、コンストラクタの引数で指定されるプロパティに対してConstructorArgumentもつけてあげるのがいいっぽい。(理由は不明だけど!)
そんな作法に従うと、HelloWorldExtensionは↓のようになる。
using System;
using System.Windows.Markup;
namespace WpfMarkupExtension
{
public class HelloWorldExtension : MarkupExtension
{
[ConstructorArgument("prefix")]
public string Prefix { get; set; }
public HelloWorldExtension() { }
public HelloWorldExtension(string prefix)
{
this.Prefix = prefix;
}
public override object ProvideValue(IServiceProvider serviceProvider)
{
return Prefix + "Hello world";
}
}
}
これでXAMLで使うときにはPrefix=を書かなくてもPrefixに値を指定できるようになる。
<Window x:Class="WpfMarkupExtension.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:WpfMarkupExtension="clr-namespace:WpfMarkupExtension"
Title="Window1" Height="300" Width="300">
<Grid>
<!-- Prefixと明示的に指定しなくてもOK -->
<TextBlock Text="{WpfMarkupExtension:HelloWorld へろー}" />
</Grid>
</Window>
実行結果もさっきと同じようになる。
めでたしめでたし。