この記事では、Xamarin.Mac アプリケーションでのソース リストの使用について説明しています。 Xcode および Interface Builder でのソース リストの作成と管理、および C# コードでのソース リストの操作について説明します。
Xamarin.Mac アプリケーションで C# と .NET を使用している場合、Objective-C や Xcode で開発者が使用しているのと同じソース リストにアクセスできます。 Xamarin.Mac は直接 Xcode と統合できるため、Xcode の Interface Builder を使用して、ソース リストを作成および管理できます (または、必要に応じて、C# コードで直接作成することもできます)。
ソース リストは、Finder や iTunes のサイド バーなど、アクションのソースを表示するために使用される特殊な種類のアウトライン ビューです。
この記事では、Xamarin.Mac アプリケーションでのソース リストの使用の基本について説明します。 この記事で使用する主要な概念と手法については、まず Hello Mac の記事、特に「Xcode と Interface Builder の概要」および「アウトレットとアクション」のセクションを参照することを強くお勧めします。
Xamarin.Mac Internals ドキュメントの 「C# クラス/メソッドの Objective-C への公開」のセクションも参照することをお勧めします。C# クラスを Objective-C オブジェクトと UI 要素に結び付けるために使われる Register および Export コマンドについて説明されています。
ソース リストの概要
前述のように、ソース リストは、Finder や iTunes のサイド バーなど、アクションのソースを表示するために使用される特殊な種類のアウトライン ビューです。 ソース リストとは、ユーザーが階層データの行を展開または折りたたむことができるテーブルの一種です。 テーブル ビューとは異なり、ソース リスト内の項目はフラット リストに含まれていないため、ハード ドライブ上のファイルやフォルダーなどの階層に編成されます。 ソース リスト内のアイテムに他の項目が含まれている場合は、ユーザーが展開または折りたたむことができます。
ソース リストは、特別なスタイルのアウトライン ビュー (NSOutlineView) で、それ自体がテーブル ビュー (NSTableView) のサブクラスであるため、その動作の多くを親クラスから継承します。 その結果、アウトライン ビューでサポートされている多くの操作が、ソース リストでもサポートされます。 Xamarin.Mac アプリケーションは、これらの機能を制御でき、ソース リストのパラメータ (コードまたは Interface Builder) を構成して、特定の操作を許可または禁止できます。
ソース リストは、独自のデータを格納するのではなく、必要に応じて必要な行と列の両方を提供するデータ ソース (NSOutlineViewDataSource) に依存しています。
ソース リストの動作をカスタマイズするには、アウトラインの種類をサポートするアウトライン ビュー デリゲートのサブクラス (NSOutlineViewDelegate) を指定して、個々の項目の機能、項目の選択と編集、カスタム追跡、カスタム ビューを選択できます。
ソース リストは、テーブル ビューとアウトライン ビューで動作と機能の多くを共有しているため、この記事を読み進める前に、テーブル ビューとアウトライン ビューのドキュメントを参照することをお勧めします。
ソース リストの操作
ソース リストは、Finder や iTunes のサイド バーなど、アクションのソースを表示するために使用される特殊な種類のアウトライン ビューです。 アウトライン ビューとは異なり、Interface Builder でソース リストを定義する前に、Xamarin.Mac でバッキング クラスを作成しましょう。
まず、ソース リストのデータを保持する新しい SourceListItem クラスを作成しましょう。 ソリューション エクスプローラーでプロジェクトを右クリックし、[追加]>[新規ファイル...] を選択します[全般]>[空のクラス] を選択し、[名前] で「SourceListItem」と入力して、[新規] ボタンをクリックします。
SourceListItem.cs ファイルを次のようにします。
using System;
using System.Collections;
using System.Collections.Generic;
using AppKit;
using Foundation;
namespace MacOutlines
{
public class SourceListItem: NSObject, IEnumerator, IEnumerable
{
#region Private Properties
private string _title;
private NSImage _icon;
private string _tag;
private List<SourceListItem> _items = new List<SourceListItem> ();
#endregion
#region Computed Properties
public string Title {
get { return _title; }
set { _title = value; }
}
public NSImage Icon {
get { return _icon; }
set { _icon = value; }
}
public string Tag {
get { return _tag; }
set { _tag=value; }
}
#endregion
#region Indexer
public SourceListItem this[int index]
{
get
{
return _items[index];
}
set
{
_items[index] = value;
}
}
public int Count {
get { return _items.Count; }
}
public bool HasChildren {
get { return (Count > 0); }
}
#endregion
#region Enumerable Routines
private int _position = -1;
public IEnumerator GetEnumerator()
{
_position = -1;
return (IEnumerator)this;
}
public bool MoveNext()
{
_position++;
return (_position < _items.Count);
}
public void Reset()
{_position = -1;}
public object Current
{
get
{
try
{
return _items[_position];
}
catch (IndexOutOfRangeException)
{
throw new InvalidOperationException();
}
}
}
#endregion
#region Constructors
public SourceListItem ()
{
}
public SourceListItem (string title)
{
// Initialize
this._title = title;
}
public SourceListItem (string title, string icon)
{
// Initialize
this._title = title;
this._icon = NSImage.ImageNamed (icon);
}
public SourceListItem (string title, string icon, ClickedDelegate clicked)
{
// Initialize
this._title = title;
this._icon = NSImage.ImageNamed (icon);
this.Clicked = clicked;
}
public SourceListItem (string title, NSImage icon)
{
// Initialize
this._title = title;
this._icon = icon;
}
public SourceListItem (string title, NSImage icon, ClickedDelegate clicked)
{
// Initialize
this._title = title;
this._icon = icon;
this.Clicked = clicked;
}
public SourceListItem (string title, NSImage icon, string tag)
{
// Initialize
this._title = title;
this._icon = icon;
this._tag = tag;
}
public SourceListItem (string title, NSImage icon, string tag, ClickedDelegate clicked)
{
// Initialize
this._title = title;
this._icon = icon;
this._tag = tag;
this.Clicked = clicked;
}
#endregion
#region Public Methods
public void AddItem(SourceListItem item) {
_items.Add (item);
}
public void AddItem(string title) {
_items.Add (new SourceListItem (title));
}
public void AddItem(string title, string icon) {
_items.Add (new SourceListItem (title, icon));
}
public void AddItem(string title, string icon, ClickedDelegate clicked) {
_items.Add (new SourceListItem (title, icon, clicked));
}
public void AddItem(string title, NSImage icon) {
_items.Add (new SourceListItem (title, icon));
}
public void AddItem(string title, NSImage icon, ClickedDelegate clicked) {
_items.Add (new SourceListItem (title, icon, clicked));
}
public void AddItem(string title, NSImage icon, string tag) {
_items.Add (new SourceListItem (title, icon, tag));
}
public void AddItem(string title, NSImage icon, string tag, ClickedDelegate clicked) {
_items.Add (new SourceListItem (title, icon, tag, clicked));
}
public void Insert(int n, SourceListItem item) {
_items.Insert (n, item);
}
public void RemoveItem(SourceListItem item) {
_items.Remove (item);
}
public void RemoveItem(int n) {
_items.RemoveAt (n);
}
public void Clear() {
_items.Clear ();
}
#endregion
#region Events
public delegate void ClickedDelegate();
public event ClickedDelegate Clicked;
internal void RaiseClickedEvent() {
// Inform caller
if (this.Clicked != null)
this.Clicked ();
}
#endregion
}
}
ソリューション エクスプローラーでプロジェクトを右クリックし、[追加]>[新規ファイル...] を選択します[全般]>[空のクラス] を選択し、[名前] で「SourceListDataSource」と入力して、[新規] ボタンをクリックします。 SourceListDataSource.cs ファイルを次のようにします。
using System;
using System.Collections;
using System.Collections.Generic;
using AppKit;
using Foundation;
namespace MacOutlines
{
public class SourceListDataSource : NSOutlineViewDataSource
{
#region Private Variables
private SourceListView _controller;
#endregion
#region Public Variables
public List<SourceListItem> Items = new List<SourceListItem>();
#endregion
#region Constructors
public SourceListDataSource (SourceListView controller)
{
// Initialize
this._controller = controller;
}
#endregion
#region Override Properties
public override nint GetChildrenCount (NSOutlineView outlineView, Foundation.NSObject item)
{
if (item == null) {
return Items.Count;
} else {
return ((SourceListItem)item).Count;
}
}
public override bool ItemExpandable (NSOutlineView outlineView, Foundation.NSObject item)
{
return ((SourceListItem)item).HasChildren;
}
public override NSObject GetChild (NSOutlineView outlineView, nint childIndex, Foundation.NSObject item)
{
if (item == null) {
return Items [(int)childIndex];
} else {
return ((SourceListItem)item) [(int)childIndex];
}
}
public override NSObject GetObjectValue (NSOutlineView outlineView, NSTableColumn tableColumn, NSObject item)
{
return new NSString (((SourceListItem)item).Title);
}
#endregion
#region Internal Methods
internal SourceListItem ItemForRow(int row) {
int index = 0;
// Look at each group
foreach (SourceListItem item in Items) {
// Is the row inside this group?
if (row >= index && row <= (index + item.Count)) {
return item [row - index - 1];
}
// Move index
index += item.Count + 1;
}
// Not found
return null;
}
#endregion
}
}
これにより、ソース リストのデータが提供されます。
ソリューション エクスプローラーでプロジェクトを右クリックし、[追加]>[新規ファイル...] を選択します[全般]>[空のクラス] を選択し、[名前] で「SourceListDelegate」と入力して、[新規] ボタンをクリックします。 SourceListDelegate.cs ファイルを次のようにします。
using System;
using AppKit;
using Foundation;
namespace MacOutlines
{
public class SourceListDelegate : NSOutlineViewDelegate
{
#region Private variables
private SourceListView _controller;
#endregion
#region Constructors
public SourceListDelegate (SourceListView controller)
{
// Initialize
this._controller = controller;
}
#endregion
#region Override Methods
public override bool ShouldEditTableColumn (NSOutlineView outlineView, NSTableColumn tableColumn, Foundation.NSObject item)
{
return false;
}
public override NSCell GetCell (NSOutlineView outlineView, NSTableColumn tableColumn, Foundation.NSObject item)
{
nint row = outlineView.RowForItem (item);
return tableColumn.DataCellForRow (row);
}
public override bool IsGroupItem (NSOutlineView outlineView, Foundation.NSObject item)
{
return ((SourceListItem)item).HasChildren;
}
public override NSView GetView (NSOutlineView outlineView, NSTableColumn tableColumn, NSObject item)
{
NSTableCellView view = null;
// Is this a group item?
if (((SourceListItem)item).HasChildren) {
view = (NSTableCellView)outlineView.MakeView ("HeaderCell", this);
} else {
view = (NSTableCellView)outlineView.MakeView ("DataCell", this);
view.ImageView.Image = ((SourceListItem)item).Icon;
}
// Initialize view
view.TextField.StringValue = ((SourceListItem)item).Title;
// Return new view
return view;
}
public override bool ShouldSelectItem (NSOutlineView outlineView, Foundation.NSObject item)
{
return (outlineView.GetParent (item) != null);
}
public override void SelectionDidChange (NSNotification notification)
{
NSIndexSet selectedIndexes = _controller.SelectedRows;
// More than one item selected?
if (selectedIndexes.Count > 1) {
// Not handling this case
} else {
// Grab the item
var item = _controller.Data.ItemForRow ((int)selectedIndexes.FirstIndex);
// Was an item found?
if (item != null) {
// Fire the clicked event for the item
item.RaiseClickedEvent ();
// Inform caller of selection
_controller.RaiseItemSelected (item);
}
}
}
#endregion
}
}
これにより、ソース リストの動作が提供されます。
最後に、ソリューション エクスプローラーでプロジェクトを右クリックし、[追加>[新規ファイル...] を選択します[全般]>[空のクラス] を選択し、[名前] で「SourceListView」と入力して、[新規] ボタンをクリックします。 SourceListView.cs ファイルを次のようにします。
using System;
using AppKit;
using Foundation;
namespace MacOutlines
{
[Register("SourceListView")]
public class SourceListView : NSOutlineView
{
#region Computed Properties
public SourceListDataSource Data {
get {return (SourceListDataSource)this.DataSource; }
}
#endregion
#region Constructors
public SourceListView ()
{
}
public SourceListView (IntPtr handle) : base(handle)
{
}
public SourceListView (NSCoder coder) : base(coder)
{
}
public SourceListView (NSObjectFlag t) : base(t)
{
}
#endregion
#region Override Methods
public override void AwakeFromNib ()
{
base.AwakeFromNib ();
}
#endregion
#region Public Methods
public void Initialize() {
// Initialize this instance
this.DataSource = new SourceListDataSource (this);
this.Delegate = new SourceListDelegate (this);
}
public void AddItem(SourceListItem item) {
if (Data != null) {
Data.Items.Add (item);
}
}
#endregion
#region Events
public delegate void ItemSelectedDelegate(SourceListItem item);
public event ItemSelectedDelegate ItemSelected;
internal void RaiseItemSelected(SourceListItem item) {
// Inform caller
if (this.ItemSelected != null) {
this.ItemSelected (item);
}
}
#endregion
}
}
これにより、NSOutlineView (SourceListView) の再利用可能なカスタム サブクラスが作成されます。これを使用して、作成した任意の Xamarin.Mac アプリケーションでソース リストを駆動できます。
Xcode でのソース リストの作成と管理
次に、Interface Builder でソース リストを設計しましょう。 Interface Builder で編集するために Main.storyboard ファイルをダブルクリックで開き、[ライブラリ インスペクター] から分割ビューをドラッグし、ビュー コントローラーに追加して、制約エディターのビューでサイズを変更するように設定します。
次に、[ライブラリインスペクター] からソース リストをドラッグし、分割ビューの左側に追加し、制約エディターのビューでサイズを変更するように設定します。
次に、ID ビューに切り替え、ソース リストを選択して、そのクラスを SourceListView に変更します。
最後に、ViewController.h ファイルに SourceList というソース リストのアウトレットを作成します。
変更内容を保存し、Visual Studio for Mac に戻って Xcode と同期します。
ソース リストの設定
Visual Studio for Mac で RotationWindow.cs ファイルを編集し、その AwakeFromNib メソッドを次のようにしてみましょう。
public override void AwakeFromNib ()
{
base.AwakeFromNib ();
// Populate source list
SourceList.Initialize ();
var library = new SourceListItem ("Library");
library.AddItem ("Venues", "house.png", () => {
Console.WriteLine("Venue Selected");
});
library.AddItem ("Singers", "group.png");
library.AddItem ("Genre", "cards.png");
library.AddItem ("Publishers", "box.png");
library.AddItem ("Artist", "person.png");
library.AddItem ("Music", "album.png");
SourceList.AddItem (library);
// Add Rotation
var rotation = new SourceListItem ("Rotation");
rotation.AddItem ("View Rotation", "redo.png");
SourceList.AddItem (rotation);
// Add Kiosks
var kiosks = new SourceListItem ("Kiosks");
kiosks.AddItem ("Sign-in Station 1", "imac");
kiosks.AddItem ("Sign-in Station 2", "ipad");
SourceList.AddItem (kiosks);
// Display side list
SourceList.ReloadData ();
SourceList.ExpandItem (null, true);
}
Initialize () メソッドは、ソース リストの Outlet before に対して呼び出す必要があります。 項目のグループごとに、親項目を作成し、そのグループ項目にサブ項目を追加します。 その後、各グループがソース リストのコレクション SourceList.AddItem (...) に追加されます。 最後の 2 行はソース リストのデータを読み込み、すべてのグループを展開します。
// Display side list
SourceList.ReloadData ();
SourceList.ExpandItem (null, true);
最後に、AppDelegate.cs ファイルを編集し、DidFinishLaunching メソッドを次のようにします。
public override void DidFinishLaunching (NSNotification notification)
{
mainWindowController = new MainWindowController ();
mainWindowController.Window.MakeKeyAndOrderFront (this);
var rotation = new RotationWindowController ();
rotation.Window.MakeKeyAndOrderFront (this);
}
アプリケーションを実行すると、以下のように表示されます。
まとめ
この記事では、Xamarin.Mac アプリケーションでのソース リストの使用について詳しく説明しました。 Xcode の Interface Builder でソース リストを作成および管理する方法と、C# コードでソース リストを操作する方法について説明しました。





