この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。
1 はじめに
CX事業本部の平内(SIN)です。
Xamarin.Macを使用すると、C#でネイティブなMacのアプリが作成可能です。 ここでは、私自身がXamarin.Macに入門して学習した事項を覚書として書かせて頂いています。
今回は、アウトラインビューを確認してみました。
アウトラインビュー(NSOutlineView)は、テーブルビュー(NSTableView)のサブクラスで、殆ど同じですが、拡張としてアイテムを階層的に持つことが出来ます。そして、三角形のクリックで、それが折り畳めるようになっています。
2 OutlineViewの配置
Interface Builderで、メインとなるビューOutline Viewを配置し、ウインドウいっぱいに表示されるように制約を追加します。
また、AssistantエディタでViewController.hを開いてOutletを接続します。 ( 接続するコントロールがNSOutlineViewになっていることに注意が必要です。)
テーブルビューと同じで、各カラムのタイトルや、デフォルトの幅は、プロパティで設定できます。
3 データクラス
クラスPersonは、超簡単な個人の情報で、名前(Name)と年齢(Age)だけを保持します。
public class Person {
public string Name { get; set; } = "";
public int Age { get; set; } = 0;
public Person(string name, int age) {
this.Name = name;
this.Age = age;
}
}
4 DataSourceとDelegate
OutlineViewにデータを表示するには、DataSourceとDelegateの仕組みが必要です。
(1) DataSource
NSOutlineViewDataSourceを継承したクラス(OutlineViewDataSource)を追加し、先のデータ(Person)のリストを保持します。
また、NSOutlineViewDataSourceのGetChild/GetChildrenCount/ItemExpandableをオーバーライドします。
GetChildでは、childIndex番目の要素、GetChildrenCountは、要素のデータ数、そして、ItemExpandableは、子要素が0個以上かどうかを返します。 ただ、それぞれは、変数itemがnullの場合、自身についての情報ですが、そうでな場合、itemの要素を意味します。
このため、それぞれ、下記のようなコードで、統一して書いてみました。
var persons = (item == null) ? Persons : ((Person)item).Persons;
public class OutlineViewDataSource : NSOutlineViewDataSource {
public List<Person> Persons = new List<Person>();
public OutlineViewDataSource() {
}
public override NSObject GetChild(NSOutlineView outlineView, nint childIndex, NSObject item) {
var persons = (item == null) ? Persons : ((Person)item).Persons;
return persons[(int)childIndex];
}
public override nint GetChildrenCount(NSOutlineView outlineView, NSObject item) {
var persons = (item == null) ? Persons : ((Person)item).Persons;
return persons.Count;
}
public override bool ItemExpandable(NSOutlineView outlineView, NSObject item) {
var persons = (item == null) ? Persons : ((Person)item).Persons;
return (persons.Count > 0);
}
}
(2) Delegate
NSOutlineViewDelegateを継承したクラス(OutlineViewDelegate)を追加し、変数でDataSourceを定義しコンストラクタで初期化します。
また、NSOutlineViewDelegateのGetViewをオーバーライドし、セルの生成と値の設定を行います。
GetViewは、1つのセルを描画する度に呼ばれますが、返すデータ(何行目かのデータ)は、itemで渡され、タイトルでカラムを識別しています。
public class OutlineViewDelegate : NSOutlineViewDelegate {
private string identifier = "cell";
private OutlineViewDataSource DataSource;
public OutlineViewDelegate(OutlineViewDataSource dataSource) {
this.DataSource = dataSource;
}
public override NSView GetView(NSOutlineView outlineView, NSTableColumn tableColumn, NSObject item) {
NSTextField view = (NSTextField)outlineView.MakeView(identifier, this);
if(view == null) {
view = new NSTextField();
view.Identifier = identifier;
view.BackgroundColor = NSColor.Clear;
view.Bordered = false;
view.Selectable = false;
view.Editable = false;
}
var person = item as Person;
switch(tableColumn.Title) {
case "Name":
view.StringValue = person.Name;
break;
case "Age":
view.StringValue = person.Age.ToString();
break;
}
return view;
}
}
5 OutlineViewの初期化
ViewControllerでAwakeFromNibをオーバーライドし、DataSourceの初期化と、コントロールへの紐付けを行います。
public partial class ViewController : NSViewController {
// ・・・略・・・
public override void AwakeFromNib() {
base.AwakeFromNib();
var DataSource = new OutlineViewDataSource();
var group1 = new Person("Group1", 0);
group1.Persons.Add(new Person("Saito", 20));
group1.Persons.Add(new Person("Suzuki", 18));
group1.Persons.Add(new Person("Yamada", 14));
DataSource.Persons.Add(group1);
var group2 = new Person("Group2", 0);
group2.Persons.Add(new Person("Yamamoto", 21));
group2.Persons.Add(new Person("Kisida", 18));
DataSource.Persons.Add(group2);
var group3 = new Person("Group3", 0);
group3.Persons.Add(new Person("Tanaka", 21));
group3.Persons.Add(new Person("Sasaki", 22));
DataSource.Persons.Add(group3);
OutlineView.DataSource = DataSource;
OutlineView.Delegate = new OutlineViewDelegate(DataSource);
}
}
ここまでの作業で、表示は以下のようになりました。
グルーピング行
Nameのカラムが、階層構造になったことで、やや違和感が出でしまったので、カラムのタイトル及び、Ageの表示を少し変更してみました。
タイトルNameは、Group / Nameに変更しました。
また、カラムAgeの表示は、階層下に要素がある場合、その平均値にしてみました。
public class OutlineViewDelegate : NSOutlineViewDelegate {
// ・・・略・・・
var person = item as Person;
switch(tableColumn.Title) {
case "Group / Name":
view.StringValue = person.Name;
break;
case "Age":
if(person.Persons.Count > 0) {
var ave = person.Persons.Average(i => i.Age);
view.StringValue =$"ave : {ave}";
} else {
view.StringValue = person.Age.ToString();
}
break;
}
return view;
}
}
上記の変更で表示されるビューは、以下のようになってます。
6 最後に
今回は、アウトラインビューについて確認してみました。
2次元データの表示でありながら、階層が表現できる、ちょっと面白いビューだと思います。 なお、1カラムだけのOutlineViewを使用すれば、Windowsで言うTreeViewが、表現できるということでしょうか。