本文介绍如何在 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# 代码中使用源列表。





