次の方法で共有


Xamarin.Mac でのデータ バインディングとキー値コーディング

この記事は、Xcode の Interface Builder の UI 要素に対するデータ バインディングを可能にするキー値コーディングとキー値監視の使用について説明しています。

概要

Xamarin.Mac アプリケーションで C# と .NET を使用する場合、開発者が Objective-CXcode で行うのと同じキー値コーディングとデータ バインディングの手法を利用できます。 Xamarin.Mac は Xcode と直接統合されるため、コードを記述する代わりに、Xcode の Interface Builder を使用して UI 要素を使用してデータ バインドを行うことができます。

Xamarin.Mac アプリケーションでキー値コーディングとデータ バインディングの手法を使用することで、UI 要素を設定して操作するために記述および維持する必要があるコードの量を大幅に減らすことができます。 また、バッキング データ ("データ モデル") をフロントエンドのユーザー インターフェイス (Model-View-Controller) からさらに切り離すことで、より管理しやすく柔軟性が高いアプリケーション設計を実現できます。

実行中のアプリの例

この記事では、Xamarin.Mac アプリケーションでのキー値コーディングとデータ バインディングの操作の基本について説明します。 この記事で使用する主要な概念と手法については、まず Hello Mac の記事、特に「Xcode と Interface Builder の概要」および「アウトレットとアクション」のセクションを参照することを強くお勧めします。

Xamarin.Mac Internals ドキュメントの C# クラス/メソッドの Objective-C への公開に関するセクションも参照することをお勧めします。C# クラスを Objective-C オブジェクトと UI 要素に結び付けるために使われる Register および Export 属性について説明されています。

キー値コーディングとは

キー値コーディング (KVC) は、インスタンス変数またはアクセサー メソッド (get/set) を介してプロパティにアクセスするのではなく、キー (特別に書式設定された文字列) を使用してプロパティを識別して、オブジェクトのプロパティに間接的にアクセスするためのメカニズムです。 Xamarin.Mac アプリケーションでキー値コーディングに準拠したアクセサーを実装することで、キー値監視 (KVO)、データ バインディング、Core Data、Cocoa バインディング、スクリプト機能など、他の macOS (旧称 OS X) 機能にアクセスできます。

Xamarin.Mac アプリケーションでキー値コーディングとデータ バインディングの手法を使用することで、UI 要素を設定して操作するために記述および維持する必要があるコードの量を大幅に減らすことができます。 また、バッキング データ (データ モデル) をフロント エンド ユーザー インターフェイス (Model-View-Controller) からさらに分離することで、保守が容易になり、より柔軟なアプリケーション設計が可能になるという利点もあります。

たとえば、KVC 準拠オブジェクトの次のクラス定義を見てみましょう。

using System;
using Foundation;

namespace MacDatabinding
{
    [Register("PersonModel")]
    public class PersonModel : NSObject
    {
        private string _name = "";

        [Export("Name")]
        public string Name {
            get { return _name; }
            set {
                WillChangeValue ("Name");
                _name = value;
                DidChangeValue ("Name");
            }
        }

        public PersonModel ()
        {
        }
    }
}

まず、[Register("PersonModel")] 属性がクラスを登録し、それを Objective-C に公開します。 次に、クラスは NSObject (または NSObject から継承するサブクラス) を継承する必要があります。これにより、クラスが KVC に準拠できるようにするいくつかの基本メソッドが追加されます。 次に、[Export("Name")] 属性が Name プロパティを公開し、後で KVC と KVO の手法を使用してプロパティにアクセスするために使用されるキー値を定義します。

最後に、プロパティの値に対するキー値の変更を監視できるようにするために、アクセサーはその値への変更を WillChangeValue および DidChangeValue メソッド呼び出しでラップする必要があります (Export 属性と同じキーを指定します)。 次に例を示します。

set {
    WillChangeValue ("Name");
    _name = value;
    DidChangeValue ("Name");
}

この手順は、Xcode の Interface Builder のデータ バインディングに非常に重要です (この記事の後半で説明します)。

詳細については、Apple の「キー値コーディング プログラミング ガイド」を参照してください。

キーとキー パス

キーとは、オブジェクトの特定のプロパティを識別する文字列です。 通常、キーは、キー値コーディングに準拠したオブジェクト内のアクセサー メソッドの名前に対応します。 キーは ASCII エンコードを使用する必要があります。通常は小文字で始まり、空白を含めてはなりません。 したがって、上記の例では、NamePersonModel クラスの Name プロパティのキー値となります。 公開するキーとプロパティの名前は同じである必要はありませんが、ほとんどの場合は同じです。

キー パスは、走査するオブジェクト プロパティの階層を指定するために使用されるドット区切りのキーの文字列です。 シーケンス内の最初のキーのプロパティは受信側に対して相対的であり、後続の各キーは前のプロパティの値を基準にして評価されます。 同様に、ドット表記を使用して、C# クラス内のオブジェクトとそのプロパティを走査します。

たとえば、PersonModel クラスを展開し、Child プロパティを追加した場合は、次のようになります。

using System;
using Foundation;

namespace MacDatabinding
{
    [Register("PersonModel")]
    public class PersonModel : NSObject
    {
        private string _name = "";
        private PersonModel _child = new PersonModel();

        [Export("Name")]
        public string Name {
            get { return _name; }
            set {
                WillChangeValue ("Name");
                _name = value;
                DidChangeValue ("Name");
            }
        }

        [Export("Child")]
        public PersonModel Child {
            get { return _child; }
            set {
                WillChangeValue ("Child");
                _child = value;
                DidChangeValue ("Child");
            }
        }

        public PersonModel ()
        {
        }
    }
}

子の名前へのキー パスは、self.Child.Name または単に Child.Name になります (キー値の使用方法に基づく)。

キー値コーディングを使用した値の取得

ValueForKey メソッドは、要求を受け取る KVC クラスのインスタンスを基準にして、指定されたキーの値を (NSString として) 返します。 たとえば、Person が上記で定義した PersonModel クラスのインスタンスである場合、次のようになります。

// Read value
var name = Person.ValueForKey (new NSString("Name"));

これにより、その PersonModel インスタンスの Name プロパティの値が返されます。

キー値コーディングを使用した値の設定

同様に、SetValueForKey は、要求を受信する KVC クラスのインスタンスを基準にして、指定したキーの値を (NSString として) 設定します。 ここでも、次に示すように、PersonModel クラスのインスタンスを使用します。

// Write value
Person.SetValueForKey(new NSString("Jane Doe"), new NSString("Name"));

Name プロパティの値が Jane Doe に変更されます。

値の変更の監視

キー値監視 (KVO) を使用すると、KVC 準拠クラスの特定のキーにオブザーバーをアタッチし、そのキーの値が変更されるたびに通知を受け取ることができます (KVC 手法を使用するか、C# コードで特定のプロパティに直接アクセスします)。 次に例を示します。

// Watch for the name value changing
Person.AddObserver ("Name", NSKeyValueObservingOptions.New, (sender) => {
    // Inform caller of selection change
    Console.WriteLine("New Name: {0}", Person.Name)
});

これで、PersonModel クラスの Person インスタンスの Name プロパティが変更されるたびに、新しい値がコンソールに書き出されます。

詳細については、Apple の「キー値監視プログラミングの導入ガイド」を参照してください。

データ バインディング

次のセクションでは、C# コードを使用して値を読み書きするのではなく、キー値コーディングとキー値監視準拠クラスを使用して Xcode の Interface Builder で UI 要素にデータをバインドする方法について説明します。 この方法では、データ モデルを表示に使用するビューから分離することで、Xamarin.Mac アプリケーションの柔軟性と保守が容易になります。 また、記述する必要があるコードの量が大幅に減ります。

データ モデルの定義

Interface Builder で UI 要素をデータ バインドする前に、バインディングのデータ モデルとして機能するように、Xamarin.Mac アプリケーションで KVC/KVO 準拠クラスを定義しておく必要があります。 データ モデルは、ユーザー インターフェイスに表示されるすべてのデータを提供し、アプリケーションの実行中にユーザーが UI で行ったデータに対する変更を受け取ります。

たとえば、従業員のグループを管理するアプリケーションを作成する場合は、次のクラスを使用してデータ モデルを定義できます。

using System;
using Foundation;
using AppKit;

namespace MacDatabinding
{
    [Register("PersonModel")]
    public class PersonModel : NSObject
    {
        #region Private Variables
        private string _name = "";
        private string _occupation = "";
        private bool _isManager = false;
        private NSMutableArray _people = new NSMutableArray();
        #endregion

        #region Computed Properties
        [Export("Name")]
        public string Name {
            get { return _name; }
            set {
                WillChangeValue ("Name");
                _name = value;
                DidChangeValue ("Name");
            }
        }

        [Export("Occupation")]
        public string Occupation {
            get { return _occupation; }
            set {
                WillChangeValue ("Occupation");
                _occupation = value;
                DidChangeValue ("Occupation");
            }
        }

        [Export("isManager")]
        public bool isManager {
            get { return _isManager; }
            set {
                WillChangeValue ("isManager");
                WillChangeValue ("Icon");
                _isManager = value;
                DidChangeValue ("isManager");
                DidChangeValue ("Icon");
            }
        }

        [Export("isEmployee")]
        public bool isEmployee {
            get { return (NumberOfEmployees == 0); }
        }

        [Export("Icon")]
        public NSImage Icon {
            get {
                if (isManager) {
                    return NSImage.ImageNamed ("group.png");
                } else {
                    return NSImage.ImageNamed ("user.png");
                }
            }
        }

        [Export("personModelArray")]
        public NSArray People {
            get { return _people; }
        }

        [Export("NumberOfEmployees")]
        public nint NumberOfEmployees {
            get { return (nint)_people.Count; }
        }
        #endregion

        #region Constructors
        public PersonModel ()
        {
        }

        public PersonModel (string name, string occupation)
        {
            // Initialize
            this.Name = name;
            this.Occupation = occupation;
        }

        public PersonModel (string name, string occupation, bool manager)
        {
            // Initialize
            this.Name = name;
            this.Occupation = occupation;
            this.isManager = manager;
        }
        #endregion

        #region Array Controller Methods
        [Export("addObject:")]
        public void AddPerson(PersonModel person) {
            WillChangeValue ("personModelArray");
            isManager = true;
            _people.Add (person);
            DidChangeValue ("personModelArray");
        }

        [Export("insertObject:inPersonModelArrayAtIndex:")]
        public void InsertPerson(PersonModel person, nint index) {
            WillChangeValue ("personModelArray");
            _people.Insert (person, index);
            DidChangeValue ("personModelArray");
        }

        [Export("removeObjectFromPersonModelArrayAtIndex:")]
        public void RemovePerson(nint index) {
            WillChangeValue ("personModelArray");
            _people.RemoveObject (index);
            DidChangeValue ("personModelArray");
        }

        [Export("setPersonModelArray:")]
        public void SetPeople(NSMutableArray array) {
            WillChangeValue ("personModelArray");
            _people = array;
            DidChangeValue ("personModelArray");
        }
        #endregion
    }
}

このクラスのほとんどの機能については、上記の「キー値コーディングとは」セクションで説明しました。 ただし、このクラスが配列コントローラーおよびツリー コントローラー (後でツリー ビューアウトライン ビュー、およびコレクション ビューをデータ バインドするために使用します) のデータ モデルとして機能できるようにするために行われたいくつかの特定の要素と追加について見てみましょう。。

まず、従業員がマネージャーになる可能性があるため、NSArray (特に、値を変更できるように NSMutableArray) を使用して、管理対象の従業員をアタッチできるようにしました。

private NSMutableArray _people = new NSMutableArray();
...

[Export("personModelArray")]
public NSArray People {
    get { return _people; }
}

注意点が 2 つあります。

  1. これは、テーブル ビューアウトライン ビューコレクションなどの AppKit コントロールへのデータ バインドの要件であるため、標準の C# 配列またはコレクションの代わりに NSMutableArray を使用しました。
  2. データ バインディングの目的で NSArray にキャストすることで従業員の配列を公開し、その C# 形式の名前 (People) をデータ バインディングが予期する名前、つまり {class_name}Array 形式の personModelArray に変更しました (最初の文字は小文字になっていることに注意してください)。

次に、配列コントローラーツリー コントローラーをサポートするために、いくつかの特別な名前のパブリック メソッドを追加する必要があります。

[Export("addObject:")]
public void AddPerson(PersonModel person) {
    WillChangeValue ("personModelArray");
    isManager = true;
    _people.Add (person);
    DidChangeValue ("personModelArray");
}

[Export("insertObject:inPersonModelArrayAtIndex:")]
public void InsertPerson(PersonModel person, nint index) {
    WillChangeValue ("personModelArray");
    _people.Insert (person, index);
    DidChangeValue ("personModelArray");
}

[Export("removeObjectFromPersonModelArrayAtIndex:")]
public void RemovePerson(nint index) {
    WillChangeValue ("personModelArray");
    _people.RemoveObject (index);
    DidChangeValue ("personModelArray");
}

[Export("setPersonModelArray:")]
public void SetPeople(NSMutableArray array) {
    WillChangeValue ("personModelArray");
    _people = array;
    DidChangeValue ("personModelArray");
}

これにより、コントローラーは、表示されるデータを要求および変更できます。 上記で公開されている NSArray と同様に、これらには非常に具体的な名前付け規則があります (一般的な C# の名前付け規則とは異なります)。

  • addObject: - オブジェクトを配列に追加します。
  • insertObject:in{class_name}ArrayAtIndex: - {class_name} はクラスの名前です。 このメソッドは、配列の指定されたインデックスにオブジェクトを挿入します。
  • removeObjectFrom{class_name}ArrayAtIndex: - {class_name} はクラスの名前です。 このメソッドは、配列の指定されたインデックスでオブジェクトを削除します。
  • set{class_name}Array: - {class_name} はクラスの名前です。 このメソッドでは、既存のキャリーを新しいものに置き換えることができます。

これらのメソッド内では、KVO 準拠のために、配列への変更を WillChangeValue メッセージと DidChangeValue メッセージにラップしました。

最後に、Icon プロパティは isManager プロパティの値に依存しているため、isManager プロパティへの変更は、データ バインドされた UI 要素の Icon に反映されない可能性があります (KVO 中)。

[Export("Icon")]
public NSImage Icon {
    get {
        if (isManager) {
            return NSImage.ImageNamed ("group.png");
        } else {
            return NSImage.ImageNamed ("user.png");
        }
    }
}

これを修正するには、次のコードを使用します。

[Export("isManager")]
public bool isManager {
    get { return _isManager; }
    set {
        WillChangeValue ("isManager");
        WillChangeValue ("Icon");
        _isManager = value;
        DidChangeValue ("isManager");
        DidChangeValue ("Icon");
    }
}

isManager アクセサーは、自身のキーに加えて、Icon キーの WillChangeValue メッセージと DidChangeValue メッセージも送信しているため、変更も確認できることに注意してください。

この記事の残りの部分では、PersonModel データ モデルを使用します。

単純データ バインディング

データ モデルが定義された状態で、Xcode の Interface Builder でのデータ バインディングの簡単な例を見てみましょう。 たとえば、上記で定義した PersonModel の編集に使用できるフォームを Xamarin.Mac アプリケーションに追加しましょう。 モデルのプロパティを表示および編集するためのテキスト フィールドとチェック ボックスをいくつか追加します。

まず、新しいビュー コントローラーを Interface Builder の Main.storyboard ファイルに追加し、そのクラスに SimpleViewController という名前を付けます。

SimpleViewController という名前のクラスを使用して新しいビュー コントローラーを追加。

次に、Visual Studio for Mac に戻り、(プロジェクトに自動的に追加された) SimpleViewController.cs ファイルを編集し、フォームをデータ バインドする PersonModel のインスタンスを公開します。 次のコードを追加します。

private PersonModel _person = new PersonModel();
...

[Export("Person")]
public PersonModel Person {
    get {return _person; }
    set {
        WillChangeValue ("Person");
        _person = value;
        DidChangeValue ("Person");
    }
}

次に、ビューが読み込まれたら、PersonModel のインスタンスを作成し、次のコードを設定します。

public override void ViewDidLoad ()
{
    base.AwakeFromNib ();

    // Set a default person
    var Craig = new PersonModel ("Craig Dunn", "Documentation Manager");
    Craig.AddPerson (new PersonModel ("Amy Burns", "Technical Writer"));
    Craig.AddPerson (new PersonModel ("Joel Martinez", "Web & Infrastructure"));
    Craig.AddPerson (new PersonModel ("Kevin Mullins", "Technical Writer"));
    Craig.AddPerson (new PersonModel ("Mark McLemore", "Technical Writer"));
    Craig.AddPerson (new PersonModel ("Tom Opgenorth", "Technical Writer"));
    Person = Craig;

}

次に、フォームを作成し、Main.storyboard ファイルをダブルクリックして開き、Interface Builder で編集する必要があります。 フォームを次のようにレイアウトします。

Xcode でのストーリーボードの編集

Person キーを介して公開した PersonModel にフォームをデータ バインドするには、次の手順を実行します。

  1. [従業員名] テキストフィールドを選択し、[バインディング インスペクター] に切り替えます。

  2. [バインド先] ボックスをチェックし、ドロップダウンから [簡易ビューコントローラー] を選択します。 次に、キー パスself.Person.Name と入力します。

    キー パスに self dot person dot name と入力します。

  3. [職業] テキストフィールドを選択し、[バインド先] ボックスをオンにして、ドロップダウンから [簡易ビューコントローラー] を選択します。 次に、キー パスself.Person.Occupation と入力します。

    キー パスに self dot Person dot Occupation と入力します。

  4. [従業員はマネージャーである] チェックボックスをオンにし、[バインド先] ボックスをオンにして、ドロップダウンから [簡易ビュー コントローラー] を選択します。 次に、キー パスself.Person.isManager と入力します。

    キー パスに self dot Person dot isManager と入力します。

  5. [管理対象従業員数] テキスト フィールドを選択し、[バインド先] ボックスをオンにして、ドロップダウンから [簡易ビュー コントローラー] を選択します。 次に、キー パスself.Person.NumberOfEmployees と入力します。

    キー パスに self dot Person dot NumberOfEmployees と入力します。

  6. 従業員がマネージャーでない場合は、[管理対象従業員数] のラベルとテキスト フィールドを非表示にします。

  7. [管理対象従業員数] ラベルを選択し、[非表示] ターンダウンを展開し、[バインド先] ボックスをオンにして、ドロップダウンから [簡易ビュー コントローラー] を選択します。 次に、キー パスself.Person.isManager と入力します。

    マネージャー以外のキー パスに self dot Person dot isManager と入力します。

  8. [値トランスフォーマー] ドロップダウンから NSNegateBoolean を選択します。

    NSNegateBoolean キー変換の選択

  9. これにより、isManager プロパティの値が false の場合、ラベルが非表示になることがデータ バインディングに通知されます。

  10. [管理対象従業員数] テキスト フィールドに対して手順 7 と 8 を繰り返します。

  11. 変更内容を保存し、Visual Studio for Mac に戻って Xcode と同期します。

アプリケーションを実行すると、Person プロパティの値がフォームに自動的に設定されます。

自動入力されたフォームの表示

ユーザーがフォームに加える変更は、ビュー コントローラーの Person プロパティに書き戻されます。 たとえば、[従業員はマネージャーである] の選択を解除すると、PersonModelPerson インスタンスが更新され、[管理対象従業員数] ラベルとテキスト フィールドが (データ バインディング経由で) 自動的に非表示になります。

マネージャー以外の従業員の数を非表示にする

テーブル ビューのデータ バインディング

データ バインディングの基本を理解したので、配列コントローラーとテーブル ビューへのデータ バインディングを使用して、より複雑なデータ バインディング タスクを見てみましょう。 テーブル ビューの操作の詳細については、「テーブル ビュー」のドキュメントを参照してください。

まず、Interface Builder の Main.storyboard ファイルに新しいビュー コントローラーを追加し、そのクラスに TableViewController という名前を付けます。

TableViewController という名前のクラスを持つ新しいビュー コントローラーを追加する。

次に、TableViewController.cs ファイル (プロジェクトに自動的に追加されたファイル) を編集し、フォームをデータ バインドする PersonModel クラスの配列 (NSArray) を公開しましょう。 次のコードを追加します。

private NSMutableArray _people = new NSMutableArray();
...

[Export("personModelArray")]
public NSArray People {
    get { return _people; }
}
...

[Export("addObject:")]
public void AddPerson(PersonModel person) {
    WillChangeValue ("personModelArray");
    _people.Add (person);
    DidChangeValue ("personModelArray");
}

[Export("insertObject:inPersonModelArrayAtIndex:")]
public void InsertPerson(PersonModel person, nint index) {
    WillChangeValue ("personModelArray");
    _people.Insert (person, index);
    DidChangeValue ("personModelArray");
}

[Export("removeObjectFromPersonModelArrayAtIndex:")]
public void RemovePerson(nint index) {
    WillChangeValue ("personModelArray");
    _people.RemoveObject (index);
    DidChangeValue ("personModelArray");
}

[Export("setPersonModelArray:")]
public void SetPeople(NSMutableArray array) {
    WillChangeValue ("personModelArray");
    _people = array;
    DidChangeValue ("personModelArray");
}

上記の「データ モデルの定義」セクションの PersonModel クラスで行ったように、配列コントローラーが PersonModels のコレクションからデータを読み書きできるように、特別に名前を付けた 4 つのパブリック メソッドを公開しました。

次に、ビューが読み込まれるときに、配列に次のコードを設定する必要があります。

public override void AwakeFromNib ()
{
    base.AwakeFromNib ();

    // Build list of employees
    AddPerson (new PersonModel ("Craig Dunn", "Documentation Manager", true));
    AddPerson (new PersonModel ("Amy Burns", "Technical Writer"));
    AddPerson (new PersonModel ("Joel Martinez", "Web & Infrastructure"));
    AddPerson (new PersonModel ("Kevin Mullins", "Technical Writer"));
    AddPerson (new PersonModel ("Mark McLemore", "Technical Writer"));
    AddPerson (new PersonModel ("Tom Opgenorth", "Technical Writer"));
    AddPerson (new PersonModel ("Larry O'Brien", "API Documentation Manager", true));
    AddPerson (new PersonModel ("Mike Norman", "API Documenter"));

}

次に、テーブル ビューを作成し、Main.storyboard ファイルをダブルクリックして開き、Interface Builder で編集する必要があります。 テーブルを次のようにレイアウトします。

新しいテーブル ビューのレイアウト

バインドされたデータをテーブルに提供するには、配列コントローラーを追加する必要があります。次の操作を行います。

  1. 配列コントローラーを [ライブラリ インスペクター] から [インターフェイス エディター] にドラッグします。

    ライブラリから配列コントローラーを選択

  2. [インターフェイス階層] で [配列コントローラー] を選択し、[属性インスペクター] に切り替えます。

    属性インスペクターの選択

  3. [クラス名] に PersonModel と入力し、[プラス] ボタンをクリックし、3 つのキーを追加します。 NameOccupationisManager という名前を指定します。

    オブジェクト コントローラーに必要なキー パスを追加。

  4. これにより、どの配列を管理しているかと、(キーを介して) どのプロパティを公開する必要があるかが配列コントローラーに通知されます。

  5. [バインディング インスペクター] に切り替え、[コンテンツ配列] で [バインド先] と [テーブル ビュー コントローラー] を選択します。 self.personModelArrayモデル キー パスを入力します。

    キー パスの入力

  6. これにより、配列コントローラーが、ビュー コントローラーで公開した PersonModels の配列に関連付けられます。

次に、テーブル ビューを配列コントローラーにバインドする必要があります。次の操作を行います。

  1. テーブル ビューと [バインディング インスペクター] を選択します。

    テーブル ビューとバインド インスペクターの選択。

  2. [テーブル コンテンツ] ターンダウンで、[バインド先] と [配列コントローラー] を選択します。 [コントローラー キー] フィールドに arrangedObjects と入力します。

    コントローラー キーの定義

  3. [従業員] 列の下にある [テーブル ビュー セル] を選択します。 [バインディング インスペクター] の [] ターンダウンで、[バインド先] と [テーブル セル ビュー] を選択します。 [モデル キー パス] に objectValue.Name と入力します。

    [従業員] 列のモデル キー パスの設定。

  4. objectValue は、配列コントローラーによって管理されている配列内の現在の PersonModel です。

  5. [職業] 列の [テーブル ビュー セル] を選択します。 [バインディング インスペクター] の [] ターンダウンで、[バインド先] と [テーブル セル ビュー] を選択します。 [モデル キー パス] に objectValue.Occupation と入力します。

    [職業] 列のモデル キー パスを設定します。

  6. 変更内容を保存し、Visual Studio for Mac に戻って Xcode と同期します。

アプリケーションを実行すると、テーブルに PersonModels の配列が設定されます。

PersonModels の配列を設定するアプリケーションを実行。

アウトライン ビューのデータ バインディング

アウトライン ビューに対するデータ バインディングは、テーブル ビューに対するバインディングとよく似ています。 主な違いは、配列コントローラーの代わりにツリー コントローラーを使用して、アウトライン ビューにバインドされたデータを提供することです。 アウトライン ビューの操作の詳細については、「アウトライン ビュー」のドキュメントを参照してください。

まず、Interface Builder の Main.storyboard ファイルに新しいビュー コントローラーを追加し、そのクラスに OutlineViewController という名前を付けます。

OutlineViewController という名前のクラスを持つ新しいビュー コントローラーを追加。

次に、OutlineViewController.cs ファイル (プロジェクトに自動的に追加されたファイル) を編集し、フォームをデータ バインドする PersonModel クラスの配列 (NSArray) を公開しましょう。 次のコードを追加します。

private NSMutableArray _people = new NSMutableArray();
...

[Export("personModelArray")]
public NSArray People {
    get { return _people; }
}
...

[Export("addObject:")]
public void AddPerson(PersonModel person) {
    WillChangeValue ("personModelArray");
    _people.Add (person);
    DidChangeValue ("personModelArray");
}

[Export("insertObject:inPersonModelArrayAtIndex:")]
public void InsertPerson(PersonModel person, nint index) {
    WillChangeValue ("personModelArray");
    _people.Insert (person, index);
    DidChangeValue ("personModelArray");
}

[Export("removeObjectFromPersonModelArrayAtIndex:")]
public void RemovePerson(nint index) {
    WillChangeValue ("personModelArray");
    _people.RemoveObject (index);
    DidChangeValue ("personModelArray");
}

[Export("setPersonModelArray:")]
public void SetPeople(NSMutableArray array) {
    WillChangeValue ("personModelArray");
    _people = array;
    DidChangeValue ("personModelArray");
}

上記の「データ モデルの定義」セクションの PersonModel クラスで行ったように、ツリー コントローラーが PersonModels のコレクションからデータを読み書きできるように、特別に名前を付けた 4 つのパブリック メソッドを公開しました。

次に、ビューが読み込まれるときに、配列に次のコードを設定する必要があります。

public override void AwakeFromNib ()
{
    base.AwakeFromNib ();

    // Build list of employees
    var Craig = new PersonModel ("Craig Dunn", "Documentation Manager");
    Craig.AddPerson (new PersonModel ("Amy Burns", "Technical Writer"));
    Craig.AddPerson (new PersonModel ("Joel Martinez", "Web & Infrastructure"));
    Craig.AddPerson (new PersonModel ("Kevin Mullins", "Technical Writer"));
    Craig.AddPerson (new PersonModel ("Mark McLemore", "Technical Writer"));
    Craig.AddPerson (new PersonModel ("Tom Opgenorth", "Technical Writer"));
    AddPerson (Craig);

    var Larry = new PersonModel ("Larry O'Brien", "API Documentation Manager");
    Larry.AddPerson (new PersonModel ("Mike Norman", "API Documenter"));
    AddPerson (Larry);

}

次に、アウトライン ビューを作成し、Main.storyboard ファイルをダブルクリックして開き、Interface Builder で編集する必要があります。 テーブルを次のようにレイアウトします。

アウトライン ビューの設定

バインドされたデータをアウトラインに提供するには、ツリー コントローラーを追加する必要があります。次の操作を行います。

  1. ツリー コントローラーを [ライブラリ インスペクター] から [インターフェイス エディター] にドラッグします。

    ライブラリからのツリー コントローラーの選択

  2. [インターフェイス階層] で [ツリー コントローラー] を選択し、[属性インスペクター] に切り替えます。

    属性インスペクターの選択

  3. [クラス名] に PersonModel と入力し、[プラス] ボタンをクリックし、3 つのキーを追加します。 NameOccupationisManager という名前を指定します。

    PersonModel に必要なキー パスを追加します。

  4. これにより、どの配列を管理しているかと、(キーを介して) どのプロパティを公開する必要があるかがツリー コントローラーに通知されます。

  5. [ツリー コントローラー] セクションで 、[] に personModelArray と入力し、[カウント] に NumberOfEmployees と入力し、[リーフ] に isEmployee と入力します。

    ツリー コントローラーのキー パスの設定

  6. これにより、子ノードを検索する場所、子ノードの数、および現在のノードに子ノードがあるかどうかがツリー コントローラーに通知されます。

  7. [バインディング インスペクター] に切り替え、[コンテンツ配列] で [バインド先] と [ファイルの所有者] を選択します。 self.personModelArrayモデル キー パスを入力します。

    キー パスの編集

  8. これにより、ツリー コントローラーが、ビュー コントローラーで公開した PersonModels の配列に関連付けられます。

次に、アウトライン ビューをツリー コントローラーにバインドする必要があります。次の操作を行います。

  1. アウトラインビューを選択し、[バインディング インスペクター] で次のように選択します。

    アウトライン ビューとバインド インスペクターを選択します。

  2. [アウトライン ビュー コンテンツ] ターンダウンで、[バインド先] と [ツリー コントローラー] を選択します。 [コントローラー キー] フィールドに arrangedObjects と入力します。

    コントローラー キーの設定

  3. [従業員] 列の下にある [テーブル ビュー セル] を選択します。 [バインディング インスペクター] の [] ターンダウンで、[バインド先] と [テーブル セル ビュー] を選択します。 [モデル キー パス] に objectValue.Name と入力します。

    モデル キー パス値 objectValue dot Name を入力します。

  4. objectValue は、ツリー コントローラーによって管理されている配列内の現在の PersonModel です。

  5. [職業] 列の [テーブル ビュー セル] を選択します。 [バインディング インスペクター] の [] ターンダウンで、[バインド先] と [テーブル セル ビュー] を選択します。 [モデル キー パス] に objectValue.Occupation と入力します。

    モデル キー パス値 objectValue dot Occupation を入力します。

  6. 変更内容を保存し、Visual Studio for Mac に戻って Xcode と同期します。

アプリケーションを実行すると、アウトラインに PersonModels の配列が設定されます。

アプリケーションを実行すると、PersonModels の配列が自動入力されます。

コレクション ビューのデータ バインディング

コレクション ビューを使用したデータ バインディングは、配列コントローラーを使用してコレクションのデータを提供するため、テーブル ビューを使用したバインディングとよく似ています。 コレクション ビューには既定の表示形式がないため、ユーザーの操作に関するフィードバックを提供し、ユーザーの選択を追跡するために、さらに多くの作業が必要です。

重要

Xcode 7 および macOS 10.11 (以降) での問題により、ストーリーボード (.storyboard) ファイル内ではコレクション ビューを使用できません。 そのため、Xamarin.Mac アプリのコレクション ビューを定義するには、引き続き .xib ファイルを使用する必要があります。 詳細については、「コレクション ビュー」のドキュメントを参照してください。

ネイティブ クラッシュのデバッグ

データ バインディングに間違いがある場合、アンマネージ コードでネイティブ クラッシュが発生し、Xamarin.Mac アプリケーションが SIGABRT エラーで完全に失敗する可能性があります。

ネイティブ クラッシュ ダイアログ ボックスの例

通常、データ バインディング中のネイティブ クラッシュの主な原因は 4 つあります。

  1. データ モデルが NSObject または NSObject のサブクラスから継承していない。
  2. [Export("key-name")] 属性を使用してプロパティを Objective-C に公開しなかった。
  3. WillChangeValue および DidChangeValue メソッド呼び出し (Export 属性と同じキーを指定) でアクセサーの値への変更をラップしなかった。
  4. Interface Builder の [バインディング インスペクター] でキーの入力が間違っている。

クラッシュのデコード

データ バインディングのネイティブ クラッシュを見つけて修正する方法を示すことができるように、ネイティブ クラッシュを引き起こしてみましょう。 Interface Builder で、コレクション ビューの例の最初のラベルのバインディングを Name から Title に変更しましょう。

バインド キーの編集

変更を保存し、Visual Studio for Mac に戻って Xcode と同期し、アプリケーションを実行しましょう。 コレクション ビューが表示されると、PersonModel がキー Title を持つプロパティを公開していないため、アプリケーションは SIGABRT エラーで一時的にクラッシュします (Visual Studio for Mac の [アプリケーション出力] に示されているように)。

バインド エラーの例

[アプリケーション出力] のエラーの一番上までスクロールすると、問題を解決するためのキーが表示されます。

エラー ログで問題を見つける

この行は、バインドするオブジェクトにキー Title が存在しないことを示しています。 Interface Builder でバインディングを Name に戻し、保存、同期、リビルド、実行すると、アプリケーションは問題なく正常に実行されます。

まとめ

この記事では、Xamarin.Mac アプリケーションでデータ バインディングとキー値コーディングを操作する方法について詳しく説明しました。 最初に、キー値コーディング (KVC) とキー値監視 (KVO) を使用して C# クラスを Objective-C に公開する方法について説明しました。 次に、KVO 準拠クラスを使用して、Xcode の Interface Builder で UI 要素にデータ バインドする方法を示しました。 最後に、配列コントローラーツリー コントローラーを使用した複雑なデータ バインディングを示しました。