Xaml
Xamarin
Xamarin.Forms

XAMLの基本(Xamarin公式XAMLページ全訳)と基礎の入り口

概要

Xamarin.Formsを使ってアプリを開発するとき、UI作成にXAMLを使用する方は多いのではないでしょうか?僕もその1人です。

僕は開発時XAMLについて調べるとき、XAMLを「どのように」使うかについて触れた記事を読んできましたが、XAMLが「なぜ」そのように使えるのか、どういう仕組みで動いているのかについてはあまり考えてきませんでした。

そこでこの記事ではXAMLの「なぜ」や「基礎」の入り口になるような話を書きたいと思っています。基礎を徹底的に掘り下げるものでも、網羅するものでも、ありません。

付録として最後に「どのように」使うかが説明されたXamarin公式ページeXtensible Application Markup Language (XAML)の全訳を載せます。とても長いですが、「基本」事項(使い方を)概観するにはぴったりの記事です。

XAMLとは?

まず、XAMLとは何なのでしょうか?XamarinによるXamarin.Formsの公式ページでは次のように説明されています。

eXtensible Application Markup Language (XAML)

XAML is a declarative markup language that can be used to define user interfaces. The user interface is defined in an XML file using the XAML syntax, while runtime behavior is defined in a separate code-behind file.

(訳)XAMLはユーザインターフェースを定義する宣言的なマークアップ言語です。ユーザインターフェースはXAML構文を用いてXMLファイル内で定義され、実行時のふるまいは別のコードビハインドファイル内で定義されます。

また、以前参加した勉強会等では次のようにも説明されていました。

XAML入門 13 / 38 by Kazuki Ota さん

UI記述特化言語ではない オブジェクトのインスタンスを組み立てるための言語

XAML&Application Platform ~これまでとこれから~ 9 / 56 by Shinobu Takahashi さん

UIを定義するために特化した言語ではなく オブジェクトのインスタンスとオブジェクト間の階層構造を構築する定義型言語

XAMLで定義した内容はUIとして活用されるけれど、その本質は「オブジェクトのインスタンスの構築」にあるということでしょうか。

XAMLをコードで表現する

XAMLではこんな風に書きます。でも、これと同等の内容はC#のコードで表現できます。という説明を行う本を見たことがあります。例えばこのようなものです

XamlSamplePage.xaml
<?xml version="1.0" encoding="utf-8"?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
  xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
  xmlns:local="clr-namespace:XamlSample"
  x:Class="XamlSample.XamlSamplePage">
    <Label Text="Welcome to Xamarin Forms!" VerticalOptions="Center" HorizontalOptions="Center" />
</ContentPage>
XamlSamplePage.xaml.cs
using Xamarin.Forms;

namespace XamlSample
{
    public partial class XamlSamplePage : ContentPage
    {
        public XamlSamplePage()
        {
            InitializeComponent();
        }
    }
}

の組み合わせと、単独の

XamlSamplePage.xaml.cs
using Xamarin.Forms;

namespace XamlSample
{
    public partial class XamlSamplePage : ContentPage
    {
        public XamlSamplePage()
        {
            Content = new Label
            {
                Text = "Welcome to Xamarin Forms!",
                VerticalOptions = LayoutOptions.Center,
                HorizontalOptions = LayoutOptions.Center
            };
        }
    }
}

は実行したときに表示されるのはいずれも次のスクリーンショットのようになります。

Welcome to Xamarin Forms

ここで、もしかしたらXAML定義からC#コードに変換しているのではないか?InitializeComponent()が大事な機能を果たしているのではないか?という考えが頭をよぎります。

どのタイミングで、なにが、どうやってXAML定義を使用するのでしょうか?

XAMLG

ここで登場するのがXAMLGタスクです。

https://github.com/xamarin/Xamarin.Forms/blob/master/Xamarin.Forms.Build.Tasks/XamlGTask.cs

Xamarin.Formsの新規プロジェクトを作成し、上記のXAMLとコードビハインドを準備してビルドしてみましょう。

XamlSample/obj/Debug内に生成された成果物を探すと次のようなファイルを見つけることができます。

XamlSample.XamlSamplePage.xaml.g.cs
//------------------------------------------------------------------------------
// <auto-generated>
//     This code was generated by a tool.
//     Runtime Version:4.0.30319.42000
//
//     Changes to this file may cause incorrect behavior and will be lost if
//     the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------

namespace XamlSample {


    [global::Xamarin.Forms.Xaml.XamlFilePathAttribute("/Users/sugita-toshinori/dev/codes/xamarin/XamlSample/XamlSample/XamlSamplePage.xa" +
        "ml")]
    public partial class XamlSamplePage : global::Xamarin.Forms.ContentPage {

        [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Xamarin.Forms.Build.Tasks.XamlG", "0.0.0.0")]
        private void InitializeComponent() {
            global::Xamarin.Forms.Xaml.Extensions.LoadFromXaml(this, typeof(XamlSamplePage));
        }
    }
}

InitializeComponentメソッドが定義されており、LoadFromXamlが何かを行なってくれそうです。

これはどこに定義されていて、何を行なっているのでしょうか?

メソッドの詳細を追う前に、このメソッドが呼ばれるまでの大まかな流れを確認しましょう。

先ほども言及したXamarin公式ページでは次のように処理フローが説明されています。

At runtime, code in the particular platform project calls a LoadApplication method, passing to it a new instance of the App class in the PCL. The App class constructor instantiates XamlSamplesPage. The constructor of that class calls InitializeComponent, which then calls the LoadFromXaml method that extracts the XAML file (or its compiled binary) from the PCL. LoadFromXaml initializes all the objects defined in the XAML file, connects them all together in parent-child relationships, attaches event handlers defined in code to events set in the XAML file, and sets the resultant tree of objects as the content of the page.

(訳)実行時に、特定のプラットフォームプロジェクトのコードがLoadApplicationメソッドを呼び出し、PCLのAppクラスの新しいインスタンスをそのクラスに渡します。Appクラスのコンストラクタは、XamlSamplesPageをインスタンス化します。そのクラスのコンストラクタはInitializeComponentを呼び出し、次に、XAMLファイル(またはそのコンパイルされたバイナリ)をPCLから抽出するLoadFromXamlメソッドを呼び出します。LoadFromXamlは、XAMLファイルで定義されているすべてのオブジェクトを初期化し、それらをすべて親子関係で接続し、コードで定義されているイベントハンドラをXAMLファイルに設定されたイベントに関連付け、オブジェクトのツリーをページのコンテンツとして設定します。

これまで追ってきたように、ページをインスタンス化する際、コンストラクタでInitializeComponentを呼び出し、LoadFromXamlXAMLで定義されたすべてのオブジェクトを初期化するというのは仮説からも離れていなさそうです。

それでは、LoadFromXamlの定義を探してみましょう。

ここにありました。

https://github.com/xamarin/Xamarin.Forms/blob/master/Xamarin.Forms.Xaml/ViewExtensions.cs

ViewExtensions.cs
using System;

namespace Xamarin.Forms.Xaml
{
    public static class Extensions
    {
        public static TXaml LoadFromXaml<TXaml>(this TXaml view, Type callingType)
        {
            XamlLoader.Load(view, callingType);
            return view;
        }

        internal static TXaml LoadFromXaml<TXaml>(this TXaml view, string xaml)
        {
            XamlLoader.Load(view, xaml);
            return view;
        }
    }
}

XamlLoader.Loadで処理を行なっていそうです。また探してみましょう。

https://github.com/xamarin/Xamarin.Forms/blob/master/Xamarin.Forms.Xaml/XamlLoader.cs

XamlLoader.cs
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Reflection;
using System.Text.RegularExpressions;
using System.Xml;
using Xamarin.Forms.Internals;

// (略)

namespace Xamarin.Forms.Xaml
{
    static class XamlLoader
    {
        public static void Load(object view, Type callingType)
        {
            var xaml = GetXamlForType(callingType);
            if (string.IsNullOrEmpty(xaml))
                throw new XamlParseException(string.Format("No embeddedresource found for {0}", callingType), new XmlLineInfo());
            Load(view, xaml);
        }

        public static void Load(object view, string xaml)
        {
            using (var textReader = new StringReader(xaml))
            using (var reader = XmlReader.Create(textReader))
            {
                while (reader.Read())
                {
                    //Skip until element
                    if (reader.NodeType == XmlNodeType.Whitespace)
                        continue;
                    if (reader.NodeType == XmlNodeType.XmlDeclaration)
                        continue;
                    if (reader.NodeType != XmlNodeType.Element) {
                        Debug.WriteLine("Unhandled node {0} {1} {2}", reader.NodeType, reader.Name, reader.Value);
                        continue;
                    }

                    var rootnode = new RuntimeRootNode (new XmlType (reader.NamespaceURI, reader.Name, null), view, (IXmlNamespaceResolver)reader);
                    XamlParser.ParseXaml (rootnode, reader);
                    Visit (rootnode, new HydrationContext {
                        RootElement = view,
#pragma warning disable 0618
                        ExceptionHandler = ResourceLoader.ExceptionHandler ?? (Internals.XamlLoader.DoNotThrowOnExceptions ? e => { }: (Action<Exception>)null)
#pragma warning restore 0618
                    });
                    break;
                }
            }
        }

        // (略)
    }

XmlReaderでXMLの各ノードをWhileでループしていく際にXamlParser.ParseXaml (rootnode, reader)しているのが気になります。

引数のrootnodeには1行手前でview(ここでの実体はXamlSamplePageのインスタンス)が渡されているので、XAMLの構造をViewのツリー構造にマッピングしているのではないかと想像できます。

https://github.com/xamarin/Xamarin.Forms/blob/master/Xamarin.Forms.Xaml/XamlParser.cs

XamlParser.cs
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Xml;
using Xamarin.Forms.Internals;

namespace Xamarin.Forms.Xaml
{
    static class XamlParser
    {
        public const string XFUri = "http://xamarin.com/schemas/2014/forms";
        public const string X2006Uri = "http://schemas.microsoft.com/winfx/2006/xaml";
        public const string X2009Uri = "http://schemas.microsoft.com/winfx/2009/xaml";
        public const string McUri = "http://schemas.openxmlformats.org/markup-compatibility/2006";

        public static void ParseXaml(RootNode rootNode, XmlReader reader)
        {
            IList<KeyValuePair<string, string>> xmlns;
            var attributes = ParseXamlAttributes(reader, out xmlns);
            var prefixes = PrefixesToIgnore(xmlns);
            (rootNode.IgnorablePrefixes ?? (rootNode.IgnorablePrefixes=new List<string>())).AddRange(prefixes);
            rootNode.Properties.AddRange(attributes);
            ParseXamlElementFor(rootNode, reader);
        }

        static void ParseXamlElementFor(IElementNode node, XmlReader reader)
        {
            Debug.Assert(reader.NodeType == XmlNodeType.Element);

            var elementName = reader.Name;
            var isEmpty = reader.IsEmptyElement;

            if (isEmpty)
                return;

            while (reader.Read())
            {
                switch (reader.NodeType)
                {
                    case XmlNodeType.EndElement:
                        Debug.Assert(reader.Name == elementName); //make sure we close the right element
                        return;
                    case XmlNodeType.Element:
                        // 1. Property Element.
                        if (reader.Name.Contains("."))
                        {
                            XmlName name;
                            if (reader.Name.StartsWith(elementName + ".", StringComparison.Ordinal))
                                name = new XmlName(reader.NamespaceURI, reader.Name.Substring(elementName.Length + 1));
                            else //Attached DP
                                name = new XmlName(reader.NamespaceURI, reader.LocalName);

                            var prop = ReadNode(reader);
                            if (prop != null)
                                node.Properties.Add(name, prop);
                        }
                        // 2. Xaml2009 primitives, x:Arguments, ...
                        else if (reader.NamespaceURI == X2009Uri && reader.LocalName == "Arguments")
                        {
                            var prop = ReadNode(reader);
                            if (prop != null)
                                node.Properties.Add(XmlName.xArguments, prop);
                        }
                        // 3. DataTemplate (should be handled by 4.)
                        else if (node.XmlType.NamespaceUri == XFUri &&
                                 (node.XmlType.Name == "DataTemplate" || node.XmlType.Name == "ControlTemplate"))
                        {
                            var prop = ReadNode(reader, true);
                            if (prop != null)
                                node.Properties.Add(XmlName._CreateContent, prop);
                        }
                        // 4. Implicit content, implicit collection, or collection syntax. Add to CollectionItems, resolve case later.
                        else
                        {
                            var item = ReadNode(reader, true);
                            if (item != null)
                                node.CollectionItems.Add(item);
                        }
                        break;
                    case XmlNodeType.Whitespace:
                        break;
                    case XmlNodeType.Text:
                    case XmlNodeType.CDATA:
                        if (node.CollectionItems.Count == 1 && node.CollectionItems[0] is ValueNode)
                            ((ValueNode)node.CollectionItems[0]).Value += reader.Value.Trim();
                        else
                            node.CollectionItems.Add(new ValueNode(reader.Value.Trim(), (IXmlNamespaceResolver)reader));
                        break;
                    default:
                        Debug.WriteLine("Unhandled node {0} {1} {2}", reader.NodeType, reader.Name, reader.Value);
                        break;
                }
            }
        }

        // (省略)
    }
}

ParseXamlElementForNodeType毎にnode(rootNode)への追加の仕方が振り分けられています。

このXamlParser.ParseXamlで整理された要素は、先ほど見たXamlLoader.Load内の次のVisitメソッドでクラスとしてviewにセットされています。(急に雑)

答え合わせという訳でもありませんが、以下のUnit Testを見ることで、LoadFromXamlへの入力と関数を実行した結果へのイメージがより具体的になると思います。

こちらのテストなんていかがでしょうか?

インスタンス化したContentPageにXamlを渡してUIを組み立てています。

ブレークポイントを貼ってデバッグ実行すると、過程がより追いやすいです。

https://github.com/xamarin/Xamarin.Forms/blob/master/Xamarin.Forms.Xaml.UnitTests/LoaderTests.cs

LoaderTests.cs
 // (省略)
[Test]
        public void TestBindingModeAndConverter ()
        {
            var xaml = @"
                <ContentPage 
                xmlns=""http://xamarin.com/schemas/2014/forms""
                xmlns:x=""http://schemas.microsoft.com/winfx/2006/xaml""
                xmlns:local=""clr-namespace:Xamarin.Forms.Xaml.UnitTests;assembly=Xamarin.Forms.Xaml.UnitTests"">
                    <ContentPage.Resources>
                        <ResourceDictionary>
                            <local:ReverseConverter x:Key=""reverseConverter""/>
                        </ResourceDictionary>
                    </ContentPage.Resources>
                    <ContentPage.Content>
                        <StackLayout Orientation=""Vertical"">
                            <StackLayout.Children>
                                <Label x:Name=""label0"" Text=""{Binding Text, Converter={StaticResource reverseConverter}}""/>
                                <Label x:Name=""label1"" Text=""{Binding Text, Mode=TwoWay}""/>
                            </StackLayout.Children>
                        </StackLayout>
                    </ContentPage.Content>
                </ContentPage>";

            var contentPage = new ContentPage ();
            contentPage.LoadFromXaml (xaml);
            contentPage.BindingContext = new ViewModel { Text = "foobar" };
            var label0 = contentPage.FindByName<Label> ("label0");
            var label1 = contentPage.FindByName<Label> ("label1");
            Assert.AreEqual ("raboof", label0.Text);

            label1.Text = "baz";
            Assert.AreEqual ("baz", ((ViewModel)(contentPage.BindingContext)).Text);
        }
// (省略)

こちらはObsoleteなメソッド向けですね。
https://github.com/xamarin/Xamarin.Forms/blob/master/Xamarin.Forms.Xaml.UnitTests/XamlLoaderCreateTests.cs

XamlLoaderCreateTests.cs
 // (省略)
[TestFixture]
    public class XamlLoaderCreateTests
    {
        [Test]
        public void CreateFromXaml ()
        {
            var xaml = @"
                <ContentView xmlns=""http://xamarin.com/schemas/2014/forms""
                             xmlns:x=""http://schemas.microsoft.com/winfx/2009/xaml""
                             x:Class=""Xamarin.Forms.Xaml.UnitTests.FOO"">
                    <Label Text=""Foo""  x:Name=""label""/>
                </ContentView>";

            var view = XamlLoader.Create (xaml);
            Assert.That (view, Is.TypeOf<ContentView> ());
            Assert.AreEqual ("Foo", ((Label)((ContentView)view).Content).Text);
        }
// (省略)

一度このように追っておくと、InitializeComponentが見つからないようなエラーのときはInitializeComponent定義が想定通りの名前空間に生成されているか、InitializeComponentの前にXAMLで定義したコントロールにアクセスしようとしても無理などこれまで遭遇したかもしれないエラーを避けることができます。

また「使い方」として説明されていたことが実装としてどう実現されているのかがだいぶしっくりくるかもしれません。

付録は次のような「使い方」を説明する記事です。

  • XAMLの構文
  • マークアップ拡張
  • データバインディング
  • MVVM触り
  • XAMLコンパイル
  • XAML名前空間
  • バインダブルプロパティ
  • 添付プロパティ
  • リソースディクショナリー
  • XAML Standard

Xamarin.Formsリポジトリを登場するキーワードを検索するともちろん引っかかるので興味がある方は追ってみてください。

XAMLC

なんで実行時にXAMLを読み込むんだろう?先にコンパイルしておけばより高速にUIを表示できるのではないか?と考える人もいるかもしれません。

そこで XAMLCの登場です。

公式ページでは次のように説明されています。

XAML can be optionally compiled directly into intermediate language (IL) with the XAML compiler (XAMLC).

XAMLC offers a number of a benefits:

It performs compile-time checking of XAML, notifying the user of any errors.
It removes some of the load and instantiation time for XAML elements.
It helps to reduce the file size of the final assembly by no longer including .xaml files.
XAMLC is disabled by default to ensure backwards compatibility. It can be enabled at both the assembly and class level by adding the XamlCompilation attribute.

(訳)XAMLは、XAMLコンパイラ(XAMLC)で中間言語(IL)に直接コンパイルすることもできます。

XAMLCには多くの利点があります。

  • XAMLのコンパイル時チェックを実行し、ユーザーにエラーを通知します。
  • XAML要素のロードおよびインスタンス化時間の一部を削減します。
  • .xamlファイルを含まなくなることで、最終アセンブリのファイルサイズを縮小するのに役立ちます。

下位互換性を確保するため、XAMLCはデフォルトで無効になっています。XamlCompilation属性を追加することで、アセンブリレベルとクラスレベルの両方で有効にすることができます。

続きも翻訳しているので、気になる方はチェックしてみてください。

プレビュー版としてこの機能がリリースされたときに書かれたQiita記事では実行時にXAMLを読み込みUIを構築するケースとC#コードだけでUIを構築するケースとの比較記事もあり興味深いです。

[Xamarin.Forms 1.5.1-pre1] XamlC (XAMLプリコンパイル)

タスク本体はこのあたりを追ってください。
https://github.com/xamarin/Xamarin.Forms/blob/master/Xamarin.Forms.Build.Tasks/XamlCTask.cs

その他

XAMLの基礎を追っていくと、いつかは自作のプレビューアなどに行き着くのではないか…という期待を込めてこの記事のリンクを貼っておきます。

Xamarin.Forms 用のプレビューアをアルファ版で公開

感想

色々遠回り(次に紹介する日本語訳とか…)した割に、自分が知りたかったことの入り口は

  • コンパイルしたときにできる成果物を見る
  • フレームワークやビルドタスクの関連しそうなソースコードを追う

をやるだけだったのが教訓的だったなぁと思いました。コード追いきれてないところはありつつ…。

動作の原理を追いたいときに必ずしもコード読めば自明ということばかりではないと思います。

でも人が説明する文章追ってばかりいないで、もう少し目の前で動いているコードを動かして仮説を作ったり、直接的に答えにたどりつくためのアプローチとっていかないと効率悪いなぁと書きながら反省しっぱなしでした。

付録: Xamarin公式XAMLドキュメントの全訳

eXtensible Application Markup Language (XAML)

XAMLはユーザインターフェースを定義する宣言的なマークアップ言語です。ユーザインターフェースはXAML構文を用いてXMLファイル内で定義され、実行時のふるまいは別のコードビハインドファイル内で定義されます。

タイトル
(アンカーリンク) 
元ページURL 概要
XAMLの基本   https://developer.xamarin.com/guides/xamarin-forms/xaml/xaml-basics/ XAMLを使用すると、開発者はコードではなくマークアップを使用してXamarin.Formsアプリケーションでユーザインターフェースを定義できます。XAMLはXamarin.Formsプログラムでは必須ではなく、ツールのように使用できます。視覚的に一貫性があり、同等のコードよりも簡潔であることがよくあります。XAMLは、一般的なModel-View-ViewModel(MVVM)アプリケーションアーキテクチャでの使用に特に適しています。XAMLは、XAMLベースのデータバインディングを介してViewModelコードにリンクされたビューを定義します。
XAMLコンパイル       https://developer.xamarin.com/guides/xamarin-forms/xaml/xamlc/ XAMLは、XAMLコンパイラ(XAMLC)で中間言語(IL)に直接コンパイルすることもできます。この記事では、XAMLCの使用方法と利点について説明します。
XAMLプレビューア https://developer.xamarin.com/guides/xamarin-forms/xaml/xaml-previewer/ Xamarin Evolve 2016で発表されたXAMLプレビューアは、Alphaチャンネルでのテストに使用できます。
XAML名前空間  https://developer.xamarin.com/guides/xamarin-forms/xaml/namespaces/ XAMLは、名前空間宣言にxmlns XML属性を使用します。この記事では、XAML名前空間の構文を紹介し、型にアクセスするためにXAML名前空間を宣言する方法を示します。
引数の受け渡し https://developer.xamarin.com/guides/xamarin-forms/xaml/passing-arguments/ XAMLを使用すると、デフォルトでないコンストラクタやファクトリメソッドに引数を渡すことができます。この記事では、コンストラクタに引数を渡したり、ファクトリメソッドを呼び出したり、ジェネリックな引数の型を指定するために使用できるXAML属性の使用方法について説明します。
バインダブルプロパティ https://developer.xamarin.com/guides/xamarin-forms/xaml/bindable-properties/ Xamarin.Formsでは、共通言語ランタイム(CLR)プロパティの機能がバインダブルプロパティによって拡張されています。バインダブルプロパティは特殊なタイプのプロパティです。プロパティの値はXamarin.Formsプロパティシステムによって追跡されます。この記事では、バインダブルプロパティの概要を紹介し、バインダブルプロパティを作成および使用する方法を示します。
添付プロパティ https://developer.xamarin.com/guides/xamarin-forms/xaml/attached-properties/ 添付プロパティはバインダブルプロパティの特殊な型であり、1つのクラスで定義されていますが他のオブジェクトに関連付けられています。XAMLでは、クラスとプロパティ名がピリオドで区切られた属性として認識されます。この記事では、添付プロパティの概要を説明し、それらを作成および使用する方法を示します。
リソースディクショナリー https://developer.xamarin.com/guides/xamarin-forms/xaml/resource-dictionaries/ XAMLリソースは、複数回使用できるオブジェクトの定義です。リソースディクショナリーを使用すると、リソースを単一の場所に定義し、Xamarin.Formsアプリケーション全体で再利用することができます。この資料では、リソースディクショナリーを作成および使用する方法と、リソースディクショナリーを別のリソースディクショナリーにマージする方法について説明します。

Xamarin.FormsのXAMLの基本

モバイルデバイス向けのクロスプラットフォームマークアップを始める

XAML(eXtensible Application Markup Language)を使用すると開発者はコードではなくマークアップを使用して、Xamarin.Formsアプリケーションでユーザインターフェースを定義できます。XAMLはXamarin.Formsプログラムでは必須ではなく、ツールのように使用できます。視覚的に一貫性があり、同等のコードよりも簡潔であることがよくあります。XAMLは、一般的なModel-View-ViewModel(MVVM)アプリケーションアーキテクチャでの使用に特に適しています。XAMLは、XAMLベースのデータバインディングを介してViewModelコードにリンクされたビューを定義します。

XAMLの基本のコンテンツ

XAMLの基本記事に加え、以下の本の各章をダウンロードできます。
Creating Mobile Apps with Xamarin.Forms Book

  • 第7章 XAMLとコード
  • 第8章 コードとXAMLの調和
  • 第10章 XAMLマークアップ拡張
  • 第18章 MVVM

概要

XAMLは、オブジェクトのインスタンス化と初期化、および親子階層内でのオブジェクトの整理のためのプログラミングコードの代替としてMicrosoftによって開発されたXMLベースの言語です。XAMLは.NETフレームワーク内のいくつかのテクノロジに採用されていますが、とりわけWindows Presentation Foundation(WPF)、Silverlight、WindowsRuntime、ユニバーサルWindowsプラットフォーム(UWP)内のユーザインターフェースのレイアウトを定義する上で大きな役割を果たしています 。

XAMLは、iOS、Android、およびUWPモバイルデバイス用のクロスプラットフォームネイティブベースのプログラミングインターフェースであるXamarin.Formsの一部です。Xamarin.Formsの開発者は、XAMLファイル内で、すべてのXamarin.Formsビュー、レイアウト、ページ、およびカスタムクラスを使用してユーザインターフェースを定義できます。XAMLファイルは、コンパイルするか、実行可能ファイルに埋め込むことができます。どちらの方法でも、XAML情報はビルド時に名前付きオブジェクトの検索、実行時にオブジェクトのインスタンス化と初期化、およびこれらのオブジェクトとプログラミングコード間のリンクを確立するために解析されます。

XAMLには、同等のコードに比べていくつかの利点があります。

  • XAMLは、同等のコードよりも簡潔で読みやすいことがよくあります。
  • XML固有の親子階層により、XAMLはユーザインターフェースオブジェクトの親子階層をより明確に視覚化することができます。
  • XAMLはプログラマが簡単に手書きすることができますが、視覚的なデザインツールでツールを作成して生成することもできます。

もちろん、主にマークアップ言語に内在する制限に関連する欠点もあります。

  • XAMLにはコードを含めることはできません。すべてのイベントハンドラは、コードファイルで定義する必要があります。
  • XAMLには繰り返し処理のループを含めることはできません。(ただし、ListViewなどのいくつかのXamarin.Formsビジュアルオブジェクトでは、ItemsSourceコレクション内のオブジェクトに基づいて複数の子を生成できます)
  • XAMLには条件付き処理を含めることはできません。(ただし、データバインディングは、条件付き処理を効果的に行えるようにするコードベースのバインディングコンバーターを参照できます)
  • XAMLは通常、パラメータのないコンストラクタを定義しないクラスはインスタンス化できません。(しかし、この制限を回避する方法があることもあります)
  • XAMLは一般的にメソッドを呼び出すことはできません。(この場合も、制限を克服できることがあります)

Xamarin.FormsアプリケーションでXAMLを生成するビジュアルデザイナーはまだありません。すべてのXAMLは手書きでなければなりませんが、XAMLプレビューアがあります。XAMLを初めて使用するプログラマは、特に明らかには正しくないアプリケーションを実行した後に、頻繁にアプリケーションをビルドして実行したいかもしれません。XAMLの多くの経験を持つ開発者でさえ、実験は報われることでしょう。

XAMLは基本的にXMLですが、XAMLには独自の構文機能があります。最も重要なものは次のとおりです。

  • プロパティ要素
  • 添付プロパティ
  • マークアップ拡張

これらの機能はXML拡張ではありません。XAMLは完全に法的なXMLです。しかし、これらのXAML構文機能はXMLを独自の方法で使用します。詳細は、以下の記事で説明します。これについてははMVVMを実装するためのXAMLの使用についての紹介があります。

要件

この記事では、Xamarin.Formsに精通していることを前提としています。An Introduction to Xamarin.Forms(リンク貼る)を読むことを強くお勧めします。

この記事では、XML名前空間宣言の使用方法、要素、タグ、属性という用語の理解を含む、XMLへのある程度の慣れを想定しています。
Xamarin.FormsとXMLに精通しているときは、第1部「XAML入門」を読んでください。

パート1 XAML入門

要素と属性を持つページの定義

Xamarin.Formsアプリケーションでは、XAMLは主にページのビジュアルコンテンツを定義するために使用されます。XAMLファイルは、常にマークアップのコードサポートを提供するC#コードファイルに関連付けられています。また、これらの2つのファイルは、子ビューとプロパティの初期化を含む新しいクラス定義に貢献します。XAMLファイル内で、クラスとプロパティはXML要素と属性で参照され、マークアップとコード間のリンクが確立されます。

ソリューションの作成

最初のXAMLファイルの編集を開始するには、Visual StudioまたはVisual Studio for Macを使用して、新しいXamarin.Formsソリューションを作成します。

Visual Studio for Macで、メニューからFile > New Solutionを選択します。New Projectダイアログで、左側にあるMultiplatform > Appを選択し、テンプレートリストからBlank Forms AppForms Appではありません)を選択します。

New Project Dialog 1

Nextをクリックします。

次のダイアログで、プロジェクトにXamlSamples(または好きなもの)の名前を付けます。Use Portable Class Libraryラジオボタンが選択されていること、Use XAML for user interface filesチェックボックスがオンになっていることを確認します。

New Project Dialog 2

Nextをクリックします。

次のダイアログではプロジェクトの場所を選択できます。

New Project Dialog 3

Createをクリックします。

このソリューションには、XamlSamplesポータブルクラスライブラリ(PCL)、XamlSamples.AndroidXamlSamples.iOSの3つのプロジェクトが作成されています。

XamlSamplesソリューションを作成したら、ソリューションのスタートアッププロジェクトとしてさまざまなプラットフォームプロジェクトを選択し、プロジェクトエミュレータまたは実際のデバイスのいずれかにプロジェクトテンプレートで作成した単純なアプリケーションをビルドして展開することで、開発環境をテストできます。

プラットフォーム固有のコードを記述する必要がない限り、共有のXamlSamples PCLプロジェクトはプログラミング時間をほとんど費やす場所です。これらの記事は、そのプロジェクトの外の話はしません。

XAMLファイルの構造

XamlSamplesポータブルクラスライブラリには、次の名前のファイルがあります。

  • XAMLファイルである App.xaml
  • XAMLファイルに関連付けられたC#コードビハインドファイルであるApp.xaml.cs

コードビハインドファイルを表示するには、App.xamlの横にある矢印をクリックする必要があります。

App.xamlApp.xaml.csの両方が、Applicationから派生したAppという名前のクラスに寄与します。XAMLファイルを持つ他のほとんどのクラスは、ContentPageから派生したクラスに寄与します。これらのファイルはXAMLを使用してページ全体のビジュアルコンテンツを定義します。これは、XamlSamplesプロジェクトの他の2つのファイルにも当てはまります。

  • XAMLファイルである XamlSamplesPage.xaml
  • XAMLファイルに関連付けられたC#コードビハインドファイルであるXamlSamplesPage.xaml.cs

XamlSamplesPage.xamlファイルは次のようになります。

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:local="clr-namespace:XamlSamples"
             x:Class="XamlSamples.XamlSamplesPage">

    <Label Text="Welcome to Xamarin Forms!"
           VerticalOptions="Center"
           HorizontalOptions="Center" />

</ContentPage>

2つのXML名前空間(xmlns)宣言は、URIを参照しています。最初はXamarinのWebサイトに、もう1つはMicrosoftのものです。これらのURIが指しているものを確認する必要はありません。そこには何もありません。これらは単純にXamarinとMicrosoftが所有するURIであり、基本的にはバージョン識別子として機能します。

最初のXML名前空間宣言は、XAMLファイル内でプレフィックスなしで定義されたタグがXamarin.Formsのクラス(たとえばContentPage)を参照することを意味します。2番目の名前空間宣言はプレフィックスxを定義します。これは、XAML自体に固有でXAMLの他の実装でサポートされているいくつかの要素と属性に対して使用されます。ただし、これらの要素と属性は、URIに埋め込まれた年によって少し異なります。Xamarin.Formsは2009 XAML仕様をサポートしていますが、そのすべてはサポートしていません。

local名前空間宣言により、PCLプロジェクトから他のクラスにアクセスすることができます。

最初のタグの最後の部分で、xプレフィックスがClassという名前の属性に使用されます。このxプレフィックスの使用はXAML名前空間では事実上共通しているため、ClassなどのXAML属性はほとんどの場合x:Classと呼ばれます。

x:Class属性は、完全修飾された.NETクラス名、つまりXamlSamples名前空間のXamlSamplesPageクラスを指定します。つまり、このXAMLファイルは、x:Class属性が表示されるタグであるContentPageから派生したXamlSamples名前空間にXamlSamplesPageという名前の新しいクラスを定義します。

x:Class属性は、派生C#クラスを定義するためにXAMLファイルのルート要素にのみ使用できます。これは、XAMLファイルで定義された唯一の新しいクラスです。XAMLファイルに表示されるその他のものは、単に既存のクラスからインスタンス化され、初期化されます。

XamlSamplesPage.xaml.csファイルは次のようになります。

using Xamarin.Forms;

namespace XamlSamples
{
    public partial class XamlSamplesPage : ContentPage
    {
        public XamlSamplesPage()
        {
            InitializeComponent();
        }
    }
}

XamlSamplesPageクラスはContentPageから派生していますが、partialクラス定義に注目してください。XamlSamplesPageの別のpartialクラス定義を持つ別のC#ファイルがあるはずですが、どこでしょうか?そしてInitializeComponentメソッドとは何でしょうか?

Visual Studio for Macがプロジェクトをビルドすると、XAMLファイルが解析されてC#コードファイルが生成されます。XamlSamples\XamlSamples\obj\Debugディレクトリを見ると、XamlSamples.XamlSamplesPage.xaml.g.csという名前のファイルがあります。'g'は生成されたという意味です。これは、XamlSamplesPageコンストラクタから呼び出されたInitializeComponentメソッドの定義を含むXamlSamplesPageの他の部分クラス定義です。これらの2つのXamlSamplesPage部分クラス定義はまとめてコンパイルされます。XAMLがコンパイルされているかどうかによって、XAMLファイルまたはバイナリ形式のXAMLファイルが実行可能ファイルに埋め込まれます。

実行時に、特定のプラットフォームプロジェクトのコードがLoadApplicationメソッドを呼び出し、PCLのAppクラスの新しいインスタンスをそのクラスに渡します。Appクラスのコンストラクタは、XamlSamplesPageをインスタンス化します。そのクラスのコンストラクタはInitializeComponentを呼び出し、次に、XAMLファイル(またはそのコンパイルされたバイナリ)をPCLから抽出するLoadFromXamlメソッドを呼び出します。LoadFromXamlは、XAMLファイルで定義されているすべてのオブジェクトを初期化し、それらをすべて親子関係で接続し、コードで定義されているイベントハンドラをXAMLファイルに設定されたイベントに関連付け、オブジェクトのツリーをページのコンテンツとして設定します。

生成されたコードファイルでは通常は時間を費やす必要はありませんが、生成されたファイルのコードでは実行時の例外が発生することがあります。

このプログラムをコンパイルして実行すると、XAMLが示唆するように、Label要素がページの中央に表示されます。左からiOS、Android、Windows 10 Mobileです。

Default Xamarin.Forms display

より興味深いビジュアルを得るには、もっと興味深いXAMLが必要です。

準備

Windows用のVisual Studioで作成されたファイルとVisual Studio for Macでファイル名を一致させるには、XamlSamplesPage.xamlの名前をMainPage.xamlに、XamlSamplesPage.xaml.csの名前をMainPage.xaml.csに変更します。XamlSamplesPage.xamlファイル内で、XamlSamplesPageMainPageに変更します。XamlSamplesPage.xaml.csファイル内で、2回出現するXamlSamplesPageMainPageに変更します。
App.xaml.csファイル内で、宣言を変更します。

MainPage = new XamlSamplesPage();

MainPage = new MainPage();

続行する前に、プログラムがコンパイルおよびデプロイされることをテストします。

新しいXAMLページの追加

他のXAMLベースのContentPageクラスをプロジェクトに追加するには、XamlSamples PCLプロジェクトを選択し、File > New Fileメニュー項目を呼び出します。New Fileダイアログの左にあるFormsを選択し、Forms ContentPage Xaml(コード・オンリー・ページを作成するForms ContentPageでもページではないContent Viewでもありません)を選択します。ページに名前を付けます(例:HelloXamlPage)。

New File Dialog

HelloXamlPage.xamlとコードビハインドファイルHelloXamlPage.xaml.csの2ファイルがプロジェクトに追加されます。

ページコンテンツの設定

唯一のタグがContentPageとContentPage.Contentのタグであるように、HelloXamlPage.xamlファイルを編集します。

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="XamlSamples.HelloXamlPage">
    <ContentPage.Content>

    </ContentPage.Content>
</ContentPage>

ContentPage.Contentタグは、XAMLの一意の構文の一部です。最初は、無効なXMLと思われるかもしれませんが、有効です。ピリオドはXMLでは特殊文字ではありません。

ContentPage.Contentタグは、プロパティ要素タグと呼ばれます。ContentContentPageのプロパティであり、通常、単一のビューまたは子ビューを持つレイアウトに設定されます。通常、プロパティはXAMLの属性になりますが、複雑なオブジェクトにContent属性を設定するのは困難でしょう。そのため、プロパティは、クラス名とプロパティ名をピリオドで区切ったXML要素として表現されます。Contentプロパティは、ContentPage.Contentタグの間で次のように設定できます。

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="XamlSamples.HelloXamlPage"
             Title="Hello XAML Page">
    <ContentPage.Content>

        <Label Text="Hello, XAML!"
               VerticalOptions="Center"
               HorizontalTextAlignment="Center"
               Rotation="-15"
               IsVisible="true"
               FontSize="Large"
               FontAttributes="Bold"
               TextColor="Blue" />

    </ContentPage.Content>
</ContentPage>

また、ルートタグにTitle属性が設定されていることにも注意してください。

この時点で、クラス、プロパティ、およびXMLの関係が明白でなければなりません。Xamarin.Formsクラス(ContentPageLabelなど)は、XML要素としてXAMLファイルに表示されます。ContentPageTitleLabelの7つのプロパティを含むそのクラスのプロパティは、通常、XML属性として表示されます。

これらのプロパティの値を設定するためのショートカットが多数存在します。たとえば、TitleTextのプロパティはString型、RotationDouble型、IsVisible(デフォルトではtrueで、ここではイラストレーション用にのみ設定されています)はBoolean型です。

Horizo​​ntalTextAlignmentプロパティの型はTextAlignmentで、列挙型です。任意の列挙型のプロパティに対して、必要なのはメンバ名だけです。

ただし、より複雑な型のプロパティでは、XAMLの解析にコンバーターが使用されます。これらはTypeConverterから派生したXamarin.Formsのクラスです。多くは公開クラスですが、一部は公開クラスではありません。この特定のXAMLファイルでは、これらのクラスのいくつかがバックグラウンドで役割を果たします。

  • VerticalOptionsプロパティ用のLayoutOptionsConverter
  • FontSizeプロパティ用のFontSizeConverter
  • TextColorプロパティ用のColorTypeConverter

これらのコンバータは、プロパティ設定の許容される構文を管理します。

ThicknessTypeConverterは、カンマで区切られた1つ、2つ、または4つの数値を処理できます。1つの番号が指定されている場合、それは4つの側面すべてに適用されます。2つの数字の場合、最初は左と右のパディング、もう1つは上と下です。4つの数字は左、上、右、下の順になります。

LayoutOptionsConverterは、LayoutOptions構造体のpublic staticフィールドの名前をLayoutOptions型の値に変換できます。

FontSizeConverterは、NamedSizeメンバーまたは数値フォントサイズを処理できます。

ColorTypeConverterは、アルファチャンネルの有無にかかわらず、数字の記号(#)が前に付いたColor構造体または16進数のRGB値のpublic staticフィールドの名前を受け入れます。アルファチャンネルのない構文は次のとおりです。

TextColor = "#rrggbb"

各小文字は16進数です。アルファチャンネルがどのように含まれているかは次のとおりです。

TextColor = "#aarrggbb">

アルファチャンネルの場合、FFは完全に不透明で00は完全に透明であることに注意してください。

他の2つのフォーマットでは、各チャンネルに対して1つの16進数字のみを指定できます。

TextColor="#rgb" TextColor="#argb"

これらの場合、数字を繰り返して値を形成します。たとえば、#CF3はRGBの色CC-FF-33です。

ページナビゲーション

XamlSamplesプログラムを実行すると、MainPageが表示されます。新しいHelloXamlPageを見るには、App.xaml.csファイルの新しいスタートアップページとして設定するか、MainPageから新しいページに遷移させます。

ナビゲーションを実装するには、まずApp.xaml.csコンストラクタのコードを変更して、NavigationPageオブジェクトを作成します。

public App()
{
    InitializeComponent();
    MainPage = new NavigationPage(new MainPage());
}

MainPage.xaml.csコンストラクタでは、シンプルなButtonを作成し、HelloXamlPageに移動するためにイベントハンドラを使用することができます。

public MainPage()
{
    InitializeComponent();

    Button button = new Button
    {
        Text = "Navigate!",
        HorizontalOptions = LayoutOptions.Center,
        VerticalOptions = LayoutOptions.Center
    };

    button.Clicked += async (sender, args) =>
    {
        await Navigation.PushAsync(new HelloXamlPage());
    };

    Content = button;
}

ページのContentプロパティを設定すると、XAMLファイルのContentプロパティの設定が置き換えられます。このプログラムの新しいバージョンをコンパイルしてデプロイすると、画面にボタンが表示されます。押すと、HelloXamlPageに遷移します。iPhone、Android、およびWindows 10 Mobileデバイスの結果ページは次のとおりです。

Rotated Label Text

iOSでは < Back ボタンを使用してメインページに戻ることができます。Androidではページの上部にある左矢印か画面下部にある矢印を使用します。Windows 10 Mobileではページ下部にある左矢印を使用します。

ラベルをレンダリングするさまざまな方法についてXAMLを自由に試してみてください。Unicode文字をテキストに埋め込む必要がある場合は、標準のXML構文を使用できます。たとえば、挨拶をスマートクオートで囲むには、次のようにします。

<Label Text="&#x201C;Hello, XAML!&#x201D;"  />

以下はその様子です。

Rotated Label Text with Unicode Characters

XAMLとコードの相互作用

HelloXamlPageサンプルには、ページ上の単一のラベルのみが含まれていますが、こういう状況はあまりありません。ほとんどのContentPage派生クラスは、StackLayoutのような何らかのレイアウトにContentプロパティを設定します。StackLayoutChildrenプロパティは、IList 型に定義されていますが、実際はElementCollection 型のオブジェクトであり、そのコレクションには複数のビューまたは他のレイアウトが設定されます。XAMLでは、これらの親子関係は通常のXML階層で確立されます。XamlPlusCodePageという名前の新しいページ用のXAMLファイルは次のとおりです。

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="XamlSamples.XamlPlusCodePage"
             Title="XAML + Code Page">
    <StackLayout>
        <Slider VerticalOptions="CenterAndExpand" />

        <Label Text="A simple Label"
               Font="Large"
               HorizontalOptions="Center"
               VerticalOptions="CenterAndExpand" />

        <Button Text="Click Me!"
                HorizontalOptions="Center"
                VerticalOptions="CenterAndExpand" />
    </StackLayout>
</ContentPage>

このXAMLファイルは構文的に完成しており、次のようになります。

Multiple Controls on a Page

しかし、あなたはきっとこのプログラムは機能的に不完全であると考えていることでしょう。おそらくSliderLabelに現在の値を表示させ、Buttonはおそらくプログラム内で何かを行うことを意図しています。

パート4 データバインディングの基礎で説明するように、Labelを使用してSlider値を表示するには、データバインディングを使用してXAMLで完全に処理できます。しかし、コードソリューションを最初に見ておくと便利です。それでも、ボタンクリックを処理するにはコードが必要です。つまり、XamlPlusCodePageのコードビハインドファイルには、SliderValueChangedイベントとButtonClickedイベントのハンドラが含まれている必要があります。それらを追加しましょう。

namespace XamlSamples
{
    public partial class XamlPlusCodePage
    {
        public XamlPlusCodePage()
        {
            InitializeComponent();
        }

        void OnSliderValueChanged(object sender, ValueChangedEventArgs args)
        {

        }

        void OnButtonClicked(object sender, EventArgs args)
        {

        }
    }
}

これらのイベントハンドラはpublicである必要はありません。

XAMLファイルに戻ると、SliderタグとButtonタグには、これらのハンドラを参照するValueChangedイベントとClickedイベントの属性が含まれている必要があります。

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="XamlSamples.XamlPlusCodePage"
             Title="XAML + Code Page">
    <StackLayout>
        <Slider VerticalOptions="CenterAndExpand"
                ValueChanged="OnSliderValueChanged" />

        <Label Text="A simple Label"
               Font="Large"
               HorizontalOptions="Center"
               VerticalOptions="CenterAndExpand" />

        <Button Text="Click Me!"
                HorizontalOptions="Center"
                VerticalOptions="CenterAndExpand"
                Clicked="OnButtonClicked" />
    </StackLayout>
</ContentPage>

イベントにハンドラを割り当てるのは、プロパティに値を代入するのと同じ構文です。

SliderValueChangedイベントのハンドラがLabelを使用して現在の値を表示する場合、ハンドラはコードからそのオブジェクトを参照する必要があります。

Labelにはx:Name属性で指定された名前が必要です。

<Label x:Name="valueLabel"
       Text="A simple Label"
       Font="Large"
       HorizontalOptions="Center"
       VerticalOptions="CenterAndExpand" />

x:Name属性のxプレフィックスは、この属性がXAMLに固有であることを示します。

x:Name属性に割り当てる名前は、C#変数名と同じ規則です。たとえば、文字またはアンダースコアで始まり、埋め込みスペースを含まないようにする必要があります。

ValueChangedイベントハンドラは、新しいSlider値を表示するようLabelを設定できるようになりました。新しい値はイベントの引数から取得できます。

void OnSliderValueChanged(object sender, ValueChangedEventArgs args)
{
    valueLabel.Text = args.NewValue.ToString("F3");
}

または、ハンドラは、sender引数からこのイベントを生成しているSliderオブジェクトを取得し、そこからValueプロパティを取得できます。

void OnSliderValueChanged(object sender, ValueChangedEventArgs args)
{
    valueLabel.Text = ((Slider)sender).Value.ToString("F3");
}

最初にプログラムを実行すると、ValueChangedイベントがまだ発生していないため、ラベルにSlider値が表示されません。しかし、Sliderを操作すると、値が表示されます。

Slider Value Displayed

次はButtonです。Clickedイベントに対する応答を、ボタンのTextで警告を表示してシミュレートしましょう。イベントハンドラは、sender引数をButtonに安全にキャストし、そのプロパティにアクセスできます。

async void OnButtonClicked(object sender, EventArgs args)
{
    Button button = (Button)sender;
    await DisplayAlert("Clicked!",
        "The button labeled '" + button.Text + "' has been clicked",
        "OK");
}

このメソッドはasyncとして定義されています。これは、DisplayAlertメソッドが非同期であり、前にawait演算子をおきます。その演算子はメソッド完了時に返します。このメソッドは、sender引数からイベントを発生させるButtonを取得するため、複数のボタンに対して同じハンドラを使用できます。

XAMLで定義されたオブジェクトがコードビハインドファイルで処理されるイベントを発生させ、XAMLで定義されたオブジェクトにコードビハインドファイルがx:Name属性で割り当てられた名前でアクセスできることがわかりました。これらはコードとXAMLが相互作用する2つの基本的な方法です。

新しく生成されたXamlPlusCode.xaml.g.csファイル(x:Name属性にプライベートフィールドとして割り当てられた任意の名前が含まれる)を調べることで、XAMLの動作に関する追加の洞察を得ることができます。そのファイルの簡略版は次のとおりです。

public partial class XamlPlusCodePage : ContentPage {

    private Label valueLabel;

    private void InitializeComponent() {
        this.LoadFromXaml(typeof(XamlPlusCodePage));
        valueLabel = this.FindByName<Label>("valueLabel");
    }
}

このフィールドの宣言により、あなたの管轄下のXamlPlusCodePagepartialクラスファイル内の任意の場所で変数を自由に使用することができます。実行時、フィールドはXAMLが解析された後に割り当てられます。これは、XamlPlusCodePageコンストラクターが開始されたときにvalueLabelフィールドがnullであり、InitializeComponentが呼び出された後に有効であることを意味します。

InitializeComponentがコントロールをコンストラクタに返すと、ページのビジュアルは、コードでインスタンス化され、初期化されたかのように構築されます。XAMLファイルはクラス内のどの役割も果たさなくなりました。たとえば、StackLayoutにビューを追加したり、ページのContentプロパティを完全に別のものに設定したりするなど、任意の方法でページ上のこれらのオブジェクトを操作できます。ページのContentプロパティとChildrenコレクションのレイアウトの項目を調べることで、「ツリー構造をたどる」ことができます。この方法でアクセスされるビューにプロパティを設定したり、イベントハンドラを動的に割り当てることができます。

どうぞ。それはあなたのページです。XAMLはコンテンツを構築するためのツールに過ぎません。

まとめ

ここでは、XAMLファイルとコードファイルがクラス定義にどのように寄与し、XAMLとコードファイルがどのように相互作用するかを見てきました。しかし、XAMLには独自の構文上の特徴があり、非常に柔軟に使用することができます。
パート2 XAML構文の本質で詳しく見て行きましょう。

パート2 XAML構文の本質

プロパティ要素と添付プロパティの操作

XAMLは主にオブジェクトのインスタンス化と初期化用に設計されています。しかし、多くの場合、XML文字列として簡単に表現できない複雑なオブジェクトにプロパティを設定する必要があります。また、あるクラスで定義されたプロパティを子クラスに設定する必要があることもあります。これらの2つプロパティ要素と添付プロパティのXAML構文の本質的な機能が必要です。

プロパティ要素

XAMLでは、クラスのプロパティは通常XML属性として設定されます。

<Label Text="Hello, XAML!"
       VerticalOptions="Center"
       FontAttributes="Bold"
       FontSize="Large"
       TextColor="Aqua" />

しかし、XAMLでプロパティを設定する別の方法があります。TextColorでこの代替方法を試すには、まず既存のTextColor設定を削除します。

<Label Text="Hello, XAML!"
       VerticalOptions="Center"
       FontAttributes="Bold"
       FontSize="Large" />

空要素のLabelタグを開始タグと終了タグに分けて開きます。

<Label Text="Hello, XAML!"
       VerticalOptions="Center"
       FontAttributes="Bold"
       FontSize="Large">

</Label>

次のように、これらの新しいタグの内容としてプロパティ値を設定します。

<Label Text="Hello, XAML!"
       VerticalOptions="Center"
       FontAttributes="Bold"
       FontSize="Large">
    <Label.TextColor>
        Aqua
    </Label.TextColor>
</Label>

TextColorプロパティを指定するこれらの2つの方法は機能的には同等ですが、同じプロパティに対して2つの方法を使用しないでください。プロパティを2回設定してしまい、あいまいになってしまうためです。

この新しい構文では、いくつかの便利な用語を紹介することができます。

  • Labelオブジェクト要素です。これはXML要素として表現されたXamarin.Formsオブジェクトです。
  • TextVerticalOptionsFontAttributesFontSizeプロパティ属性です。
  • 最後のスニペットでは、TextColorプロパティ要素になりました。これはXamarin.Formsプロパティですが、XML要素になりました。

プロパティ要素の定義は、最初はXML構文の違反と思われるかもしれませんが、そうではありません。ピリオドはXMLでは特別な意味を持ちません。XMLデコーダにとっては、Label.TextColorは単なる通常の子要素です。

しかし、XAMLでは、この構文は非常に特殊です。プロパティ要素の規則の1つは、Label.TextColorタグに何も表示されないことです。プロパティの値は、プロパティ要素の開始タグと終了タグの間のコンテンツとして常に定義されます。

複数のプロパティでプロパティ要素構文を使用できます。

<Label Text="Hello, XAML!"
       VerticalOptions="Center">
    <Label.FontAttributes>
        Bold
    </Label.FontAttributes>
    <Label.FontSize>
        Large
    </Label.FontSize>
    <Label.TextColor>
        Aqua
    </Label.TextColor>
</Label>

または、すべてのプロパティーに対してプロパティー要素構文を使用できます。

<Label>
    <Label.Text>
        Hello, XAML!
    </Label.Text>
    <Label.FontAttributes>
        Bold
    </Label.FontAttributes>
    <Label.FontSize>
        Large
    </Label.FontSize>
    <Label.TextColor>
        Aqua
    </Label.TextColor>
    <Label.VerticalOptions>
        Center
    </Label.VerticalOptions>
</Label>

最初に、プロパティ要素の構文は比較的簡単なものの、不要で冗長な置き換えのように見えるかもしれません。これらの例では確かです。

しかし、プロパティの値が複雑すぎて単純な文字列として表現できない場合は、プロパティ要素の構文が不可欠になります。プロパティ要素タグ内で、別のオブジェクトをインスタンス化してプロパティを設定することができます。たとえば、VerticalOptionsなどのプロパティをプロパティ設定でLayoutOptions値に明示的に設定することができます。

<Label>
    ...
    <Label.VerticalOptions>
        <LayoutOptions Alignment="Center" />
    </Label.VerticalOptions>
</Label>

別の例: GridにはRowDefinitionsColumnDefinitionsという2つのプロパティがあります。これら2つのプロパティは、RowDefinitionCollectionColumnDefinitionCollectionタイプです。これらはRowDefinitionColumnDefinitionオブジェクトのコレクションです。

RowDefinitionsコレクションとColumnDefinitionsコレクションのプロパティ要素タグを示す、GridDemoPageクラスのXAMLファイルの最初は次のとおりです。

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="XamlSamples.GridDemoPage"
             Title="Grid Demo Page">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto" />
            <RowDefinition Height="*" />
            <RowDefinition Height="100" />
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="Auto" />
            <ColumnDefinition Width="*" />
            <ColumnDefinition Width="100" />
        </Grid.ColumnDefinitions>
        ...
    </Grid>
</ContentPage>

自動サイズのセル、ピクセルの幅と高さのセル、および星の設定を定義するための省略形の構文に注目してください。

添付プロパティ

Gridは、行と列を定義するためにRowDefinitionsコレクションとColumnDefinitionsコレクションのプロパティ要素が必要であることがわかりました。しかし、プログラマがGridの子が置かれている行と列を指示する方法もなければなりません。

Gridの子のタグ内で、次の属性を使用してその子の行と列を指定します。

  • Grid.RowSpan
  • Grid.ColumnSpan

これら2つの属性のデフォルト値は1です。

完成したGridDemoPage.xamlファイルがこちらです。

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="XamlSamples.GridDemoPage"
             Title="Grid Demo Page">

    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto" />
            <RowDefinition Height="*" />
            <RowDefinition Height="100" />
        </Grid.RowDefinitions>

        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="Auto" />
            <ColumnDefinition Width="*" />
            <ColumnDefinition Width="100" />
        </Grid.ColumnDefinitions>

        <Label Text="Autosized cell"
               Grid.Row="0" Grid.Column="0"
               TextColor="White"
               BackgroundColor="Blue" />

        <BoxView Color="Silver"
                 HeightRequest="0"
                 Grid.Row="0" Grid.Column="1" />

        <BoxView Color="Teal"
                 Grid.Row="1" Grid.Column="0" />

        <Label Text="Leftover space"
               Grid.Row="1" Grid.Column="1"
               TextColor="Purple"
               BackgroundColor="Aqua"
               HorizontalTextAlignment="Center"
               VerticalTextAlignment="Center" />

        <Label Text="Span two rows (or more if you want)"
               Grid.Row="0" Grid.Column="2" Grid.RowSpan="2"
               TextColor="Yellow"
               BackgroundColor="Blue"
               HorizontalTextAlignment="Center"
               VerticalTextAlignment="Center" />

        <Label Text="Span two columns"
               Grid.Row="2" Grid.Column="0" Grid.ColumnSpan="2"
               TextColor="Blue"
               BackgroundColor="Yellow"
               HorizontalTextAlignment="Center"
               VerticalTextAlignment="Center" />

        <Label Text="Fixed 100x100"
               Grid.Row="2" Grid.Column="2"
               TextColor="Aqua"
               BackgroundColor="Red"
               HorizontalTextAlignment="Center"
               VerticalTextAlignment="Center" />

    </Grid>
</ContentPage>

Grid.RowGrid.Columnの0の設定は必須ではありませんが、一般的には分かりやすくするために含まれています。

3つのプラットフォームすべてでこのように見えます。

Grid Layout

これらのGrid.RowGrid.ColumnGrid.RowSpanGrid.ColumnSpanの各属性は、構文から判断すると、静的なフィールドかGridのプロパティであるように見えますが、興味深いことに、GridではRowColumnRowSpanColumnSpanという名前は定義されていません。

その代わり、GridではRowPropertyColumnPropertyRowSpanPropertyColumnSpanPropertyという4つのバインド可能なプロパティが定義されています。これらは、添付プロパティとして知られている特別なタイプのバインド可能なプロパティです。それらはGridクラスによって定義されますが、Gridの子に設定されます。

これらの添付プロパティをコードで使用する場合、GridクラスはSetRowGetColumnなどの静的メソッドを提供します。しかし、XAMLでは、これらの添付プロパティは、単純なプロパティ名を使用してGridの子に属性として設定されます。

添付プロパティは、XAMLファイルでは、クラスとプロパティ名をピリオドで区切った属性として認識することができます。あるクラス(この例ではGrid)で定義されているが、他のオブジェクト(この場合はGridの子)に添付されているため、添付プロパティと呼ばれます。レイアウト中、Gridはこれらの添付プロパティの値を調べて、各子をどこに配置するかを知ることができます。

AbsoluteLayoutクラスは、LayoutBoundsLayoutFlagsという2つの添付プロパティを定義します。AbsoluteLayoutの比率を維持した配置とサイジングの機能を使って実現したチェッカーボードパターンです

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="XamlSamples.AbsoluteDemoPage"
             Title="Absolute Demo Page">

    <AbsoluteLayout BackgroundColor="#FF8080">
        <BoxView Color="#8080FF"
                 AbsoluteLayout.LayoutBounds="0.33, 0, 0.25, 0.25"
                 AbsoluteLayout.LayoutFlags="All" />

        <BoxView Color="#8080FF"
                 AbsoluteLayout.LayoutBounds="1, 0, 0.25, 0.25"
                 AbsoluteLayout.LayoutFlags="All" />

        <BoxView Color="#8080FF"
                 AbsoluteLayout.LayoutBounds="0, 0.33, 0.25, 0.25"
                 AbsoluteLayout.LayoutFlags="All" />

        <BoxView Color="#8080FF"
                 AbsoluteLayout.LayoutBounds="0.67, 0.33, 0.25, 0.25"
                 AbsoluteLayout.LayoutFlags="All" />

        <BoxView Color="#8080FF"
                 AbsoluteLayout.LayoutBounds="0.33, 0.67, 0.25, 0.25"
                 AbsoluteLayout.LayoutFlags="All" />

        <BoxView Color="#8080FF"
                 AbsoluteLayout.LayoutBounds="1, 0.67, 0.25, 0.25"
                 AbsoluteLayout.LayoutFlags="All" />

        <BoxView Color="#8080FF"
                 AbsoluteLayout.LayoutBounds="0, 1, 0.25, 0.25"
                 AbsoluteLayout.LayoutFlags="All" />

        <BoxView Color="#8080FF"
                 AbsoluteLayout.LayoutBounds="0.67, 1, 0.25, 0.25"
                 AbsoluteLayout.LayoutFlags="All" />

  </AbsoluteLayout>
</ContentPage>

このように表示されます。

Absolute Layout

このような実装をするにあたっては、XAMLでの実装は賢明でないと感じるかもしれません。確かに、LayoutBounds矩形の繰り返しと規則性は、コードでよりよく実現できることを示唆しています。

これは確かに正当な懸案事項であり、ユーザインターフェースの定義時にコードとマークアップの使用のバランスをとることには問題ありません。XAMLでビジュアルの一部を定義してから、コードビハインドファイルのコンストラクタを使用して、ループでより効率的に生成されるビジュアルを追加できます。

コンテントプロパティ

前の例では、StackLayoutGridAbsoluteLayoutオブジェクトはContentPageのContentプロパティに設定され、これらのレイアウトの子は実際にはChildrenコレクション内の項目です。しかし、これらのContentおよびChildrenプロパティはXAMLファイルにはありません。

XamlPlusCodeサンプルのように、Content要素とChildrenプロパティを明示的にプロパティ要素として含めることができます。

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="XamlSamples.XamlPlusCodePage"
             Title="XAML + Code Page">
    <ContentPage.Content>
        <StackLayout>
            <StackLayout.Children>
                <Slider VerticalOptions="CenterAndExpand"
                        ValueChanged="OnSliderValueChanged" />

                <Label x:Name="valueLabel"
                       Text="A simple Label"
                       FontSize="Large"
                       HorizontalOptions="Center"
                       VerticalOptions="CenterAndExpand" />

                <Button Text="Click Me!"
                      HorizontalOptions="Center"
                      VerticalOptions="CenterAndExpand"
                      Clicked="OnButtonClicked" />
            </StackLayout.Children>
        </StackLayout>
    </ContentPage.Content>
</ContentPage>

XAMLファイルでこれらのプロパティ要素が不要なのはなぜでしょうか?

XAMLで使用するためにXamarin.Formsで定義された要素は、そのクラスのContentProperty属性にフラグが立てられた1つのプロパティを持つことができます。オンラインのXamarin.FormsドキュメントでContentPageクラスを調べると、次の属性が見つかります。

[Xamarin.Forms.ContentProperty("Content")]
public class ContentPage : TemplatedPage

これはContentプロパティ要素タグは必須ではないという意味です。ContentPageタグの先頭と末尾の間に表示されるXMLコンテンツは、Contentプロパティに割り当てられているものとみなされます。

StackLayoutGridAbsoluteLayoutRelativeLayoutはすべてLayout から派生し、Xamarin.FormsドキュメントでLayout を調べると、別のContentProperty属性が見つかります。

[Xamarin.Forms.ContentProperty("Children")]
public abstract class Layout<T> : Layout ...

これにより、レイアウトのコンテンツを明示的なChildrenプロパティ要素タグなしでChildrenコレクションに自動的に追加することができます。

他のクラスにもContentProperty属性定義があります。たとえば、LabelContentプロパティはTextです。他のAPIドキュメントをチェックしてみてください。

OnPlatformとプラットフォームの違い

シングルページアプリケーションでは、iOSのステータスバーを上書きしないように、ページのPaddingプロパティを設定するのが一般的です。コードでは、この目的でDevice.RuntimePlatformプロパティを使用できます。

if (Device.RuntimePlatform == Device.iOS)
{
    Padding = new Thickness(0, 20, 0, 0);
}

また、OnPlatformクラスとOnクラスを使用して、XAMLでも同様のことを行うことができます。まず、ページの上部にあるPaddingプロパティのプロパティ要素を含めます。

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="...">

    <ContentPage.Padding>

    </ContentPage.Padding>
    ...
</ContentPage>

これらのタグには、OnPlatformタグが含まれています。OnPlatformはジェネリッククラスです。ジェネリック型の引数を指定する必要があります。この場合は、Paddingプロパティの型であるThicknessを指定します。幸い、x:TypeArgumentsというジェネリック引数を定義するためのXAML属性があります。これは設定しているプロパティの型と一致する必要があります。

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="...">

    <ContentPage.Padding>
        <OnPlatform x:TypeArguments="Thickness">

        </OnPlatform>
    </ContentPage.Padding>
  ...
</ContentPage>

OnPlatformには、OnオブジェクトのIListであるPlatformsという名前のプロパティがあります。そのプロパティのプロパティ要素タグを使用します。

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="...">

    <ContentPage.Padding>
        <OnPlatform x:TypeArguments="Thickness">
            <OnPlatform.Platforms>

            </OnPlatform.Platforms>
        </OnPlatform>
    </ContentPage.Padding>
  ...
</ContentPage>

On要素を追加します。各要素には、Platformプロパティと、Thicknessプロパティのマークアップ用のValueプロパティを設定します。

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="...">

    <ContentPage.Padding>
        <OnPlatform x:TypeArguments="Thickness">
            <OnPlatform.Platforms>
                <On Platform="iOS" Value="0, 20, 0, 0" />
                <On Platform="Android" Value="0, 0, 0, 0" />
                <On Platform="UWP" Value="0, 0, 0, 0" />
            </OnPlatform.Platforms>
        </OnPlatform>
    </ContentPage.Padding>
  ...
</ContentPage>

このマークアップは単純化することができます。 OnPlatformContentプロパティはPlatformsであるため、これらのプロパティ要素タグは削除できます。

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="...">

    <ContentPage.Padding>
        <OnPlatform x:TypeArguments="Thickness">
            <On Platform="iOS" Value="0, 20, 0, 0" />
            <On Platform="Android" Value="0, 0, 0, 0" />
            <On Platform="UWP" Value="0, 0, 0, 0" />
        </OnPlatform>
    </ContentPage.Padding>
  ...
</ContentPage>

OnPlatformプロパティはIList 型であるため、値が同じ場合は複数のプラットフォームを含めることができます。

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="...">

    <ContentPage.Padding>
        <OnPlatform x:TypeArguments="Thickness">
            <On Platform="iOS" Value="0, 20, 0, 0" />
            <On Platform="Android, UWP" Value="0, 0, 0, 0" />
        </OnPlatform>
    </ContentPage.Padding>
  ...
</ContentPage>

AndroidとWindowsはPaddingのデフォルト値に設定されているので、そのタグは削除できます。

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="...">

    <ContentPage.Padding>
        <OnPlatform x:TypeArguments="Thickness">
            <On Platform="iOS" Value="0, 20, 0, 0" />
        </OnPlatform>
    </ContentPage.Padding>
  ...
</ContentPage>

これは、XAMLでプラットフォーム依存のPaddingプロパティを設定する標準的な方法です。Valueの設定を単一の文字列で表すことができない場合は、プロパティの要素を定義できます。

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="...">

    <ContentPage.Padding>
        <OnPlatform x:TypeArguments="Thickness">
            <On Platform="iOS">
                <On.Value>
                    0, 20, 0, 0
                </On.Value>
            </On>
        </OnPlatform>
    </ContentPage.Padding>
  ...
</ContentPage>

まとめ

プロパティ要素と添付プロパティに関して基本的なXAML構文の多くが確立されています。しかし、リソースディクショナリーなどの間接的な方法でオブジェクトにプロパティを設定する必要がある場合もあります。このアプローチは、次の部で説明されています。

パート3 XAMLマークアップ拡張

一般化されたオブジェクト参照構文の探索

XAMLマークアップ拡張は、他のソースから間接的に参照されるオブジェクトまたは値にプロパティを設定できるXAMLの重要な機能です。XAMLマークアップ拡張は、オブジェクトの共有やアプリケーション全体で使用される定数の参照に特に重要ですが、データバインディングで最大の有用性を見出します。

XAMKマークアップ拡張

一般に、XAMLを使用してオブジェクトのプロパティを文字列、数値、列挙型メンバ、または裏で値に変換された文字列などの明示的な値に設定します。

ただし、プロパティは他の場所で定義された値を参照するか、実行時にコードによって少し処理する必要がある場合があります。これらの目的のために、XAMLマークアップ拡張が利用できます。

これらのXAMLマークアップ拡張は、XMLの拡張ではありません。 XAMLは正式なXMLです。それらはIMarkupExtensionを実装しているクラスのコードに裏付けられているので、「拡張」と呼ばれています。独自のカスタムマークアップ拡張を書くことができます。

多くの場合、XAMLマークアップ拡張は中括弧({と})で区切られた属性設定として表示されるため、XAMLファイルで即座に認識されますが、マークアップ拡張が従来の要素としてマークアップに表示されることがあります。

共有リソース

一部のXAMLページには、プロパティが同じ値に設定された複数のビューが含まれています。たとえば、これらのButtonオブジェクトの多くのプロパティ設定は同じです。

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="XamlSamples.SharedResourcesPage"
             Title="Shared Resources Page">

    <StackLayout>
        <Button Text="Do this!"
                HorizontalOptions="Center"
                VerticalOptions="CenterAndExpand"
                BorderWidth="3"
                Rotation="-15"
                TextColor="Red"
                FontSize="Large" />

        <Button Text="Do that!"
                HorizontalOptions="Center"
                VerticalOptions="CenterAndExpand"
                BorderWidth="3"
                Rotation="-15"
                TextColor="Red"
                FontSize="Large" />

        <Button Text="Do the other thing!"
                HorizontalOptions="Center"
                VerticalOptions="CenterAndExpand"
                BorderWidth="3"
                Rotation="-15"
                TextColor="Red"
                FontSize="Large" />

    </StackLayout>
</ContentPage>

これらのプロパティのいずれかを変更する必要がある場合は、変更を3回ではなく1回だけ行うことをお勧めします。これがコードの場合は、定数や静的な読み取り専用オブジェクトを使用して、そのような値の一貫性を保ち、変更しやすくすることでしょう。

XAMLでは、そのような値やオブジェクトをリソースディクショナリーに格納することが一般的な解決策の1つです。VisualElementクラスはResourceDictionary型のResourcesという名前のプロパティを定義します。これは、string型のキーとobject型の値を持つ辞書です。この辞書にオブジェクトを配置し、XAML内のすべてのオブジェクトをマークアップから参照できます。

ページ上でリソースディクショナリーを使用するには、リソースのプロパティ要素タグのペアを含めます。これらをページの一番上に置くのが最も便利です。

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="XamlSamples.SharedResourcesPage"
             Title="Shared Resources Page">

    <ContentPage.Resources>

    </ContentPage.Resources>
    ...
</ContentPage>

明示的にResourceDictionaryタグを含める必要もあります。

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="XamlSamples.SharedResourcesPage"
             Title="Shared Resources Page">

    <ContentPage.Resources>
        <ResourceDictionary>

        </ResourceDictionary>
    </ContentPage.Resources>
    ...
</ContentPage>

様々なタイプのオブジェクトと値をリソースディクショナリーに追加できるようになりました。これらの型はインスタンス化可能でなければなりません。たとえば、抽象クラスにすることはできません。これらの型には、パブリックなパラメータのないコンストラクタも必要です。各項目には、x:Key属性で指定された辞書キーが必要です。例えば次のようになります。

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="XamlSamples.SharedResourcesPage"
             Title="Shared Resources Page">

    <ContentPage.Resources>
        <ResourceDictionary>
            <LayoutOptions x:Key="horzOptions"
                           Alignment="Center" />

            <LayoutOptions x:Key="vertOptions"
                           Alignment="Center"
                           Expands="True" />
        </ResourceDictionary>
    </ContentPage.Resources>
    ...
</ContentPage>

これらの2つの項目はLayoutOptions構造体型の値であり、それぞれ固有のキーと1つか2つのプロパティーセットを持ちます。コードやマークアップでは、LayoutOptionsの静的フィールドを使用する方がはるかに一般的ですが、今回はプロパティを設定する方が便利です。

これらのボタンのHorizo​​ntalOptionsプロパティとVerticalOptionsプロパティをこれらのリソースに設定する必要があります。これはStaticResource XAMLマークアップ拡張で行います

<Button Text="Do this!"
        HorizontalOptions="{StaticResource horzOptions}"
        VerticalOptions="{StaticResource vertOptions}"
        BorderWidth="3"
        Rotation="-15"
        TextColor="Red"
        FontSize="Large" />

StaticResourceマークアップ拡張子は、常に中括弧で区切られ、辞書キーが含まれています。

StaticResourceという名前は、Xamarin.FormsでもサポートされているDynamicResourceと区別されます。DynamicResourceは実行時に変更される可能性のある値に関連付けられた辞書キー用であるのに対し、StaticResourceはページ上の要素が構築されたときに、一度だけ辞書から要素にアクセスします。

BorderWidthプロパティの場合は、辞書にdouble型の値を格納する必要があります。XAMLはx:Doublex:Int32のような一般的なデータ型のタグを便利に定義します。

<ContentPage.Resources>
    <ResourceDictionary>
        <LayoutOptions x:Key="horzOptions"
                       Alignment="Center" />

        <LayoutOptions x:Key="vertOptions"
                       Alignment="Center"
                       Expands="True" />

        <x:Double x:Key="borderWidth">
            3
        </x:Double>
    </ResourceDictionary>
</ContentPage.Resources>

それを3行にする必要はありません。この回転角度の辞書エントリーは1行だけです。

<ContentPage.Resources>
    <ResourceDictionary>
        <LayoutOptions x:Key="horzOptions"
                       Alignment="Center" />

        <LayoutOptions x:Key="vertOptions"
                       Alignment="Center"
                       Expands="True" />

         <x:Double x:Key="borderWidth">
            3
         </x:Double>

        <x:Double x:Key="rotationAngle">-15</x:Double>
    </ResourceDictionary>
</ContentPage.Resources>

これらの2つのリソースは、LayoutOptionsの値と同じ方法で参照できます。

<Button Text="Do this!"
        HorizontalOptions="{StaticResource horzOptions}"
        VerticalOptions="{StaticResource vertOptions}"
        BorderWidth="{StaticResource borderWidth}"
        Rotation="{StaticResource rotationAngle}"
        TextColor="Red"
        FontSize="Large" />

Color型のリソースの場合、これらの型の属性を直接割り当てるときに使用するものと同じ文字列表現を使用できます。型コンバーターは、リソースが作成されるときに呼び出されます。ここにColorというタイプのリソースがあります。

<Color x:Key="textColor">Red</Color>

FontSizeプロパティには少し問題があります。このプロパティはdouble型であると定義されています。プロパティをLargeなどのNamedSize列挙体のメンバに設定すると、FontSizeConverterクラスは背後で動作し、Device.GetNamedSizedメソッドを使用してプラットフォーム依存の値に変換します。

しかし、フォントサイズのリソースをdoubleとして定義して値を "Large"に設定することはできません。XAMLパーサーがリソースを処理するときに、値がフォントサイズとして使用されることはわかりません。

解決策は、x:String型を使用してリソースを文字列として定義することです。

<x:String x:Key="fontSize">Large</x:String>

Text以外のすべてのプロパティは、リソース設定で定義されます。

<Button Text="Do this!"
        HorizontalOptions="{StaticResource horzOptions}"
        VerticalOptions="{StaticResource vertOptions}"
        BorderWidth="{StaticResource borderWidth}"
        Rotation="{StaticResource rotationAngle}"
        TextColor="{StaticResource textColor}"
        FontSize="{StaticResource fontSize}" />

リソースディクショナリー内でOnPlatformを使用して、プラットフォームに異なる値を定義することもできます。OnPlatformオブジェクトをさまざまなテキストカラーのリソースディクショナリーに含める方法は次のとおりです。

<OnPlatform x:Key="textColor"
            x:TypeArguments="Color">
    <On Platform="iOS" Value="Red" />
    <On Platform="Android" Value="Aqua" />
    <On Platform="UWP" Value="#80FF80" />
</OnPlatform>

OnPlatformは辞書内のオブジェクトであるためx:Key属性を取得し、ジェネリッククラスであるためx:TypeArguments属性を取得することに注意してください。オブジェクトが初期化されると、iOSAndroidUWP属性はColor値に変換されます。

6つの共有値にアクセスする3つのボタンを備えた、完成した状態のXAMLファイルは次のようになります。

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="XamlSamples.SharedResourcesPage"
             Title="Shared Resources Page">

    <ContentPage.Resources>
        <ResourceDictionary>
            <LayoutOptions x:Key="horzOptions"
                           Alignment="Center" />

            <LayoutOptions x:Key="vertOptions"
                           Alignment="Center"
                           Expands="True" />

            <x:Double x:Key="borderWidth">3</x:Double>

            <x:Double x:Key="rotationAngle">-15</x:Double>

            <OnPlatform x:Key="textColor"
                        x:TypeArguments="Color"
                <On Platform="iOS" Value="Red" />
                <On Platform="Android" Value="Aqua" />
                <On Platform="UWP" Value="#80FF80" />
            </OnPlatform>

            <x:String x:Key="fontSize">Large</x:String>
        </ResourceDictionary>
    </ContentPage.Resources>

    <StackLayout>
        <Button Text="Do this!"
                HorizontalOptions="{StaticResource horzOptions}"
                VerticalOptions="{StaticResource vertOptions}"
                BorderWidth="{StaticResource borderWidth}"
                Rotation="{StaticResource rotationAngle}"
                TextColor="{StaticResource textColor}"
                FontSize"{StaticResource fontSize}" />

        <Button Text="Do that!"
                HorizontalOptions="{StaticResource horzOptions}"
                VerticalOptions="{StaticResource vertOptions}"
                BorderWidth="{StaticResource borderWidth}"
                Rotation="{StaticResource rotationAngle}"
                TextColor="{StaticResource textColor}"
                FontSize="{StaticResource fontSize}" />

        <Button Text="Do the other thing!"
                HorizontalOptions="{StaticResource horzOptions}"
                VerticalOptions="{StaticResource vertOptions}"
                BorderWidth="{StaticResource borderWidth}"
                Rotation="{StaticResource rotationAngle}"
                TextColor="{StaticResource textColor}"
                FontSize="{StaticResource fontSize}" />

    </StackLayout>
</ContentPage>

スクリーンショットで一貫したスタイリングとプラットフォームに依存するスタイリングを見てみましょう。

Styled Controls

ページ上部でResourcesコレクションを定義するのが最も一般的ですが、ResourcesプロパティはVisualElementによって定義され、ページ上の他の要素にはResourcesコレクションを持つことができるということを覚えておいてください。例えば、この例ではStackLayoutに追加してみましょう。

<StackLayout>
    <StackLayout.Resources>
        <ResourceDictionary>
            <Color x:Key="textColor">Blue</Color>
        </ResourceDictionary>
    </StackLayout.Resources>
    ...
</StackLayout>

ボタンのテキストの色が青色になっていることがわかります。基本的には、XAMLパーサがStaticResourceマークアップ拡張を検出するたびに、ビジュアルツリーを検索し、そのキーを含む最初のResourceDictionaryを使用します。

リソース辞書に格納されている最も一般的なオブジェクトの1つは、プロパティ設定のコレクションを定義するXamarin.FormsのStyleです。スタイルについては、 Styleの記事で説明します。

XAMLを初めて使用する開発者は、LabelButtonなどのビジュアル要素をResourceDictionaryに配置できるかどうか疑問に思うことがあります。確かに可能ですが、あまり意味がありません。ResourceDictionaryの目的は、オブジェクトを共有することです。ビジュアル要素は共有できません。同じインスタンスを1ページに2回表示することはできません。

x:Staticマークアップ拡張

名前は似ていますが、x:StaticStaticResourceはかなり異なっています。StaticResourceはリソースディクショナリーからオブジェクトを返します一方でx:Staticは次のいずれかにアクセスします。

  • public staticフィールド
  • public staticプロパティ
  • public constフィールド
  • 列挙メンバー

StaticResourceマークアップ拡張はリソースディクショナリーを定義するXAML実装でサポートされ、x:StaticxプレフィックスからわかるようにXAMLの本質的な部分です。

x:staticが静的フィールドと列挙メンバーを明示的に参照できる方法を示すいくつかの例を示します。

<Label Text="Hello, XAML!"
       VerticalOptions="{x:Static LayoutOptions.Start}"
       HorizontalTextAlignment="{x:Static TextAlignment.Center}"
       TextColor="{x:Static Color.Aqua}" />

これまでのところ、これはあまり印象的ではありません。しかし、x:Staticマークアップ拡張は、自分のコードから静的フィールドやプロパティを参照することもできます。たとえば、AppConstantsクラスには、アプリケーション全体で複数のページで使用する静的フィールドがいくつか含まれているとします。

using System;
using Xamarin.Forms;

namespace XamlSamples
{
    static class AppConstants
    {
        public static readonly Thickness PagePadding;

        public static readonly Font TitleFont;

        public static readonly Color BackgroundColor = Color.Aqua;

        public static readonly Color ForegroundColor = Color.Brown;

        static AppConstants()
        {
            switch (Device.RuntimePlatform)
            {
                case Device.iOS:
                    PagePadding = new Thickness(5, 20, 5, 0);
                    TitleFont = Font.SystemFontOfSize(35, FontAttributes.Bold);
                    break;

                case Device.Android:
                    PagePadding = new Thickness(5, 0, 5, 0);
                    TitleFont = Font.SystemFontOfSize(40, FontAttributes.Bold);
                    break;

                case Device.UWP:
                    PagePadding = new Thickness(5, 0, 5, 0);
                    TitleFont = Font.SystemFontOfSize(50, FontAttributes.Bold);
                    break;
            }
        }
    }
}

XAMLファイルでこのクラスの静的フィールドを参照するには、XAMLファイル内でこのファイルの場所を示す方法が必要です。これは、XML名前空間宣言で行います。

標準のXamarin.Forms XAMLテンプレートの一部として作成されたXAMLファイルには、Xamarin.FormsクラスにアクセスするためのXML名前空間宣言と、XAMLに固有のタグと属性を参照するためのXML名前空間宣言の2つのXML名前空間宣言が含まれています。

xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"

他のクラスにアクセスするには、追加のXML名前空間宣言が必要です。追加の各XML名前空間宣言は、新しいプレフィックスを定義します。AppConstantsなど、共有アプリケーションPCLのローカルクラスにアクセスするには、XAMLプログラマはよくlocalプレフィックスを使用します。名前空間宣言は、CLR(共通言語ランタイム)名前空間名(.NET名前空間名とも呼ばれます)を示す必要があります。これは、C#名前空間定義またはusingディレクティブに表示される名前です。

xmlns:local="clr-namespace:XamlSamples"

また、PCLが参照するアセンブリ内の.NET名前空間のXML名前空間宣言を定義することもできます。たとえば、かつてmscorlibアセンブリにおいて「Microsoft Common Object Runtime Library」を意味したの標準.NET System名前空間のsysプレフィックスが今では「多言語標準共通オブジェクトランタイムライブラリ」を意味します。これは別のアセンブリなので、アセンブリ名(この場合はmscorlib)も指定する必要があります。

xmlns:sys="clr-namespace:System;assembly=mscorlib"

キーワードclr-namespaceの後にはコロン、次に.NET名前空間の名前、セミコロン、キーワードアセンブリ、等号、アセンブリ名が続きます。

そうです、コロンはclr-namespaceの後に続きますが、等号はアセンブリの後に続きます。構文はこのようにして意図的に定義されました。大部分のXML名前空間宣言は、httpなどのURIスキーム名を開始するURIを参照し、その後には常にコロンが続きます。この文字列のclr-namespace部分は、その慣習を模倣するためのものです。

これらの名前空間宣言は、両方ともStaticConstantsPageサンプルに含まれています。BoxViewの次元はMath.PIとMath.Eに設定されていますが、100倍にスケーリングされています。

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:local="clr-namespace:XamlSamples"
             xmlns:sys="clr-namespace:System;assembly=mscorlib"
             x:Class="XamlSamples.StaticConstantsPage"
             Title="Static Constants Page"
             Padding="{x:Static local:AppConstants.PagePadding}">

    <StackLayout>
       <Label Text="Hello, XAML!"
              TextColor="{x:Static local:AppConstants.BackgroundColor}"
              BackgroundColor="{x:Static local:AppConstants.ForegroundColor}"
              Font="{x:Static local:AppConstants.TitleFont}"
              HorizontalOptions="Center" />

      <BoxView WidthRequest="{x:Static sys:Math.PI}"
               HeightRequest="{x:Static sys:Math.E}"
               Color="{x:Static local:AppConstants.ForegroundColor}"
               HorizontalOptions="Center"
               VerticalOptions="CenterAndExpand"
               Scale="100" />
    </StackLayout>
</ContentPage>

結果として得られるBoxViewの画面に対するサイズは、プラットフォームによって異なります。

Controls using x:Static Markup Extension

その他の標準マークアップ拡張

いくつかのマークアップ拡張はXAMLに固有であり、Xamarin.Forms XAMLファイルでサポートされています。これらのうちのいくつかは頻繁には使用されませんが、必要なときには不可欠です。

  • デフォルトでプロパティがnull以外の値を持っているが、nullに設定する場合は、{x:Null}マークアップ拡張に設定します。
  • プロパティがType型の場合は、マークアップ拡張子{x:Type someClass}を使用してTyepオブジェクトに割り当てることができます。
  • x:Arrayマークアップ拡張を使用してXAMLで配列を定義できます。このマークアップ拡張には、配列内の要素の型を示すTypeという必須属性があります。
  • Bindingマークアップ拡張については、パート4 データバインディングの基礎で説明します。

ConstraintExpressionマークアップ拡張

マークアップ拡張はプロパティを持つことができますが、XML属性のようには設定されません。マークアップ拡張では、プロパティ設定がコンマで区切られ、中括弧内に引用符は表示されません。

これは、RelativeLayoutクラスで使用されるConstraintExpressionという名前のXamarin.Formsマークアップ拡張で説明できます。子ビューの位置またはサイズを定数として指定することも、親ビューや他の名前付きビューを基準に指定することもできます。ConstraintExpressionの構文を使用すると、別のビューのプロパティを倍にするFactorConstantを加えたものを使用して、ビューの位置またはサイズを設定できます。それより複雑なものはコードが必要です。

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="XamlSamples.RelativeLayoutPage"
             Title="RelativeLayout Page">

    <RelativeLayout>

        <!-- Upper left -->
        <BoxView Color="Red"
                 RelativeLayout.XConstraint=
                     "{ConstraintExpression Type=Constant,
                                            Constant=0}"
                 RelativeLayout.YConstraint=
                     "{ConstraintExpression Type=Constant,
                                            Constant=0}" />
        <!-- Upper right -->
        <BoxView Color="Green"
                 RelativeLayout.XConstraint=
                     "{ConstraintExpression Type=RelativeToParent,
                                            Property=Width,
                                            Factor=1,
                                            Constant=-40}"
                 RelativeLayout.YConstraint=
                     "{ConstraintExpression Type=Constant,
                                            Constant=0}" />
        <!-- Lower left -->
        <BoxView Color="Blue"
                 RelativeLayout.XConstraint=
                     "{ConstraintExpression Type=Constant,
                                            Constant=0}"
                 RelativeLayout.YConstraint=
                     "{ConstraintExpression Type=RelativeToParent,
                                            Property=Height,
                                            Factor=1,
                                            Constant=-40}" />
        <!-- Lower right -->
        <BoxView Color="Yellow"
                 RelativeLayout.XConstraint=
                     "{ConstraintExpression Type=RelativeToParent,
                                            Property=Width,
                                            Factor=1,
                                            Constant=-40}"
                 RelativeLayout.YConstraint=
                     "{ConstraintExpression Type=RelativeToParent,
                                            Property=Height,
                                            Factor=1,
                                            Constant=-40}" />

        <!-- Centered and 1/3 width and height of parent -->
        <BoxView x:Name="oneThird"
                 Color="Red"
                 RelativeLayout.XConstraint=
                     "{ConstraintExpression Type=RelativeToParent,
                                            Property=Width,
                                            Factor=0.33}"
                 RelativeLayout.YConstraint=
                     "{ConstraintExpression Type=RelativeToParent,
                                            Property=Height,
                                            Factor=0.33}"
                 RelativeLayout.WidthConstraint=
                     "{ConstraintExpression Type=RelativeToParent,
                                            Property=Width,
                                            Factor=0.33}"
                 RelativeLayout.HeightConstraint=
                     "{ConstraintExpression Type=RelativeToParent,
                                            Property=Height,
                                            Factor=0.33}"  />

        <!-- 1/3 width and height of previous -->
        <BoxView Color="Blue"
                 RelativeLayout.XConstraint=
                     "{ConstraintExpression Type=RelativeToView,
                                            ElementName=oneThird,
                                            Property=X}"
                 RelativeLayout.YConstraint=
                     "{ConstraintExpression Type=RelativeToView,
                                            ElementName=oneThird,
                                            Property=Y}"
                 RelativeLayout.WidthConstraint=
                     "{ConstraintExpression Type=RelativeToView,
                                            ElementName=oneThird,
                                            Property=Width,
                                            Factor=0.33}"
                 RelativeLayout.HeightConstraint=
                     "{ConstraintExpression Type=RelativeToView,
                                            ElementName=oneThird,
                                            Property=Height,
                                            Factor=0.33}"  />
    </RelativeLayout>
</ContentPage>

おそらくこのサンプルから取るべき最も重要な教訓は、マークアップ拡張の構文です。マークアップ拡張の中括弧内に引用符は使用できません。XAMLファイルにマークアップ拡張を入力するときは、プロパティの値を引用符で囲みます。誘惑に負けないでください!

実行すると次のようになります。

Relative Layout using Constraints

まとめ

ここに示すXAMLマークアップ拡張は、XAMLファイルの重要なサポートを提供します。しかしおそらく最も価値のあるXAMLマークアップの拡張はBindingです。これについては、このシリーズの次のパートで説明します。

パート4 データバインディングの基礎

XAMLファイルのプロパティのリンク

データバインディングを使用すると、2つのオブジェクトのプロパティがリンクされ、一方を変更すると他方が変更されます。これは非常に価値あるツールです。データバインディングをコードで完全に定義できますが、XAMLはショートカットと利便性を提供します。したがって、Xamarin.Formsの最も重要なマークアップ拡張の1つはBindingです。

データバインディング

データバインディングは、ソースターゲットと呼ばれる2つのオブジェクトのプロパティを接続します。コードでは、2つのステップが必要です。ターゲットオブジェクトのBindingContextプロパティは、ソースオブジェクトに設定する必要があります。ターゲットオブジェクトのプロパティをソースオブジェクトのプロパティにバインドするには、ターゲットオブジェクトに対してSetBindingメソッド(Bindingクラスと組み合わせて使用​​されることが多い)を呼び出す必要があります。

ターゲットプロパティはバインダブルプロパティでなければなりません。つまり、ターゲットオブジェクトはBindableObjectから派生する必要があります。オンラインのXamarin.Formsドキュメントでは、どのプロパティがバインダブルプロパティであるかを説明しています。TextなどのLabelのプロパティは、バインダブルプロパティTextPropertyに関連付けられています。

マークアップでは、コードに必要な同じ2つの手順も実行する必要があります。ただし、Bindingマークアップ拡張がSetBinding呼び出しとBindingクラスの代わりに使用される点が異なります。

しかし、XAMLでデータバインディングを定義する場合、ターゲットオブジェクトのBindingContextを設定する方法は複数あります。場合によっては、StaticResourceまたはx:Staticマークアップ拡張を使用するコードビハインドファイルから設定されることもあります。また、BindingContextのプロパティ要素タグの内容として設定されることもあります。

バインディングは、プログラムのビジュアルを基になるデータモデルと結びつけるために最も頻繁に使用されます。特に、パート5 データバインディングからMVVMへで議論するように、MVVM(Model-View-ViewModel)アプリケーション・アーキテクチャーを実現する際に通常は使用されますが、他のシナリオでも使用できます。

ビューからビューへのバインディング

同じページ上の2つのビューのプロパティをリンクするデータバインディングを定義できます。この場合、x:Referenceマークアップ拡張を使用してターゲットオブジェクトのBindingContextを設定します。

次の例では、Sliderと2つのLabelビューが含まれているXAMLファイルがあり、そのうちの1つはSlider値で回転し、もう1つはSlider値を表示します。

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="XamlSamples.SliderBindingsPage"
             Title="Slider Bindings Page">

    <StackLayout>
        <Label Text="ROTATION"
               BindingContext="{x:Reference Name=slider}"
               Rotation="{Binding Path=Value}"
               FontAttributes="Bold"
               FontSize="Large"
               HorizontalOptions="Center"
               VerticalOptions="CenterAndExpand" />

        <Slider x:Name="slider"
                Maximum="360"
                VerticalOptions="CenterAndExpand" />

        <Label BindingContext="{x:Reference slider}"
               Text="{Binding Value, StringFormat='The angle is {0:F0} degrees'}"
               FontAttributes="Bold"
               FontSize="Large"
               HorizontalOptions="Center"
               VerticalOptions="CenterAndExpand" />
    </StackLayout>
</ContentPage>

Sliderには、x:Referenceマークアップ拡張を使用して2つのLabelビューによって参照されるx:Name属性が含まれています。

x:Referenceバインディング拡張では、Nameという名前のプロパティを定義して、参照される要素(この場合はslider)の名前を設定します。しかし、x:Referenceマークアップ拡張を定義するReferenceExtensionクラスでは、明示的に必須ではないことを意味するNameContentProperty属性も定義されています。最初のx:Referenceには "Name ="が含まれていますが、2番目にはありません。

BindingContext="{x:Reference Name=slider}"

BindingContext="{x:Reference slider}"

Bindingマークアップ拡張機能自体には、BindingBaseクラスやBindingクラスと同様に、いくつかのプロパティがあります。BindingContentPropertyPathですが、パスがBindingマークアップ拡張の最初の項目である場合は、マークアップ拡張の "Path ="部分を省略できます。最初の例には "Path ="がありますが、2番目の例では省略しています。

Rotation="{Binding Path=Value}"

Text="{Binding Value, StringFormat='The angle is {0:F0} degrees'}"

プロパティは、すべて1行にすることも、複数行に分割することもできます。

Text="{Binding Value, 
               StringFormat='The angle is {0:F0} degrees'}"

都合のいいように書いてください。

2番目のBindingマークアップ拡張のStringFormatプロパティに注目してください。Xamarin.Formsでは、バインディングは暗黙の型変換を実行しません。文字列以外のオブジェクトを文字列として表示する必要がある場合は、型変換を提供するか、StringFormatを使用する必要があります。背後では、静的なString.Formatメソッドを使用してStringFormatを実装します。これは潜在的に問題です。.NETの書式指定には中括弧が含まれていますが、これはマークアップ拡張の区切りにも使用されるためです。これにより、XAMLパーサーを混乱させる危険があります。回避するには、書式設定文字列全体をシングルクオートで囲みます。

Text="{Binding Value, StringFormat='The angle is {0:F0} degrees'}"

実行中のプログラムは次のとおりです。

View-to-View Bindings

バインディングモード

1つビューは、複数のプロパティにデータバインディングを設定することができます。しかし、各ビューにはBindingContextが1つしかないため、そのビューの複数のデータバインディングはすべて同じオブジェクトのすべてのプロパティを参照する必要があります。

この問題やその他の問題を解決するために、BindingMode列挙体のメンバに設定されているModeプロパティがあります。

  • Default
  • OneWay — 値はソースからターゲットに転送されます
  • OneWayToSource — 値はターゲットからソースに転送されます
  • TwoWay — 値はソースとターゲットの間で双方向に転送されます

次のプログラムは、OneWayToSourceおよびTwoWayバインディングモードの一般的な使用方法の1つを示しています。4つのSliderビューは、ラベルのScaleRotateRotateXRotateYプロパティを制御するためのものです。最初は、Labelのこれら4つのプロパティがデータバインディングターゲットである必要があるように見えます。これは、それぞれがSliderによって設定されているためです。しかし、LabelBindingContextは1つのオブジェクトにすぎず、4つの異なるスライダがあります。

そのため、すべてのバインディングは一見逆方向に設定されています。4つのスライダーのそれぞれのBindingContextLabelに設定され、バインディングはスライダーのValueプロパティに設定されます。OneWayToSourceモードとTwoWayモードを使用することで、これらのValueプロパティで、LabelScaleRotateRotateXRotateYプロパティであるソースプロパティを設定できます。

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="XamlSamples.SliderTransformsPage"
             Padding="5"
             Title="Slider Transforms Page">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="*" />
            <RowDefinition Height="Auto" />
            <RowDefinition Height="Auto" />
            <RowDefinition Height="Auto" />
            <RowDefinition Height="Auto" />
        </Grid.RowDefinitions>

        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="*" />
            <ColumnDefinition Width="Auto" />
        </Grid.ColumnDefinitions>

        <!-- Scaled and rotated Label -->
        <Label x:Name="label"
               Text="TEXT"
               HorizontalOptions="Center"
               VerticalOptions="CenterAndExpand" />

        <!-- Slider and identifying Label for Scale -->
        <Slider x:Name="scaleSlider"
                BindingContext="{x:Reference label}"
                Grid.Row="1" Grid.Column="0"
                Maximum="10"
                Value="{Binding Scale, Mode=TwoWay}" />

        <Label BindingContext="{x:Reference scaleSlider}"
               Text="{Binding Value, StringFormat='Scale = {0:F1}'}"
               Grid.Row="1" Grid.Column="1"
               VerticalTextAlignment="Center" />

        <!-- Slider and identifying Label for Rotation -->
        <Slider x:Name="rotationSlider"
                BindingContext="{x:Reference label}"
                Grid.Row="2" Grid.Column="0"
                Maximum="360"
                Value="{Binding Rotation, Mode=OneWayToSource}" />

        <Label BindingContext="{x:Reference rotationSlider}"
               Text="{Binding Value, StringFormat='Rotation = {0:F0}'}"
               Grid.Row="2" Grid.Column="1"
               VerticalTextAlignment="Center" />

        <!-- Slider and identifying Label for RotationX -->
        <Slider x:Name="rotationXSlider"
                BindingContext="{x:Reference label}"
                Grid.Row="3" Grid.Column="0"
                Maximum="360"
                Value="{Binding RotationX, Mode=OneWayToSource}" />

        <Label BindingContext="{x:Reference rotationXSlider}"
               Text="{Binding Value, StringFormat='RotationX = {0:F0}'}"
               Grid.Row="3" Grid.Column="1"
               VerticalTextAlignment="Center" />

        <!-- Slider and identifying Label for RotationY -->
        <Slider x:Name="rotationYSlider"
                BindingContext="{x:Reference label}"
                Grid.Row="4" Grid.Column="0"
                Maximum="360"
                Value="{Binding RotationY, Mode=OneWayToSource}" />

        <Label BindingContext="{x:Reference rotationYSlider}"
               Text="{Binding Value, StringFormat='RotationY = {0:F0}'}"
               Grid.Row="4" Grid.Column="1"
               VerticalTextAlignment="Center" />
    </Grid>
</ContentPage>

3つのSliderビューのバインディングはOneWayToSourceです。つまり、Sliderの値によって、そのBindingContextのプロパティが変更されます。それはlabelという名前のLabelです。これら3つのSliderビューは、LabelRotateRotateXRotateYプロパティを変更します。

しかし、ScaleプロパティのバインディングはTwoWayです。これは、Scaleプロパティのデフォルト値が1で、TwoWayバインディングを使用するとSliderの初期値が0ではなく1に設定されるためです。そのバインディングがもしOneWayToSourceの場合、ScaleプロパティはSliderのデフォルト値から最初は0に設定されます。ラベルは表示されず、ユーザーに混乱を招く可能性があります。

Backwards Bindings

バインディングとコレクション

テンプレート化されたListViewよりもXAMLとデータバインディングの機能が優れていることはまだ何も示されていません。

ListViewIEnumerable型のItemsSourceプロパティを定義し、そのコレクション内のアイテムを表示します。これらのアイテムは、どのタイプのオブジェクトでもかまいません。デフォルトでは、ListViewは各項目のToStringメソッドを使用してその項目を表示します。場合によってはこれが必要な場合もありますが、多くの場合、ToStringはオブジェクトの完全修飾クラス名のみを返します。

しかし、ListViewコレクションのアイテムは、Cellから派生したクラスを含むtemplateを使用して任意の方法で表示できます。テンプレートはListViewのすべてのアイテムに対して複製され、テンプレートに設定されているデータバインディングは個々の複製に転送されます。

非常に多くの場合、ViewCellクラスを使用してこれらの項目のカスタムセルを作成する必要があります。このプロセスはコードで行うとやや乱雑になりますが、XAMLでは非常に簡単に実現できます。

XamlSamplesプロジェクトにはNamedColorというクラスが含まれています。各NamedColorオブジェクトは、文字列型のNameプロパティとFriendlyNameプロパティ、およびColor型のColorプロパティを持ちます。さらに、NamedColorには、Xamarin.FormsのColorクラスで定義された色に対応する、Color型の静的読み取り専用フィールドが141あります。静的コンストラクタは、これらの静的フィールドに対応するNamedColorオブジェクトを含むIEnumerableコレクションを作成し、そのpublic static Allプロパティに割り当てます。

静的なNamedColor.AllプロパティをListViewItemsSourceに設定するには、x:Staticマークアップ拡張を使用するのが簡単です。

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:local="clr-namespace:XamlSamples;assembly=XamlSamples"
             x:Class="XamlSamples.ListViewDemoPage"
             Title="ListView Demo Page">

    <ListView ItemsSource=