実践! Cocoa Binding
はじめに
Cocoa Bindingとは、MacOS独自の変更通知機構を使ったViewとModel/Controller層の同期機能です。Cocoa Binding使うことにより、多くの「Glue code(グルーコード)」を書くことなく、ModelとViewの値を同期させる方法を提供します。
ただし、バインドする際、プロパティの型は基底にNSObjectを持たなければいけない制約があります。
そんな強力なCocoa Bindingの使用方法について記載します。
※本稿の開発環境は下記の通りです。
| 項目 | 値 |
|---|---|
| macOSX | Sierra 10.12.6 |
| Xcode | 9.2 (9C40b) |
| Visual Studio for Mac | 7.3.2(build 12) |
| Xamarin.Mac | 4.0.0.214 |
| Development Target | 10.12 |
NSTextFieldとのバインド
ViewController内のプロパティとバインド
ViewControllerにNameプロパティを作成し、これにTextFieldとバインディングしてみます。
まずはMain.StoryBoardをXcodeで開き、ViewにTextField、Push Buttonを追加します。
次にボタンを押したときに実行されるメソッドを作成します。Controlキーを押しながら、Push ButtonをViewController.mにドラッグ&ドロップします。Nameに適当な名前をつけ(今回はShowMessageとする)、ConnectボタンをクリックするとActionが作成されます。
作成後、ViewController.csにNameプロパティ、ボタンを押した際の処理を記載します。
public partial class ViewController : NSViewController
{
public ViewController(IntPtr handle) : base(handle)
{
}
public override void ViewDidLoad()
{
base.ViewDidLoad();
}
partial void ShowMessage(NSObject sender)
{
using(var alert = new NSAlert())
{
alert.MessageText = $"{Name}さん";
alert.InformativeText = $"こんにちは。{Name}さん";
}
}
private NSString name;
[Outlet]
public NSString Name
{
get => name;
set
{
WillChangeValue(nameof(Name));
name = value;
DidChangeValue(nameof(Name));
}
}
}
再び、Main.StoryBoardを開き、左上のインスペクタをバインディングインスペクタに切り替えます。Valueを展開し、Bind toにチェックを入れます。バインド先をViewControllerに変更し、Model Key PathでNameを指定します。
Xcodeを終了し、アプリケーションを実行します。
NSTextFieldのデフォルトでは、TextFieldからフォーカスが外れた時に変更通知が送られるため、TabやReturnキーが押されないと同期しません。
文字列が変更されるたびに同期させたい場合はバインディングインスペクタで Continuously Updates Valueにチェックを入れて実行します。
自作クラスとのバインド
次に自作クラスのオブジェクトをバインディングしてみます。Personというクラスを作成し、NSObjectを継承させ、Nameプロパティを追加します。
[Register(nameof(Person))]
public class Person : NSObject
{
private NSString name;
[Outlet]
public NSString Name
{
get => name;
set
{
WillChangeValue(nameof(Name));
name = value;
DidChangeValue(nameof(Name));
}
}
}
ViewContoller内に上記のクラスのプロパティを追加します。
public partial class ViewController : NSViewController
{
public ViewController(IntPtr handle) : base(handle)
{
Person = new Person();
}
public override void ViewDidLoad()
{
base.ViewDidLoad();
}
partial void ShowMessage(NSObject sender)
{
using(var alert = new NSAlert())
{
alert.MessageText = $"{Person.Name}さん";
alert.InformativeText = $"こんにちは。{Person.Name}さん";
alert.RunSheetModal(View.Window);
}
}
private Person person;
[Outlet]
public Person Person
{
get => person;
set
{
WillChangeValue(nameof(Person));
person = value;
DidChangeValue(nameof(Person));
}
}
}
Main.StoryBoardを開き、バインディングインスペクタに切り替えます。Valueを展開し、Model Key PathをPerson.Nameを指定します。
Xcodeを終了し、アプリケーションを実行します。
Bottunの制御
次は、TextFieldに入力されていない場合、Buttonを押せないようにします。
PersonクラスにHasNameというプロパティを追加します。
[Register(nameof(Person))]
public class Person : NSObject
{
private NSString name;
[Outlet]
public NSString Name
{
get => name;
set
{
WillChangeValue(nameof(Name));
WillChangeValue(nameof(HasName));
name = value;
DidChangeValue(nameof(Name));
DidChangeValue(nameof(HasName));
}
}
[Outlet]
public bool HasName => !String.IsNullOrEmpty(Name) && Name.Length != 0;
}
Main.StoryBoardを開き、Viewに配置したButtonを選択しバインディングインスペクタに切り替えます。Availabilityを展開し、Model Key PathでPerson.HasNameを指定します。
Xcodeを終了し、アプリケーションを実行します。
NSComboBoxとのバインド
ViewController内のプロパティとバインド
ViewControllerにLanguagesプロパティを作成し、これにComboBoxとバインディングしてみます。
まずはMain.StoryBoardをXcodeで開き、ViewにComboBox、 Push Buttonを追加します。
上記と同様に次にボタンを押したときに実行されるメソッドを作成します。
作成後、ViewController.csにLanguagesプロパティ、選択された値を保持すSelectedLanguage、ボタンを押した際の処理を記載します。
public ViewController(IntPtr handle) : base(handle)
{
Languages = new NSString[]
{
(NSString)"Bash",
(NSString)"C#",
(NSString)"C++",
(NSString)"Java",
(NSString)"JavaScript",
(NSString)"Swift",
(NSString)"Objective-C",
(NSString)"Python"
};
selectedLanguage = Languages.FirstOrDefault();
}
partial void ShowMessage(NSObject sender)
{
using (var alert = new NSAlert())
{
alert.MessageText = "あなたの好きな言語は...";
alert.InformativeText = $"{SelectedLanguage}です。";
alert.RunSheetModal(View.Window);
}
}
[Outlet]
public NSString[] Languages { get; }
private NSString selectedLanguage;
[Outlet]
public NSString SelectedLanguage
{
get => selectedLanguage;
set
{
WillChangeValue(nameof(SelectedLanguage));
selectedLanguage = value;
DidChangeValue(nameof(SelectedLanguage));
}
}
Main.StoryBoardを開き、バインディングインスペクタに切り替え、Content Valueを展開し、Bind toにチェックを入れます。バインド先をViewControllerに変更し、Model Key PathでLanguagesを指定します。次にValueを展開し、Bind toにチェックを入れます。バインド先をViewControllerに変更し、Model Key PathでSelectedLanguageを指定します。
Xcodeを終了し、アプリケーションを実行します。
自作クラスとのバインド
次に自作クラスのオブジェクトをバインディングしてみます。Programmingというクラスを作成しLanguages、SelectedLanguageプロパティを追加します。TextField同様ProgrammingクラスはNSObjectを継承させます。
public class Programming : NSObject
{
public Programming()
{
Languages = new NSString[]
{
(NSString)"Bash",
(NSString)"C#",
(NSString)"C++",
(NSString)"Java",
(NSString)"JavaScript",
(NSString)"Swift",
(NSString)"Objective-C",
(NSString)"Python"
};
selectedLanguage = Languages.FirstOrDefault();
}
[Outlet]
public NSString[] Languages{ get; }
private NSString selectedLanguage;
[Outlet]
public NSString SelectedLanguage
{
get => selectedLanguage;
set
{
WillChangeValue(nameof(SelectedLanguage));
selectedLanguage = value;
DidChangeValue(nameof(SelectedLanguage));
}
}
}
TextField同様、ViewContoller内に上記のクラスのプロパティを追加します。
public ViewController(IntPtr handle) : base(handle)
{
programming = new Programming();
}
partial void ShowMessage(NSObject sender)
{
using (var alert = new NSAlert())
{
alert.MessageText = "あなたの好きな言語は...";
alert.InformativeText = $"{Programming.SelectedLanguage}です。";
alert.RunSheetModal(View.Window);
}
}
private Programming programming;
[Outlet]
public Programming Programming
{
get => programming;
set
{
WillChangeValue(nameof(Programming));
programming = value;
DidChangeValue(nameof(Programming));
}
}
上記同様、Main.StoryBoardを開き、バインディングインスペクタに切り替え、Content Valueを展開し、Bind toにチェックを入れます。バインド先をViewControllerに変更し、Model Key PathでProgramming.Languagesを指定します。次にValueを展開し、Bind toにチェックを入れます。バインド先をViewControllerに変更し、Model Key PathでProgramming.SelectedLanguageを指定します。
Xcodeを終了し、アプリケーションを実行します。
NSPopUpButtonとのバインド
ViewController内のプロパティとバインド
ViewControllerにLanguagesプロパティを作成し、これにPopUpButtonとバインディングしてみます。
まずはMain.StoryBoardをXcodeで開き、ViewにPopUpButton、 Push Buttonを追加します。
上記と同様に次にボタンを押したときに実行されるメソッドを作成します。
作成後、ViewController.csにToDoプロパティ、選択された値を保持するSelectedToDo、ボタンを押した際の処理を記載します。
public ViewController(IntPtr handle) : base(handle)
{
ToDo = new NSString[]
{
(NSString)"Xamarin.Android",
(NSString)"Xamarin.iOS",
(NSString)"Xamarin.Mac",
(NSString)"Xamarin.Forms",
(NSString)"Electron",
};
selectedToDo = ToDoList.FirstOrDefault();
}
partial void ShowMessage(NSObject sender)
{
using (var alert = new NSAlert())
{
alert.MessageText = "私が来年やることは...";
alert.InformativeText = $"{ToDoList.SelectedToDo}です。";
alert.RunSheetModal(View.Window);
}
}
[Outlet]
public NSString[] ToDo { get; }
private NSString selectedToDo;
[Outlet]
public NSString SelectedToDo
{
get => selectedLanguage;
set
{
WillChangeValue(nameof(SelectedToDo));
selectedToDo = value;
DidChangeValue(nameof(SelectedToDo));
}
}
Main.StoryBoardを開き、バインディングインスペクタに切り替え、Content Valueを展開し、Bind toにチェックを入れます。バインド先をViewControllerに変更し、Model Key PathをToDoを指定します。次にSelected Valueを展開し、Bind toにチェックを入れます。バインド先をViewControllerに変更し、Model Key PathでSelectedToDoを指定します。
Xcodeを終了し、アプリケーションを実行します。
自作クラスとのバインド
次に自作クラスのオブジェクトをバインディングしてみます。ComboBoxと同様にPlanというクラスを作成ます。その後、ViewControllerにPlanプロパティを追加します(ソースコードはComboBoxとほぼ同様のため割愛します。)
上記同様、Main.StoryBoardを開き、バインディングインスペクタに切り替え、Content Valueを展開し、Bind toにチェックを入れます。バインド先をViewControllerに変更し、Model Key PathをPlan.ToDoを指定します。次にSelected Valueを展開し、Bind toにチェックを入れます。バインド先をViewControllerに変更し、Model Key PathでPlan.SelectedToDoを指定します。
Xcodeを終了し、アプリケーションを実行します。(実行結果は上記同様のため割愛させていただきます。)
TableViewとのバインド
ViewControllerにPeopleプロパティを作成し、これにTableViewとバインディングしてみます。
まずは、 NSObjectを継承した、PersonというModelを作成します。
[Register(nameof(Person))]
public class Person : NSObject
{
public Person(NSString name, NSNumber age)
{
Name =name;
Age = age;
}
private NSString name;
[Outlet]
public NSString Name
{
get => name;
set
{
WillChangeValue(nameof(Name));
name = value;
DidChangeValue(nameof(Name));
}
}
private NSNumber age;
[Outlet]
public NSNumber Age
{
get => age;
set
{
WillChangeValue(nameof(Age));
age = value;
DidChangeValue(nameof(Age));
}
}
}
次に、ViewConrrollerにPersonオブジェクトを保持するリストのプロパティを作成します。こちらとバインドするため、NSObjectを継承した、可変配列であるNSMutableArrayを採用しました。また、PopUpButton、TextFieldとバインドするプロパティ、Input、AgeList、SelectedAgeも追加します。
public partial class ViewController : NSViewController
{
public ViewController(IntPtr handle) : base(handle)
{
people = new NSMutableArray();
AgeList = Enumerable.Range(0, 100).Select(i => (NSNumber)i).ToArray()
selectedAge = AgeList.FirstOrDefault();
}
private NSMutableArray people;
[Outlet]
public NSMutableArray People
{
get => people;
set
{
WillChangeValue(nameof(People));
people = value;
DidChangeValue(nameof(People));
}
}
private NSString input;
[Outlet]
public NSString Input
{
get => input;
set
{
WillChangeValue(nameof(Input));
input = value;
DidChangeValue(nameof(Input));
}
}
[Outlet]
public NSNumber[] AgeList { get; }
private NSNumber selectedAge;
[Outlet]
public NSNumber SelectedAge
{
get => selectedAge;
set
{
WillChangeValue(nameof(SelectedAge));
selectedAge = value;
DidChangeValue(nameof(SelectedAge));
}
}
}
次に、Main.StoryBoardをXcodeで開き、ViewにNSTableView、SceneにArrayController、おまけで PopUpButton、 Push Buttonを追加します(今回はTableViewのカラム名をName、Ageとします)。
上記同様、ボタンを押したときに実行されるメソッドを作成し(今回はAddPersonとします)、TextFiled、PopUpButtoのバインドを行います。
次に、ArrayContollerをViewControllerに配置します。ArrayControllerをViewController.hにContorolキーを押しながらドラッグ&ドロップします。Nameに適当な名前をつけ(今回はPersonArrayControllerとします)、ConnectボタンをクリックするとViewControllerに配置されます。
次に、ArrayControllerを選択した状態で、Attributesの設定項目のObject Controllerを編集します。Class Name をPersonにし、KeysにNameとAgeを追加します。
バインディングインスペクタに切り替え、Controller Contentを展開し、Bind toにチェックを入れます。バインド先をViewControllerに変更し、Model Key PathでPeopleを指定します。
次に、TableViewを選択した状態で、バインディングインスペクタに切り替え、Table Contentを展開し、Bind toにチェックを入れます。バインド先をPerson Array Controllerに変更し、Controller KeyでarrangedPbjectsと指定します。
次にNameとTable View Cellを選択した状態でバインディングインスペクタに切り替え、Valueを展開し、Bind toにチェックを入れます。バインド先をTable Cell Viewにし、Model Key PathでそれぞれarrangedPbjects.Nameと指定します。Ageも同様の作業を行います。
最後に、AddPersonの処理を書きます。
partial void AddPerson(NSObject sender)
{
var person = new Person(Input, SelectedAge);
PersonArrayController.AddObject(person);
}
アプリケーションを実行します。
まとめ
強力とまで言われている、データバインディングがCocoaフレームワークに含まれています。MacOS独自の機構ではありますが、これを用いることで、より開発工数を削減できると思われます。Cocoa Bindingを使って素敵なXamarin.Macの開発を行ってみてはいかがでしょうか。





























