実践! 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の開発を行ってみてはいかがでしょうか。