Xamarin.Mac 中的源列表
本文介绍如何在 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 内部机制文档的向 Objective-C 公开 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 中设计源列表。 双击 Main.storyboard
文件将其打开,以便在 Interface Builder 中编辑,并从“库检查器”拖动拆分视图,将其添加到视图控制器,并将其设置为在约束编辑器中使用视图调整大小:
接下来,从“库检查器”拖动源列表,将其添加到拆分视图的左侧,并将其设置为在约束编辑器中使用视图调整大小:
接下来,切换到“标识视图”,选择源列表,并将其类更改为 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 ()
在将任何项添加到源列表之前,需要针对源列表的出口调用该方法。 对于每个项组,我们将创建一个父项,然后将子项添加到该组项。 然后将每个组添加到源列表的集合 SourceList.AddItem (...)
。 最后两行加载源列表的数据并展开所有组:
// 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# 代码中使用源列表。