銀の弾丸

プログラミングに関して、いろいろ書き残していければと思っております。

まだ間に合うXAMLの基本

XAMLはなんだか複雑だ」と思ってました。

しかし、あることに気が付いてから「なかなかシンプルなんじゃない?」と思えるようになりました。

ちゃんと知っている方にとっては、当たり前のことかと思いますが、その「ちょっとしたこと」を出発点に、1段掘り下げて調べた結果を書いておきます。

f:id:takamints:20161026121034p:plain
photo credit: Doug Kline Star Wars Celebration IV - X-Wing fighter (back) via photopin (license)

目次

  1. XAML要素でインスタンスが生成される
  2. 属性でパブリックプロパティを設定する
  3. プロパティへクラスオブジェクトを代入する
  4. 静的クラスのプロパティへオブジェクトを代入する
  5. そもそも子要素は何処に格納されるのか

1. XAML要素でインスタンスが生成される

XAMLの要素名はクラス名で、実行時には、そのクラスのデフォルトコンストラクタでインスタンスが生成される

クラス名であることは知っていましたが、デフォルトコンストラクタで生成されるという認識がありませんでした。 だから実行時のオブジェクトの状態を正確に把握できていなかったんですわ。

MainWindow.xamlMVVM的に真っ当にMessageBoxを表示する - 銀の弾丸より)

<Window x:Class="StudyDotNet.MainWindow"
        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:StudyDotNet"
        xmlns:vm="clr-namespace:StudyDotNet.ViewModels"
        xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity" 
        xmlns:tr="clr-namespace:StudyDotNet.Triggers"
        mc:Ignorable="d"
        Title="MainWindow" Height="350" Width="525">
    <Window.DataContext>
        <vm:MainWindowViewModel/>
    </Window.DataContext>

    <i:Interaction.Triggers>
        <tr:MessageBoxTrigger>
            <tr:MessageBoxAction />
        </tr:MessageBoxTrigger>
    </i:Interaction.Triggers>

    <Grid>
        <Button x:Name="button" Content="Button"
                Command="{Binding SampleCommand}"
                HorizontalAlignment="Left" Margin="50,50,0,0"
                VerticalAlignment="Top" Width="75"/>
    </Grid>
</Window>
takamints.hatenablog.jp

上のXAMLでは、次のクラスオブジェクトが生成されています。 (ドットを含む要素名と、子要素の所在については後述します)

  • MainWindowViewModel
  • MessageBoxTrigger
  • MessageBoxAction
  • Grid
  • Button

確かにビューモデルやボタンなどは、実行時に生成されていて、コードビハインドからアクセスできますよね。 でも、デフォルトコンストラクタとの関連は想像していなかったんです。 このような、標準の部品に関しては、.NETフレームワーク内で特別なことが行われているのだろう・・・などと思ってました。

でも、自作クラスも生成されてるってことは・・・という段になって、やっと気付いた。

思った以上にシンプルですね。そもそもフレームワークをそんなガチガチに作るわけ無いですし。

2. 属性でパブリックプロパティを設定する

要素の属性はそのクラスオブジェクトのパブリックプロパティへの代入です。 デフォルトコンストラクタで生成されたのち、プロパティが設定されます。

属性値として直接記述できるのは、文字列とか数値といったプリミティブな型に制限されると思います。 バインディングの解決は、少し複雑なことがなされているのかもしれませんね。

3. プロパティへクラスオブジェクトを代入する

ところで、XAMLでデータコンテキストを生成しているところ(以下)。

<Window x:Class="StudyDotNet.MainWindow" ~中略~ >
    <Window.DataContext>
        <vm:MainWindowViewModel/>
    </Window.DataContext>
    ・
    ・
    ・
</Window>

この Window.DataContext要素のように、親エレメント.プロパティ名という要素は、親要素のインスタンスプロパティへの代入なんですね。

つまりここでは、MainWindowクラスの基本クラスであるWindowDataContextプロパティへ、MainWindowViewModelインスタンスを設定(Property Set)しているということです。

4. 静的クラスのプロパティへオブジェクトを代入する

以下のInteraction.Triggersも似ていますが、ドットの前がInteractionとなっていて、親要素ではありません。 これは、System.Windows.Interactivity.Interactionというスタティッククラスで、そのTriggersというプロパティにMessageBoxTriggerを追加しています。

<Window x:Class="StudyDotNet.MainWindow" ~中略~ >
    ・
    ・
    ・
    <i:Interaction.Triggers>
        <tr:MessageBoxTrigger>
            <tr:MessageBoxAction />
        </tr:MessageBoxTrigger>
    </i:Interaction.Triggers>
    ・
    ・
    ・
</Window>

ところが InteractionクラスにそのものスバリのTriggersというプロパティはありません。 かわりにTriggerCollection GetTriggers(DependencyObject obj)というメソッドがありますので、XAMLのパーサーがうまくやってくれているのだと思います(詳細不明)。

以下のように、コードビハインドからthisを与えて返されるコレクションに、ちゃんとMessageBoxTriggerインスタンスが入っていました。

using System;
using System.Windows;
using System.Windows.Interactivity;

namespace StudyDotNet
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
            var triggers = Interaction.GetTriggers(this);
            foreach(var trigger in triggers)
            {
                Console.WriteLine(trigger.GetType().Name);
            }
        }
    }
}

ちなみにInitializeComponentの前では何も表示されなかったので、XAMLInitializeComponentで処理されているのでしょう。

5. そもそも子要素は何処に格納されるのか

XAMLのパーサーは、子要素のインスタンスを親要素の何処に格納するの?という疑問。 コレクションであることは間違いなさそうなのですが。

XAMLの以下の部分などですね。

<i:Interaction.Triggers>
    <tr:MessageBoxTrigger>
        <tr:MessageBoxAction />
    </tr:MessageBoxTrigger>
</i:Interaction.Triggers>

XAMLのパーサーが、MessageBoxTriggerのインスタンスに、MessageBoxActionのインスタンスを保持させている」ということは明らか。

実際にアクションはイベントトリガーのActionsというコレクションプロパティに保持されており、 このプロパティは、MessageBoxTriggerの基本クラスであるTriggerBaseに実装されています。 でも、XAMLだけを見る限りは、そんな事実はわからない。

ということで、System.Windows.Interactivity.TriggerBaseの説明を見てみると、以下のようになっています。

[ContentPropertyAttribute("Actions")] 
public abstract class TriggerBase : DependencyObject, IAttachedObject

なるほど、ContentPropertyAttributeで「XMLのコンテントはプロパティ名 Actions に」と読めますね。 XAMLのパーサーはこの宣言に従って子要素を親要素に格納している。

ということで、Interaction.Triggersの中身を、以下のように冗長に書き換えても同じように動作します。

<i:Interaction.Triggers>
    <tr:MessageBoxTrigger>
        <tr:MessageBoxTrigger.Actions>
            <tr:MessageBoxAction />
        </tr:MessageBoxTrigger.Actions>
    </tr:MessageBoxTrigger>
</i:Interaction.Triggers>

まとめと所感

このように、曖昧に済ませていたことを深掘りしてみて、かなりスッキリ気分爽快。 XAMLで何でもできる気がしてきました。

基本を押さえるのはホントに大切。 知識やスキルを習得するときの効率性に直結しますね。

いくつになってもお勉強です。