連載:次世代技術につながるSilverlight入門

XAMLの基礎

岩永 信之
2012/03/29
Page1 Page2

コンテンツ・プロパティ

 XAMLには、XAMLコードの可読性を向上させるため、XML要素の省略機構がいくつか存在する。その1つがコンテンツ・プロパティ(content property)で、クラスに対してContentProperty属性(System.Windows.Markup名前空間)が付いている場合、その情報に基づいてXML要素の省略が可能になる。

 例えば、Buttonクラスの場合、親クラスであるContentControlクラスに「[ContentProperty("Content")]」という属性が付いているため、<Button.Content>要素を省略できる。すなわち、List 4に示す2つの<Button>要素は同じ意味になる。

<Button>
  <Button.Content>ボタン</Button.Content>
</Button>

<Button>
  ボタン
</Button>
List 4: コンテンツ・プロパティにおけるXML要素の省略(上:省略なし、下:省略あり)

添付プロパティ

 XAMLには、自分自身ではなく親要素で用いる値を保持するための機構として添付プロパティ(attached property)という機能が存在する。例えば「<Button Canvas.Left="10" />」というように、XML属性名に「<型名>.<添付プロパティ名>」を指定する。このCanvas.Left属性の値は、Canvas要素が子要素の表示位置を決めるために使うものである。

 このような添付プロパティの値の設定は、「<型名>.Set<添付プロパティ名>」という名前の静的メソッド呼び出しとして解釈される。前記の「Canvas.Left」の例であれば、「Canvas.SetLeft(button, 10)」というようなメソッド呼び出しになる。

 ちなみに、Silverlightの内部的には、後述する依存関係プロパティ(dependency property)というものを用いて添付プロパティを実装している。Canvas.SetLeftメソッドの例でいうと、内部的にはList 5に示すような実装になっている。

public class Canvas
{
  public static readonly DependencyProperty LeftProperty
    = // 詳細は省略

  public static void SetLeft(DependencyObject element, double value)
  {
    element.SetValue(LeftProperty, value);
  }
}
Public Class Canvas

  Public Shared ReadOnly LeftProperty As DependencyProperty
    = ' 詳細は省略

  Public Shared Sub SetLeft(ByVal element As DependencyObject, ByVal value As Double)
    element.SetValue(LeftProperty, value)
  End Sub

End Class
List 5: Canvas.SetLeftメソッドの実装(上:C#、下:VB)

マークアップ拡張

 属性構文を用いて複雑なインスタンスを生成する手段として、「マークアップ拡張(markup extension)」と呼ばれる機能がある。詳細は次回以降で解説することになるが、データ・バインディングを行うためのBindingマークアップ拡張がその代表例だ。List 6にBindingマークアップ拡張の例を示す。<TextBox>要素のText属性の中身の、{ } で囲われた部分がマークアップ拡張である。

<StackPanel>
  <Slider Name="slider" Minimum="0" Maximum="100" />
  <TextBox Text="{Binding ElementName=slider, Path=Value, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
</StackPanel>
List 6: Bindingマークアップ拡張

 データ・バインディングの詳細については次回以降で説明する。簡単にいうと、あるプロパティと別のプロパティの値を常に同期させる機構で、この例の場合、スライダ・コントロールの値(=Valueプロパティの値)がテキストボックスに反映される。

 マークアップ拡張では、XML属性中に { } で囲った記述を書くと、対応するマークアップ拡張クラス(Bindingマークアップ拡張の場合はBindingクラス(System.Windows.Data名前空間))のインスタンス生成と、そのProvideValueメソッド呼び出しを通してプロパティの値が設定される。

 Silverlight 5で、Silverlightでもマークアップ拡張クラスを自作できるようになった(WPFでは当初からできたが、Silverlightは4まではできなかった)。以下のようなクラスを作ることで実現する。

  • マークアップ拡張クラスは、MarkupExtensionクラス(System.Windows.Markup名前空間)を継承する必要がある
    • ProvideValueメソッドをオーバーライドする
  • 名前の末尾に「Extension」が付く場合、XAMLコード中では「Extension」の部分を省略できる(例えば「ArrayExtension」は「{Array}」と記述できる。なお、一般的なクラスの命名規則としては「Extension」を付ける。Bindingクラスはこの例には当てはまらない)

Silverlight固有のXAML機能

 続いて、XAMLと関連した、Silverlight固有の機能について説明していこう。

XAMLコードからのC#/VBコード生成

 Silverlightでは、XAMLを使ってUIを表示する際に必要な処理のうち、定型的な部分はXAMLコードからC#/VBコードを自動生成している(そのコードのファイルは「obj」フォルダの配下に作成されるので、通常は開発者がこのコードを参照することはない)。

 List 1のXAMLコードを見ると、ルート要素である<UserControl>要素に「x:Class」という属性が付いている。Figure 2に示すように、この属性を付けることで、その型を継承した新しいクラスを自動生成する。

Figure 2: x:Class属性によるクラスの自動生成(C#の場合の例)

 生成されたクラスをもう少し詳細に見てみよう。List 1に示す、Visual Studioのプロジェクト・テンプレートに含まれるMainPage.xamlからは、List 7に示すようなクラスが生成される。

public partial class MainPage : System.Windows.Controls.UserControl
{
  // XAMLコード中でx:Name属性が付いている要素は、同名のフィールドが作られる
  internal System.Windows.Controls.Grid LayoutRoot;

  private bool _contentLoaded;

  [System.Diagnostics.DebuggerNonUserCodeAttribute()]
  public void InitializeComponent()
  {
    if (_contentLoaded)
    {
      return;
    }
    _contentLoaded = true;

    // XAMLのロード
    System.Windows.Application.LoadComponent(this, new System.Uri("/SilverlightApplication1;component/MainPage.xaml", System.UriKind.Relative));

    // XAMLから生成されたインスタンスを、対応するフィールドに格納
    this.LayoutRoot = ((System.Windows.Controls.Grid)(this.FindName("LayoutRoot")));
  }
}
Partial Public Class MainPage
  Inherits System.Windows.Controls.UserControl

  ' XAMLコード中でx:Name属性が付いている要素は、同名のフィールドが作られる
  Friend WithEvents LayoutRoot As System.Windows.Controls.Grid

  Private _contentLoaded As Boolean

  <System.Diagnostics.DebuggerNonUserCodeAttribute()> _
  Public Sub InitializeComponent()
    If _contentLoaded Then
      Return
    End If
    _contentLoaded = true

    ' XAMLのロード
    System.Windows.Application.LoadComponent(Me, New System.Uri("/SilverlightApplication1;component/MainPage.xaml", System.UriKind.Relative))

    ' XAMLから生成されたインスタンスを、対応するフィールドに格納
    Me.LayoutRoot = CType(Me.FindName("LayoutRoot"), System.Windows.Controls.Grid)
  End Sub
End Class
List 7: MainPage.xamlから自動生成されたクラス(抜粋、コメント追加)(上:C#、下:VB)

 生成されたクラスは、以下のような仕事をしている。

  • リソースからXAMLコードをロード
  • XAMLコード中でx:Name属性を付けた要素を、クラスのフィールドとして保持

 この仕組みによって、XAMLコード中で定義したUI要素に対して、C#/VBコードから簡単にアクセスできる。

値の継承

 SilverlightのほとんどのUI要素(正確には、FrameworkElementクラスから派生している要素)は、プロパティの値を親要素から継承するという仕組みを持っている。List 9に例を挙げよう。

<UserControl x:Class="SilverlightApplication1.MainPage"
  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  FontSize="30">
  <StackPanel>
    <Button Content="button 1"/>
    <Button Content="button 2"/>
  </StackPanel>
</UserControl>
List 8: 親から子へのプロパティ値の継承の例

 この例では、ルート要素である<UserControl>要素のFontSizeの値を「30」に設定しているが、その孫に当たる2つのボタンのFontSizeも「30」になり、Figure 3に示すように文字が大きくなる。

Figure 3: 親から子へのプロパティ値の継承の結果の例

 ちなみに、この仕組みはXAMLコードではなくC#やVBコード中でオブジェクトを作っても機能する(XAMLの機能というわけではなく、FrameworkElementクラスが提供する機能である)。

依存関係プロパティ

 SilverlightのUI要素は、多くのプロパティ値を、通常のCLRプロパティやフィールドではなく、一種の辞書構造である依存関係プロパティ(dependency property)という仕組みを使って保持している。辞書構造を使うのは、以下のような理由による。

  • 疎なデータになる: フォントや色など、UI要素の設定の多くは何も設定せず(デフォルトのままで)使うものが多い
  • 添付プロパティ: 前述のとおり、自分自身ではなく親要素で用いる(例えば、親要素がCanvasのときにだけ使う)値がある

 また、親要素からの値の継承やデータ・バインディングも、依存関係プロパティを使って実現している。いわば「ほかの要素の値に依存してプロパティの値を決定する機構」であるため、「依存関係プロパティ」という呼び名になっている。

依存関係プロパティの定義

 依存関係プロパティの定義は、DependencyPropertyクラスとDependencyObjectクラス(いずれもSystem.Windows名前空間)という2つのクラスを使う。

 DependencyPropertyクラスは辞書のキーとして使うもので、(名前に反して)依存関係プロパティの値そのものではない。辞書構造のデータを保持するのはDependencyObjectクラスの方である。List 8に依存関係プロパティの定義例を示そう。

// 依存関係プロパティを定義したいクラスはDependencyObjectから派生させる
public class MyData : DependencyObject
{
  // 通常のプロパティでラッピング
  public int X
  {
    // DependencyObjectのSetValue/GetValueで値を読み書き
    get { return (int)GetValue(XProperty); }
    set { SetValue(XProperty, value); }
  }

  // DependencyProperty = 辞書のキーとして使う
  public static readonly DependencyProperty XProperty =
    DependencyProperty.Register(
      "X", // 依存関係プロパティの名前
      typeof(int), // プロパティの値の型
      typeof(MyData), // 依存関係プロパティを定義している型
      new PropertyMetadata(0)); // メタデータ。既定値などを指定
}
' 依存関係プロパティを定義したいクラスはDependencyObjectから派生させる
Public Class MyData
  Inherits DependencyObject

  ' 通常のプロパティでラッピング
  Public Property X() As Integer
    ' DependencyObjectのSetValue/GetValueで値を読み書き
    Get
      Return CType(GetValue(XProperty), Integer)
    End Get
    Set(ByVal value As Integer)
      SetValue(XProperty, value)
    End Set
  End Property

  ' DependencyProperty = 辞書のキーとして使う
  Public Shared ReadOnly XProperty As DependencyProperty =
    DependencyProperty.Register(
      "X",
      GetType(Integer),
      GetType(MyData),
      New PropertyMetadata(0)) ' 依存関係プロパティの名前/プロパティの値の型/依存関係プロパティを定義している型/メタデータ。既定値などを指定
End Class
List 9: 依存関係プロパティを定義するクラスの例(上:C#、下:VB)

 依存関係プロパティは以下のようにして定義する。

  • (辞書のキーとして使われる)依存関係プロパティの静的フィールド名は「<プロパティ名>+Property」とする
  • DependencyProperty.Registerメソッドを用いて、Silverlightのフレームワークに登録する
    • 添付プロパティとして使いたい場合には、Registerメソッドの代わりにRegisterAttachedメソッドを使う
  • DependencyProperty.Registerメソッドの引数には、プロパティ名、プロパティの型、プロパティを定義する型、および、メタデータ(=既定値や、値が変化した際に呼ばれるイベント・ハンドラ)を与える

 通常、依存関係プロパティ(のSetValue/GetValueメソッド)をラッピングするCLRプロパティを定義しておいて、C#やVBのコードからはこちらのCLRプロパティを利用する。

 ただし、データ・バインディングを使った場合など、ラッピング用のCLRプロパティを介さず直接SetValue/GetValueメソッドが呼ばれる場面があるため、注意が必要である(CLRプロパティの内部でSetValue/GetValueメソッドの呼び出し以外の処理を行うと、その処理が呼ばれず、意図しない挙動になる)。

ルーティング・イベント

 プロパティと同様、イベントに関しても、通常のCLRイベントではなく、ルーティング・イベント(routed event*1)という仕組みが存在する。

*1 MSDNにおける「〜ed〜」の訳語は「〜された〜」という言葉を補って読める形に統一しているようで、「routed event」の訳語は「ルーティング(された)イベント」となっている。

 ルーティング・イベントを使うと、子要素で発生したイベントを、親要素で処理することができる。イベントが親要素に向かって登る様子から「“バブル”ルーティング(bubble routing)」と呼ばれる。

 WPFの場合にはこの逆の、親要素で発生したイベントを子要素に伝える「“トンネル”ルーティング(tunnel routing)」というイベント処理方法も採れるが、Silverlightの場合はバブル・ルーティングのみである。

 また、WPFとは異なり、Silverlightでは、ルーティング・イベントの自作ができなかったり、コントロールのClickイベントなどがルーティング・イベントになっていなかったり(つまり“バブル”しない)など、制限が多く、直接使う機会はあまりないだろう。

 次回は、基本的なコントロールや、レイアウト用のUI要素について説明する予定である。end of article


 INDEX
  [連載] 次世代技術につながるSilverlight入門
  XAMLの基礎
    1.XAMLの基礎構造
  2.XAMLの特殊な構文/Silverlight固有のXAML機能

インデックス・ページヘ  「連載:次世代技術につながるSilverlight入門」


Insider.NET フォーラム 新着記事
  • 第2回 簡潔なコーディングのために (2017/7/26)
     ラムダ式で記述できるメンバの増加、throw式、out変数、タプルなど、C# 7には以前よりもコードを簡潔に記述できるような機能が導入されている
  • 第1回 Visual Studio Codeデバッグの基礎知識 (2017/7/21)
     Node.jsプログラムをデバッグしながら、Visual Studio Codeに統合されているデバッグ機能の基本の「キ」をマスターしよう
  • 第1回 明瞭なコーディングのために (2017/7/19)
     C# 7で追加された新機能の中から、「数値リテラル構文の改善」と「ローカル関数」を紹介する。これらは分かりやすいコードを記述するのに使える
  • Presentation Translator (2017/7/18)
     Presentation TranslatorはPowerPoint用のアドイン。プレゼンテーション時の字幕の付加や、多言語での質疑応答、スライドの翻訳を行える
@ITメールマガジン 新着情報やスタッフのコラムがメールで届きます(無料)
- PR -

注目のテーマ

業務アプリInsider 記事ランキング

本日 月間
ソリューションFLASH