かずきのBlog@hatena

すきな言語は C# + XAML の組み合わせ。Azure Functions も好き。最近は Go 言語勉強中。日本マイクロソフトで働いていますが、ここに書いていることは個人的なメモなので会社の公式見解ではありません。

WPF4.5入門 その11 「マークアップ拡張」

マークアップ拡張

XAMLは、XMLをベースにして作られた言語なので、複雑な構造をもったオブジェクトでもタグを入れ子にしていくことで柔軟に定義できます。しかし、XMLは書く人にとっては冗長でちょっとした内容でも記述量が跳ね上がるといった問題点もあります。しかも、それがよく書くものだったときには少しうんざりしてしまいます。XAMLでは、マークアップ拡張という機能を使うことによって、本来は大量のXMLを書かなければいけないところを簡潔に記述できるようにする機能が提供されています。また、マークアップ拡張を使ってXMLで記述できないような値を取得して設定することもできます。
マークアップ拡張は、XAMLの属性の値を指定するときに{ではじまり}で終わる形で記述します。こうすることで、System.Windows.Markup.MarkupExtensionから継承したクラスに値の生成を委譲することが出来ます。WPFでは組み込みで{Binding Path=…}や{StaticResource …}や{DynamicResource …}など様々な種類のマークアップ拡張が定義されています。これらを使うことで、XAMLの記述を簡潔に行ったり、プロパティに設定する値を特殊な方法で取得することが出来るようになります。


例えば、以下のようなItemという名前のクラスのIdというプロパティにXAMLから毎回ユニークになるような値を設定しないといけないような場合に、マークアップ拡張を使うことができます。マークアップ拡張とItemクラスのコードを以下に示します。

namespace CollectionXaml
{
    using System;
    using System.Windows.Markup;
 
    public class Item
    {
        public string Id { get; set; }
    }
 
    // Idを提供するマークアップ拡張
    public class IdProviderExtension : MarkupExtension
    {
        // Idのプリフィックス
        public string Prefix { get; set; }
 
        // 値を提供するロジックを記述する
        public override object ProvideValue(System.IServiceProvider serviceProvider)
        {
            return Prefix + Guid.NewGuid().ToString();
        }
    }
}

IdProviderExtensionというクラスがマークアップ拡張のクラスになります。{IdProvider Prefix=hoge}のように使用します。実際にItemクラスのIdプロパティに指定したXAMLは以下のようになります。

<Item xmlns="clr-
namespace:MarkupExtensionSample;assembly=MarkupExtensionSample"
      Id="{IdProvider Prefix=item-}" />

このXAMLを2回読み込んでIdの値を表示してみます。

namespace MarkupExtensionSample
{
    using System;
    using System.Windows.Markup;
 
    class Program
    {
        static void Main(string[] args)
        {
            // XAMLを読み込んでIdを表示
            var item = XamlReader.Load(
                typeof(Program).Assembly.GetManifestResourceStream("MarkupExtensionSample.Item.xaml")) as Item;
            Console.WriteLine(item.Id);
 
            // 再度XAMLを読み込んでIdを表示
            var item2 = XamlReader.Load(
                typeof(Program).Assembly.GetManifestResourceStream("MarkupExtensionSample.Item.xaml")) as Item;
            Console.WriteLine(item2.Id);
        }
    }
}

実行すると、Idの値が毎回ことなっていることが確認できます。

item-574cb4ed-4ec4-46ce-9e99-76f09b7545b7
item-d56c2d5e-f608-4ccc-8702-dffa18f366a9
ProvideValueメソッドのIServiceProviderって何者?

簡単なマークアップ拡張を作るときには利用しませんが、WPFが提供しているBindingやStaticResourceなどのような1つのマークアップ拡張に閉じた範囲で値の設定ができないような複雑なマークアップ拡張を作るときにはProvideValueメソッドの引数のIServiceProviderを使用します。IServiceProviderのGetServiceメソッドを使って、様々な関連情報にアクセスできるクラスが取得できます。どのようなクラスが取得できるかは、MSDNを参照してください。

MarkupExtension.ProvideValue メソッド