[Xamarin.Mac] データバインディングでコントロールと連携してみました

2020.11.23

1 はじめに

CX事業本部の平内(SIN)です。

Xamarin.Macを使用すると、C#でネイティブなMacのアプリが作成可能です。 ここでは、私自身がXamarin.Macに入門して学習した事項を覚書として書かせて頂いています。

今回は、データバインディングを使用して、コントロールと連携してみました。

Xamarin.Macでは、規定の属性を設定することで、C#からKey-Value Observing Programmingにアクセスが可能です。

2 商品クラス

簡単なクラスを元に確認を進めます。

下記は、「名前」と「価格」だけを保持する「商品」クラスです。名前(_name)と価格(_price)をプライベートで保持し、アクセス要領をプロパティ(Name及び、Age)で公開しています。

public class Product {

    private string _name;
    private int _price;

    public Product(string name, int price) {
        _name = name;
        _price = price;
    }

    public string Name {
        get { return _name; }
        set { _name = value; }
    }

    public int Price {
        get { return _price; }
        set { _price = value; }
    }
}

3 Objective-Cにクラスを公開

続いて、商品クラスをデータバインディング可能なクラスに変更します。 作業は、以下の3つです。

  • NSObject(または、NSObjectを継承クラス)から継承
  • Registerでクラスを登録
  • Exportでプロパティを公開
  • WillChangeValue及び、DidChangeValueで通知先プロパティを指定

なお、RegisterExportで指定する名前は、あくまでObjective-Cに向けて公開される名前であって、元のクラス名やプロパティ名と一致している必要はありません。

[Register("Product")]
public class Product : NSObject {

    private string _name;
    private int _price;


    public Product(string name, int price) {
        _name = name;
        _price = price;
    }

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

    [Export("Price")]
    public int Price {
        get { return _price; }
        set {
            WillChangeValue("Price");
            _price = value;
            DidChangeValue("Price");
        }
    }
}

4 操作

(1) ValueForKey / SetValueForKey

データバインディング可能になったクラスは、ValueForKey及び、SetValueForKeyでプロパティ値の取得・設定が可能になります。

// 商品クラスを生成
var product = new Product("AAA",200);

// 名前を変更
product.SetValueForKey(new NSString("BBB"), new NSString("Name"));

// 名前及び、価格を取得
var name = product.ValueForKey(new NSString("Name"));
var price = product.ValueForKey(new NSString("Price"));
// 確認
Console.WriteLine($"name={name} price={price}");

出力

name=BBB price=200

(2) AddObserver

AddObserverで、指定したプロパティの変化を受け取ることが出来ます。

// 商品クラスを生成
var product = new Product("AAA",200);

// オブザーバを設定(Nameプロパティの値が変化した時にイベントを登録)
product.AddObserver("Name", NSKeyValueObservingOptions.New, (sender) => {
    // 変化した値を確認
    Console.WriteLine($"New Name: {product.Name}");
});

// 名前の変更
product.SetValueForKey(new NSString("BBB"), new NSString("Name"));

// 名前及び、価格を取得
var name = product.ValueForKey(new NSString("Name"));
var price = product.ValueForKey(new NSString("Price"));
// 確認
Console.WriteLine($"name={name} price={price}");

出力

New Name: BBB
New Name: BBB
name=BBB price=200

5 コントロールとの同期

簡単なUIを作成して、商品クラスとデータ連携してみました。

(1) データの公開

ViewControllerで保持されるデータ(product)を、プロパティとして定義し、[Export("Product")] で公開します。

public partial class ViewController : NSViewController {

    // データの定義(生成)
    Product product = new Product("AAA", 100);

    // プロパティとして公開
    [Export("Product")]
    public Product Product {
        get { return product; }
        set {
            WillChangeValue("Product");
            product = value;
            DidChangeValue("Product");
        }
    }

    public override void ViewDidLoad() {
        base.ViewDidLoad();
        // 変化を確認するために、オブザーバーを定義する
        product.AddObserver("Name", NSKeyValueObservingOptions.New, (sender) => {
            Console.WriteLine($"New Name: {product.Name}");
        });
    }

(2) Interface Builder から bind

ウインドウにText Fieldを追加し、バインド設定します。

Bind toにチェックを入れて、バインド先をViewControllerとし、その中の、ProductNameに紐づけています。

self.Product.Name

ここで、selfは、当該コントロールが配置されているビューコントローラーで、ここでは、ViewControllerとなります。

実行してみると、初期化されたProduct.Nameの値である「AAA」が、TextFieltの値として表示され、変更すると、Observerでトリガしたイベントにより、その内容がコンソールに出力されている事が確認できます。

6 最後に

今回は、簡単なデータバインディングの動作を確認してみました。

UIコントロールへの表示や、UIで変更された値の反映は、様々な実装が必要になりますが、バインディングを使用することで、その実装量は結構省力化されるかも知れません。 ビューとロジックとの分離のためにも、積極的に利用していきたいところです。