こちらのエントリーはSilverlight Advent Calendar 2011の24日分の参加エントリーです。
書いていて気がつきましたが、このエントリーってクリスマスイブのエントリーなんですね(^^;
といっても、それほど特別な内容ではなくて恐縮なんですが、今回はせっかくですのでSilverlight 5に追加されたカスタムのマークアップ拡張を作成するというシナリオについてご紹介したいと思います。
まずは概要のおさらいから
マークアップ拡張とは
ご存じのとおり、SilverlightではXAMLを使ってUIを作成し、それ以外の処理はC#, VisualBasicを使ってアプリケーションを構築します。
このXAMLはご存じのとおり、XMLベースのマークアップでオブジェクトのインスタンスの宣言を行い、併せてそのオブジェクトのプロパティやイベントを設定する宣言型プログラミング言語という位置づけでした。
そのプロパティ・イベントの設定には「属性構文」「プロパティ要素構文」「コレクション構文」などがあって、設定するオブジェクトの種類などによってこれらを使い分ける必要がありました。
中でも、「属性構文」・「プロパティ要素構文」はプロパティやイベント設定の礎となる重要な構文です。
「属性構文」はXMLの属性の形でプロパティやイベントの設定が行える代わりに、その中に設定できるオブジェクトが限定されており、「文字列」もしくは「直接変換可能な値」となっていました。
「直接変換可能な値」とはintやdoubleなどのプリミティブ型(正確にはネイティブテキスト構文と呼ばれる構文で定義されたデータ型)や列挙型、または型コンバーターによる変換が可能な値などがこれらに当たりますが、つまりは上記以外のプロパティ設定を行う場合は必然的に「プロパティ要素構文」で記述する必要があります。
対して「プロパティ要素構文」はオブジェクトとなるXML要素のコンテント領域に[オブジェクト名].[プロパティ名]と命名された要素を内包することで柔軟なオブジェクトの設定が可能です。
コンテンツ領域に任意のオブジェクトタグを宣言することでプロパティに対して任意のオブジェクトを設定することが可能です。
その代り、開始タグ・終了タグのマークアップとなるため、表現が冗長となりがちで、ステップ数もかさむことから可読性の意味ではあまり優れているとは言えません。
このような理由から、多くは可能な限り属性構文で設定を行い、属性構文で設定が難しいものに関してはプロパティ要素構文を利用するという形で宣言を行います。
しかし、利用頻度の高いオブジェクトなどは、プロパティ要素構文で宣言するとステップ数が増えてしまいますので、一部のオブジェクトは属性構文で宣言できるような方法が用意されていました。
それがマークアップ拡張になります。
マークアップ拡張の利用方法
マークアップ拡張は属性構文で中かっこで指定が可能です。
マークアップ拡張として利用可能なクラスを中かっこ{}の中に指定し、スペースを開けてコンストラクタやプロパティの指定が行えます。 これらは,をトークンとすることで複数のパラメーターが指定可能です。
パラメーターの中に等号「=」が含まれるものがプロパティへの設定、含まれないものがコンストラクタへの設定となります。
Silverlightにはあらかじめいくつかのマークアップ拡張として利用可能なクラスが定義されています。
- Binding:データバインディングで利用するクラス
- StaticResource:リソースディクショナリからの参照を指定するクラス
- TemplateBinding:コントロールテンプレート内から元となる親オブジェクトのプロパティとの通信を行うためのクラス
- RelativeSource:バインディングやテンプレートバインディングなどで相対位置のソースを指定するためのクラス
また、XAML内の機能で利用できるマークアップ拡張としてプロパティにNull値を指定する{x:Null}があります。
Silverlight 4まで上記の定義済のマークアップ拡張を利用するのみだったのですが、Silverlight 5からは任意のマークアップ拡張を作成するというシナリオが追加されています。
マークアップ拡張は自身でカスタムのマークアップ拡張の作成方法を知ることで、上記のようなパラメーターの指定が、実際のマークアップ拡張に対してどのように作用しているのかが理解しやすくなります。
それでは早速、その方法を確認してマークアップ拡張の挙動を確認してみましょう。
カスタムのマークアップ拡張の作成方法
マークアップ拡張はMarkupExtensionクラスの派生クラスのオブジェクトになります。そのため必然的にMarkupExtensionを継承する必要があります。
MarkupExtensionはSystem.Windows.Markup名前空間にあるクラスです。
この中のProvideValueというメソッドを継承することでマークアップ拡張は作成が可能です。
using System; using System.Windows.Markup; namespace SLAdv2011_Day24_MarkupExtension { public class MerryChristmasExtension : MarkupExtension { public override object ProvideValue(IServiceProvider serviceProvider) { return "Merry X'mas to XAMLDeveloper"; } } }
以上のコードをXAMLから呼び出す場合は、以下のようになります。
<UserControl x:Class="SLAdv2011_Day24_MarkupExtension.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:SLAdv2011_Day24_MarkupExtension"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="400">
<Grid x:Name="LayoutRoot" Background="DarkBlue">
<TextBlock Text="{local:MerryChristmas}"
HorizontalAlignment="Center"
VerticalAlignment="Center"
FontSize="40">
<TextBlock.Foreground>
<LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">
<GradientStop Color="#FFE0FF00" Offset="0.012" />
<GradientStop Color="#FFFF8A00" Offset="1" />
</LinearGradientBrush>
</TextBlock.Foreground>
</TextBlock>
</Grid>
</UserControl>
xmlnsにlocalの接頭辞で名前空間の宣言を行い、TextBlockコントロールのTextプロパティに対してマークアップ拡張を指定しました。
上記のように、クラス名に「~Extension」と定義することが一般的で、このExtensionに関してはXAMLでは省略が可能です。
ただし、Silverlightでは「~Extension」と名前のついていないマークアップ拡張も存在しており、、「~Extension」とすること自体は必須ではありません。
次にプロパティを定義してみます。
using System; using System.Windows.Markup; namespace SLAdv2011_Day24_MarkupExtension { public class MerryChristmasExtension : MarkupExtension { public string To { get; set; } public MerryChristmasExtension() { To = "XAMLDeveloper"; } public override object ProvideValue(IServiceProvider serviceProvider) { return "Merry X'mas to " + To; } } }
今回はToというstring型のプロパティを1つ定義しました。
そして、XAMLではTextBlockコントロールのTextプロパティを以下のように定義してみます。
<TextBlock Text="{local:MerryChristmas To=SilverlightDeveloper}"
HorizontalAlignment="Center"
VerticalAlignment="Center"
FontSize="40">
<TextBlock.Foreground>
<LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">
<GradientStop Color="#FFE0FF00" Offset="0.012" />
<GradientStop Color="#FFFF8A00" Offset="1" /></LinearGradientBrush>
</TextBlock.Foreground>
</TextBlock>
すると実行結果は以下のような形となります。
上記のように、マークアップ拡張そのものはとても簡単に作成できることから、利用頻度の高いユーティリティクラスなどをマークアップ拡張として定義することですっきりとしたXAMLにすることが可能です。
従来WPFでは行えたことがSilverlightでもようやく行えるようになったことで、利用方法もすっきり理解できるようになったのではないかと思います。
データの表現などはBindingでかなりの表現が行えるので、むやみにマークアップ拡張を作成することはおすすめできませんが、共通的に利用するようなユーティリティクラスを定義することで効率的な開発を行うことが可能になります。
今回のサンプルコードはこちらになります。
そんなワケでTipsというよりはどちらかというと基礎的な内容ばかりで大変恐縮です(^^;
それでは僭越ながら私のSilverlight Advent Calendar 2011のエントリーは以上になります。
みなさん、よいクリスマスをお過ごしください!
ツイート