Xamarin.Mac 中的表视图
本文介绍如何在 Xamarin.Mac 应用程序中使用表视图。 它介绍如何在 Xcode 和 Interface Builder 中创建表视图,并在代码中与其交互。
在 Xamarin.Mac 应用程序中使用 C# 和 .NET 时,你可以访问的表视图与使用 Objective-C 和 Xcode 的开发人员访问的菜单相同。 由于 Xamarin.Mac 与 Xcode 直接集成,你可以使用 Xcode 的 Interface Builder 来创建和维护表视图(或选择直接使用 C# 代码创建)。
表视图以表格格式显示数据,其中包含多行中一列或多列信息。 根据要创建的表视图的类型,用户可以按列排序、重新组织列、添加列、删除列或编辑表中包含的数据。
本文将介绍在 Xamarin.Mac 应用程序中使用表视图的基础知识。 强烈建议先阅读 Hello, Mac 一文,特别是 Xcode 和 Interface Builder 简介和输出口和操作部分,因为其中介绍了我们将在本文中使用的关键概念和技术。
你可能还需要查看 Xamarin.Mac 内部机制文档的向 Objective-C 公开 C# 类/方法部分,因为其中介绍了用于将 C# 类连接到 Objective-C 对象和 UI 元素的 Register
和 Export
命令。
表视图简介
表视图以表格格式显示数据,其中包含多行中一列或多列信息。 表视图显示在滚动视图 (NSScrollView
) 内,从 macOS 10.7 开始,可以使用任何 NSView
而不是单元格 (NSCell
) 来显示行和列。 也就是说,你仍然可以使用 NSCell
,但通常会子类化 NSTableCellView
并创建自定义行和列。
表视图不存储它自己的数据,而是依赖于数据源 (NSTableViewDataSource
) 来根据需要提供所需的行和列。
可以通过提供表视图委托 (NSTableViewDelegate
) 的子类来支持表列管理、键入以选择功能、行选择和编辑、自定义跟踪以及单个列和行的自定义视图来自定义表视图的行为。
创建表视图时,Apple 建议以下各项:
- 允许用户通过单击列标题对表进行排序。
- 创建作为名词或短名词短语的列标题,用于描述该列中显示的数据。
有关详细信息,请参阅 Apple OS X 人机界面指南的内容视图部分。
在 Xcode 中创建和维护表视图
创建新的 Xamarin.Mac Cocoa 应用程序时,默认情况下会获得标准空白窗口。 此窗口在项目中自动包含的 .storyboard
文件中定义。 若要编辑窗口设计,请在“解决方案资源管理器”中双击 Main.storyboard
文件:
这将在 Xcode 的 Interface Builder 中打开窗口设计:
在“库检查器”的搜索框中键入 table
,以便更轻松地查找表视图控件:
将表视图拖到“界面编辑器”的视图控制器上,使其填充视图控制器的内容区域,并将其设置为在“约束编辑器”中的窗口收缩和增长的位置:
选择“接口层次结构”中的表视图,“属性检查器”中提供以下属性:
- 内容模式 - 允许使用视图 (
NSView
) 或单元格 (NSCell
) 在行和列中显示数据。 从 macOS 10.7 开始,应使用视图。 - 浮点数组行 - 如果
true
,表视图将绘制分组单元格,就像它们浮动一样。 - 列 - 定义显示的列数。
- 标头 - 如果
true
,列将具有标头。 - 重新排序 - 如果
true
,用户将能够拖动对表中的列进行重新排序。 - 调整大小 - 如果
true
,用户将能够拖动列标题以调整列的大小。 - 列大小调整 - 控制表自动调整列大小的方式。
- 突出显示 - 控制选定单元格时表格使用的突出显示类型。
- 交替行 - 如果
true
,相邻的行将具有不同的背景色。 - 水平网格 - 选择水平单元格之间绘制的边框类型。
- 垂直网格 - 选择垂直单元格之间绘制的边框类型。
- 网格颜色 - 设置单元格边框颜色。
- 背景 - 设置单元格背景色。
- 选择 - 允许你控制用户可以如何选择表中的单元格,如下所示:
- 多个 - 如果
true
,用户可以选择多个行和列。 - 列 - 如果
true
,用户可以选择列。 - 类型选择 - 如果
true
,用户可以键入字符以选择行。 - 空 - 如果
true
,用户不需要选择行或列,表格允许完全不选择。
- 多个 - 如果
- 自动保存 - 表格式的名称会自动保存。
- 列信息 - 如果
true
,将自动保存列的顺序和宽度。 - 换行符 - 选择单元格如何处理换行符。
- 截断最后一个可见行 - 如果
true
,则单元格将被截断,因为数据无法在其范围内存储。
重要
除非要维护旧版 Xamarin.Mac 应用程序,否则应使用基于 NSView
的表视图,而不是基于 NSCell
的表视图。 NSCell
被视为旧版,今后可能不受支持。
在“接口层次结构”中选择一个表列,“属性检查器”中提供了以下属性:
- 标题 - 设置列的标题。
- 对齐 - 设置单元格中文本的对齐方式。
- 标题字体 - 选择单元格标题文本的字体。
- 排序键 - 用于对列中的数据进行排序的键。 如果用户无法对此列进行排序,请留空。
- 选择器 - 用于执行排序的操作。 如果用户无法对此列进行排序,请留空。
- 顺序 - 列数据的排序顺序。
- 调整大小 - 选择列的大小调整类型。
- 可编辑 - 如果
true
,用户可以编辑基于单元格的表中的单元格。 - 隐藏 - 如果
true
,列处于隐藏状态。
还可以通过向左或向右拖动列的手柄(垂直居中)调整列的大小。
让我们选择表视图中的每一列,并为第一列提供 Product
标题 ,第二列则为 Details
。
在“接口层次结构”中选择表单元格视图 (NSTableViewCell
) ,“属性检查器”中提供以下属性:
这些是标准视图的所有属性。 还可以在此处选择调整此列的行大小。
在“接口层次结构”中选择表视图单元(默认情况下,这是 NSTextField
),“属性检查器”中提供以下属性:
你将拥有要在此处设置的标准文本字段的所有属性。 默认情况下,标准文本字段用于显示列中单元格的数据。
在“接口层次结构”中选择表单元格视图 (NSTableFieldCell
) ,“属性检查器”中提供以下属性:
此处最重要的设置包括:
- 布局 - 选择此列中的单元格布局方式。
- 使用单行模式 - 如果
true
,单元格限制为单行。 - 第一个运行时布局宽度 - 如果
true
,单元格在首次运行应用程序时将首选为其设置的宽度(手动或自动)。 - 操作 - 控制何时为单元格发送编辑操作。
- 行为 - 定义单元格是否可选择或可编辑。
- 格式文本 - 如果
true
,单元格可以显示带格式的文本和样式文本。 - 撤消 - 如果
true
,单元格承担撤消行为的责任。
选择“接口层次结构”中表列底部的表单元格视图 (NSTableFieldCell
):
这样,您可以编辑用作为给定列创建的所有单元格的基模式的表格单元格视图。
添加操作和出口
与任何其他 Cocoa UI 控件一样,我们需要使用操作和出口(基于所需的功能)向 C# 代码公开表视图及其列和单元格。
对于要公开的任何表视图元素,此过程是相同的:
切换到“助理编辑器”并确保已选择
ViewController.h
文件:从“接口层次结构”中选择表视图,按住 Control 键单击并拖动到
ViewController.h
文件。为名为
ProductTable
的表视图创建出口:为表列创建出口,也称为
ProductColumn
和DetailsColumn
:保存更改并返回到 Visual Studio for Mac 以与 Xcode 同步。
接下来,我们将编写代码,以便在运行应用程序时显示表的一些数据。
填充表视图
在 Interface Builder 设计的表视图并通过出口公开后,接下来需要创建 C# 代码来填充它。
首先,让我们创建一个新的 Product
类来保存各个行的信息。 在“解决方案资源管理器”中,右键单击项目并选择“添加”>“新建文件...”。选择“常规>“空类”,输入 Product
作为“名称”,然后单击“新建”按钮:
使 Product.cs
文件如下所示:
using System;
namespace MacTables
{
public class Product
{
#region Computed Properties
public string Title { get; set;} = "";
public string Description { get; set;} = "";
#endregion
#region Constructors
public Product ()
{
}
public Product (string title, string description)
{
this.Title = title;
this.Description = description;
}
#endregion
}
}
接下来,我们需要创建一个 NSTableDataSource
子类,以便在请求表时为表提供数据。 在“解决方案资源管理器”中,右键单击项目并选择“添加”>“新建文件...”。选择“常规”>“空类”,输入 ProductTableDataSource
作为“名称”,然后单击“新建”按钮。
编辑 ProductTableDataSource.cs
文件,使其如下所示:
using System;
using AppKit;
using CoreGraphics;
using Foundation;
using System.Collections;
using System.Collections.Generic;
namespace MacTables
{
public class ProductTableDataSource : NSTableViewDataSource
{
#region Public Variables
public List<Product> Products = new List<Product>();
#endregion
#region Constructors
public ProductTableDataSource ()
{
}
#endregion
#region Override Methods
public override nint GetRowCount (NSTableView tableView)
{
return Products.Count;
}
#endregion
}
}
此类包含表视图项的存储,并重写 GetRowCount
以返回表中的行数。
最后,我们需要创建一个 NSTableDelegate
的子类来提供表的行为。 在“解决方案资源管理器”中,右键单击项目并选择“添加”>“新建文件...”。选择“常规>“空类”,输入 ProductTableDelegate
作为“名称”,然后单击“新建”按钮。
编辑 ProductTableDelegate.cs
文件,使其如下所示:
using System;
using AppKit;
using CoreGraphics;
using Foundation;
using System.Collections;
using System.Collections.Generic;
namespace MacTables
{
public class ProductTableDelegate: NSTableViewDelegate
{
#region Constants
private const string CellIdentifier = "ProdCell";
#endregion
#region Private Variables
private ProductTableDataSource DataSource;
#endregion
#region Constructors
public ProductTableDelegate (ProductTableDataSource datasource)
{
this.DataSource = datasource;
}
#endregion
#region Override Methods
public override NSView GetViewForItem (NSTableView tableView, NSTableColumn tableColumn, nint row)
{
// This pattern allows you reuse existing views when they are no-longer in use.
// If the returned view is null, you instance up a new view
// If a non-null view is returned, you modify it enough to reflect the new data
NSTextField view = (NSTextField)tableView.MakeView (CellIdentifier, this);
if (view == null) {
view = new NSTextField ();
view.Identifier = CellIdentifier;
view.BackgroundColor = NSColor.Clear;
view.Bordered = false;
view.Selectable = false;
view.Editable = false;
}
// Setup view based on the column selected
switch (tableColumn.Title) {
case "Product":
view.StringValue = DataSource.Products [(int)row].Title;
break;
case "Details":
view.StringValue = DataSource.Products [(int)row].Description;
break;
}
return view;
}
#endregion
}
}
创建 ProductTableDelegate
的实例时,还会传入提供表数据的 ProductTableDataSource
的实例。 GetViewForItem
方法负责返回视图(数据)以显示给定列和行的单元格。 如果可能,将重复使用现有视图来显示单元格(如果不是新视图)。
若要填充表,让我们编辑 ViewController.cs
文件,使 AwakeFromNib
方法如下所示:
public override void AwakeFromNib ()
{
base.AwakeFromNib ();
// Create the Product Table Data Source and populate it
var DataSource = new ProductTableDataSource ();
DataSource.Products.Add (new Product ("Xamarin.iOS", "Allows you to develop native iOS Applications in C#"));
DataSource.Products.Add (new Product ("Xamarin.Android", "Allows you to develop native Android Applications in C#"));
DataSource.Products.Add (new Product ("Xamarin.Mac", "Allows you to develop Mac native Applications in C#"));
// Populate the Product Table
ProductTable.DataSource = DataSource;
ProductTable.Delegate = new ProductTableDelegate (DataSource);
}
如果运行应用程序,将显示以下内容:
按列排序
让我们允许用户通过单击列标题对表中的数据进行排序。 首先,双击 Main.storyboard
该文件将其打开,以便在 Interface Builder 中编辑。 选择 Product
列,为“排序键”输入 Title
,为“选择器”compare:
输入 ,并为“顺序”选择 Ascending
:
选择 Details
列,为“排序键”输入 Description
,为“选择器”compare:
输入 ,并为“顺序”选择 Ascending
:
保存更改并返回到 Visual Studio for Mac 以与 Xcode 同步。
现在,让我们编辑 ProductTableDataSource.cs
文件并添加以下方法:
public void Sort(string key, bool ascending) {
// Take action based on key
switch (key) {
case "Title":
if (ascending) {
Products.Sort ((x, y) => x.Title.CompareTo (y.Title));
} else {
Products.Sort ((x, y) => -1 * x.Title.CompareTo (y.Title));
}
break;
case "Description":
if (ascending) {
Products.Sort ((x, y) => x.Description.CompareTo (y.Description));
} else {
Products.Sort ((x, y) => -1 * x.Description.CompareTo (y.Description));
}
break;
}
}
public override void SortDescriptorsChanged (NSTableView tableView, NSSortDescriptor[] oldDescriptors)
{
// Sort the data
if (oldDescriptors.Length > 0) {
// Update sort
Sort (oldDescriptors [0].Key, oldDescriptors [0].Ascending);
} else {
// Grab current descriptors and update sort
NSSortDescriptor[] tbSort = tableView.SortDescriptors;
Sort (tbSort[0].Key, tbSort[0].Ascending);
}
// Refresh table
tableView.ReloadData ();
}
Sort
方法允许我们根据给定 Product
类字段按升序或降序对数据源中的数据进行排序。 每次使用单击列标题时,都会调用重写的 SortDescriptorsChanged
方法。 它将传递我们在 Interface Builder 中设置的键值以及该列的排序顺序。
如果我们运行应用程序并单击列标题,则行将按该列进行排序:
行选择
如果要允许用户选择单行,请双击 Main.storyboard
文件以在 Interface Builder 中编辑。 在“接口层次结构”中选择表视图,然后取消选中“属性检查器”中的“多个”复选框:
保存更改并返回到 Visual Studio for Mac 以与 Xcode 同步。
接下来,编辑 ProductTableDelegate.cs
文件并添加以下方法:
public override bool ShouldSelectRow (NSTableView tableView, nint row)
{
return true;
}
这将允许用户选择表视图中的任何单行。 如果不希望用户能够选择任何行,则返回 false
,表示不希望用户能够选择或 false
每一行的 ShouldSelectRow
。
表视图 (NSTableView
) 包含以下用于处理行选择的方法:
DeselectRow(nint)
- 取消选择表中的给定行。SelectRow(nint,bool)
- 选择给定行。 传递第二个参数的false
,以便一次仅选择一行。SelectedRow
- 返回表中所选的当前行。IsRowSelected(nint)
- 如果选择给定行,则返回true
。
多行选择
如果希望允许用户选择多行,请双击 Main.storyboard
文件将其打开,以便在 Interface Builder 中编辑。 选择“接口层次结构”中的表视图,并在“属性检查器”中选中“多个”复选框:
保存更改并返回到 Visual Studio for Mac 以与 Xcode 同步。
接下来,编辑 ProductTableDelegate.cs
文件并添加以下方法:
public override bool ShouldSelectRow (NSTableView tableView, nint row)
{
return true;
}
这将允许用户选择表视图中的任何单行。 如果不希望用户能够选择任何行,则返回 false
,表示不希望用户能够选择或 false
每一行的 ShouldSelectRow
。
表视图 (NSTableView
) 包含以下用于处理行选择的方法:
DeselectAll(NSObject)
- 取消选择表中的所有行。 对执行选择的对象中发送的第一个参数使用this
。DeselectRow(nint)
- 取消选择表中的给定行。SelectAll(NSobject)
- 选择表中的所有行。 对执行选择的对象中发送的第一个参数使用this
。SelectRow(nint,bool)
- 选择给定行。 传递第二个参数的false
清除选定内容,只选择一行,传递true
以扩展所选内容并包含此行。SelectRows(NSIndexSet,bool)
- 选择给定的行集。 传递第二个参数的false
清除选定内容并仅选择这些行,传递true
以扩展所选内容并包括这些行。SelectedRow
- 返回表中所选的当前行。SelectedRows
- 返回包含所选行索引的NSIndexSet
。SelectedRowCount
- 返回所选行数。IsRowSelected(nint)
- 如果选择给定行,则返回true
。
键入以选择行
如果希望允许用户键入选中“表视图”的字符,并选择具有该字符的第一行,请双击 Main.storyboard
文件将其打开,以便在 Interface Builder 中编辑。 选择“接口层次结构”中的表视图,并在“属性检查器”中选中“类型选择”复选框:
保存更改并返回到 Visual Studio for Mac 以与 Xcode 同步。
现在,让我们编辑 ProductTableDelegate.cs
文件并添加以下方法:
public override nint GetNextTypeSelectMatch (NSTableView tableView, nint startRow, nint endRow, string searchString)
{
nint row = 0;
foreach(Product product in DataSource.Products) {
if (product.Title.Contains(searchString)) return row;
// Increment row counter
++row;
}
// If not found select the first row
return 0;
}
GetNextTypeSelectMatch
方法采用给定的 searchString
,并返回在其 Title
中具有该字符串的第一个 Product
。
如果运行应用程序并键入字符,则选择一行:
重新排序列
如果要允许用户在表视图中拖动重新排序列,请双击 Main.storyboard
文件将其打开,以便在 Interface Builder 中编辑。 在“接口层次结构”中选择表视图,并在“属性检查器”中选中“重新排序”复选框:
如果我们为“自动保存”属性提供值并检查“列信息”字段,则对表布局所做的任何更改将自动保存,并在下次运行应用程序时还原。
保存更改并返回到 Visual Studio for Mac 以与 Xcode 同步。
现在,让我们编辑 ProductTableDelegate.cs
文件并添加以下方法:
public override bool ShouldReorder (NSTableView tableView, nint columnIndex, nint newColumnIndex)
{
return true;
}
ShouldReorder
方法应返回要允许重新排序到 newColumnIndex
的任何列的 true
,否则返回 false
;
如果运行应用程序,我们可以拖动列标题来重新排序列:
编辑单元格
如果希望允许用户编辑给定单元格的值,请编辑 ProductTableDelegate.cs
文件并更改 GetViewForItem
方法,如下所示:
public override NSView GetViewForItem (NSTableView tableView, NSTableColumn tableColumn, nint row)
{
// This pattern allows you reuse existing views when they are no-longer in use.
// If the returned view is null, you instance up a new view
// If a non-null view is returned, you modify it enough to reflect the new data
NSTextField view = (NSTextField)tableView.MakeView (tableColumn.Title, this);
if (view == null) {
view = new NSTextField ();
view.Identifier = tableColumn.Title;
view.BackgroundColor = NSColor.Clear;
view.Bordered = false;
view.Selectable = false;
view.Editable = true;
view.EditingEnded += (sender, e) => {
// Take action based on type
switch(view.Identifier) {
case "Product":
DataSource.Products [(int)view.Tag].Title = view.StringValue;
break;
case "Details":
DataSource.Products [(int)view.Tag].Description = view.StringValue;
break;
}
};
}
// Tag view
view.Tag = row;
// Setup view based on the column selected
switch (tableColumn.Title) {
case "Product":
view.StringValue = DataSource.Products [(int)row].Title;
break;
case "Details":
view.StringValue = DataSource.Products [(int)row].Description;
break;
}
return view;
}
现在,如果运行应用程序,用户可以编辑表视图中的单元格:
在表视图中使用图像
若要在单元格中包含NSTableView
图像,需要更改表视图NSTableViewDelegate's
GetViewForItem
方法返回数据的方式,以使用NSTableCellView
而不是典型NSTextField
数据。 例如:
public override NSView GetViewForItem (NSTableView tableView, NSTableColumn tableColumn, nint row)
{
// This pattern allows you reuse existing views when they are no-longer in use.
// If the returned view is null, you instance up a new view
// If a non-null view is returned, you modify it enough to reflect the new data
NSTableCellView view = (NSTableCellView)tableView.MakeView (tableColumn.Title, this);
if (view == null) {
view = new NSTableCellView ();
if (tableColumn.Title == "Product") {
view.ImageView = new NSImageView (new CGRect (0, 0, 16, 16));
view.AddSubview (view.ImageView);
view.TextField = new NSTextField (new CGRect (20, 0, 400, 16));
} else {
view.TextField = new NSTextField (new CGRect (0, 0, 400, 16));
}
view.TextField.AutoresizingMask = NSViewResizingMask.WidthSizable;
view.AddSubview (view.TextField);
view.Identifier = tableColumn.Title;
view.TextField.BackgroundColor = NSColor.Clear;
view.TextField.Bordered = false;
view.TextField.Selectable = false;
view.TextField.Editable = true;
view.TextField.EditingEnded += (sender, e) => {
// Take action based on type
switch(view.Identifier) {
case "Product":
DataSource.Products [(int)view.TextField.Tag].Title = view.TextField.StringValue;
break;
case "Details":
DataSource.Products [(int)view.TextField.Tag].Description = view.TextField.StringValue;
break;
}
};
}
// Tag view
view.TextField.Tag = row;
// Setup view based on the column selected
switch (tableColumn.Title) {
case "Product":
view.ImageView.Image = NSImage.ImageNamed ("tags.png");
view.TextField.StringValue = DataSource.Products [(int)row].Title;
break;
case "Details":
view.TextField.StringValue = DataSource.Products [(int)row].Description;
break;
}
return view;
}
向行添加“删除”按钮
根据应用的要求,有时可能需要为表中的每一行提供一个操作按钮。 在此示例中,让我们展开上面创建的表视图示例,在每一行中包含“删除”按钮。
首先,在 Xcode 的 Interface Builder 中编辑 Main.storyboard
,选择表视图并将列数增加到三 (3)。 接下来,将新列的“标题”更改为 Action
:
保存对情节提要所做的更改,并返回到 Visual Studio for Mac 以同步更改。
接下来,编辑 ViewController.cs
文件并添加以下公共方法:
public void ReloadTable ()
{
ProductTable.ReloadData ();
}
在同一文件中,修改在 ViewDidLoad
方法内创建新的表视图委托,如下所示:
// Populate the Product Table
ProductTable.DataSource = DataSource;
ProductTable.Delegate = new ProductTableDelegate (this, DataSource);
现在,编辑 ProductTableDelegate.cs
文件以包含与视图控制器的专用连接,并在创建新委托实例时将控制器作为参数:
#region Private Variables
private ProductTableDataSource DataSource;
private ViewController Controller;
#endregion
#region Constructors
public ProductTableDelegate (ViewController controller, ProductTableDataSource datasource)
{
this.Controller = controller;
this.DataSource = datasource;
}
#endregion
接下来,将以下新的专用方法添加到类:
private void ConfigureTextField (NSTableCellView view, nint row)
{
// Add to view
view.TextField.AutoresizingMask = NSViewResizingMask.WidthSizable;
view.AddSubview (view.TextField);
// Configure
view.TextField.BackgroundColor = NSColor.Clear;
view.TextField.Bordered = false;
view.TextField.Selectable = false;
view.TextField.Editable = true;
// Wireup events
view.TextField.EditingEnded += (sender, e) => {
// Take action based on type
switch (view.Identifier) {
case "Product":
DataSource.Products [(int)view.TextField.Tag].Title = view.TextField.StringValue;
break;
case "Details":
DataSource.Products [(int)view.TextField.Tag].Description = view.TextField.StringValue;
break;
}
};
// Tag view
view.TextField.Tag = row;
}
这将采用以前在 GetViewForItem
方法中完成的所有文本视图配置,并将其置于单个可调用的位置(因为表的最后一列不包含文本视图,而是按钮)。
最后,编辑 GetViewForItem
方法,使其如下所示:
public override NSView GetViewForItem (NSTableView tableView, NSTableColumn tableColumn, nint row)
{
// This pattern allows you reuse existing views when they are no-longer in use.
// If the returned view is null, you instance up a new view
// If a non-null view is returned, you modify it enough to reflect the new data
NSTableCellView view = (NSTableCellView)tableView.MakeView (tableColumn.Title, this);
if (view == null) {
view = new NSTableCellView ();
// Configure the view
view.Identifier = tableColumn.Title;
// Take action based on title
switch (tableColumn.Title) {
case "Product":
view.ImageView = new NSImageView (new CGRect (0, 0, 16, 16));
view.AddSubview (view.ImageView);
view.TextField = new NSTextField (new CGRect (20, 0, 400, 16));
ConfigureTextField (view, row);
break;
case "Details":
view.TextField = new NSTextField (new CGRect (0, 0, 400, 16));
ConfigureTextField (view, row);
break;
case "Action":
// Create new button
var button = new NSButton (new CGRect (0, 0, 81, 16));
button.SetButtonType (NSButtonType.MomentaryPushIn);
button.Title = "Delete";
button.Tag = row;
// Wireup events
button.Activated += (sender, e) => {
// Get button and product
var btn = sender as NSButton;
var product = DataSource.Products [(int)btn.Tag];
// Configure alert
var alert = new NSAlert () {
AlertStyle = NSAlertStyle.Informational,
InformativeText = $"Are you sure you want to delete {product.Title}? This operation cannot be undone.",
MessageText = $"Delete {product.Title}?",
};
alert.AddButton ("Cancel");
alert.AddButton ("Delete");
alert.BeginSheetForResponse (Controller.View.Window, (result) => {
// Should we delete the requested row?
if (result == 1001) {
// Remove the given row from the dataset
DataSource.Products.RemoveAt((int)btn.Tag);
Controller.ReloadTable ();
}
});
};
// Add to view
view.AddSubview (button);
break;
}
}
// Setup view based on the column selected
switch (tableColumn.Title) {
case "Product":
view.ImageView.Image = NSImage.ImageNamed ("tag.png");
view.TextField.StringValue = DataSource.Products [(int)row].Title;
view.TextField.Tag = row;
break;
case "Details":
view.TextField.StringValue = DataSource.Products [(int)row].Description;
view.TextField.Tag = row;
break;
case "Action":
foreach (NSView subview in view.Subviews) {
var btn = subview as NSButton;
if (btn != null) {
btn.Tag = row;
}
}
break;
}
return view;
}
让我们更详细地了解一下此代码的几个部分。 首先,如果正在创建新的 NSTableViewCell
操作,则根据列的名称执行。 对于前两列(Product 和 Details),将调用新的 ConfigureTextField
方法。
对于“操作”列,将创建一个新的 NSButton
,并将其作为子视图添加到单元格中:
// Create new button
var button = new NSButton (new CGRect (0, 0, 81, 16));
button.SetButtonType (NSButtonType.MomentaryPushIn);
button.Title = "Delete";
button.Tag = row;
...
// Add to view
view.AddSubview (button);
按钮的 Tag
属性用于保存当前正在处理的行数。 当用户请求在按钮的 Activated
事件中删除行时,稍后将使用此数字:
// Wireup events
button.Activated += (sender, e) => {
// Get button and product
var btn = sender as NSButton;
var product = DataSource.Products [(int)btn.Tag];
// Configure alert
var alert = new NSAlert () {
AlertStyle = NSAlertStyle.Informational,
InformativeText = $"Are you sure you want to delete {product.Title}? This operation cannot be undone.",
MessageText = $"Delete {product.Title}?",
};
alert.AddButton ("Cancel");
alert.AddButton ("Delete");
alert.BeginSheetForResponse (Controller.View.Window, (result) => {
// Should we delete the requested row?
if (result == 1001) {
// Remove the given row from the dataset
DataSource.Products.RemoveAt((int)btn.Tag);
Controller.ReloadTable ();
}
});
};
在事件处理程序的开头,我们获取位于给定表行上的按钮和产品。 然后向用户显示一个警报,确认删除行。 如果用户选择删除该行,则会从数据源中删除给定行,并重新加载表:
// Remove the given row from the dataset
DataSource.Products.RemoveAt((int)btn.Tag);
Controller.ReloadTable ();
最后,如果要重复使用表视图单元格而不是创建新单元格,则以下代码将基于正在处理的列对其进行配置:
// Setup view based on the column selected
switch (tableColumn.Title) {
case "Product":
view.ImageView.Image = NSImage.ImageNamed ("tag.png");
view.TextField.StringValue = DataSource.Products [(int)row].Title;
view.TextField.Tag = row;
break;
case "Details":
view.TextField.StringValue = DataSource.Products [(int)row].Description;
view.TextField.Tag = row;
break;
case "Action":
foreach (NSView subview in view.Subviews) {
var btn = subview as NSButton;
if (btn != null) {
btn.Tag = row;
}
}
break;
}
对于“操作”列,将扫描所有子视图,直到找到 NSButton
,然后将它的 Tag
属性更新为指向当前行。
在这些更改到位后,当应用运行时,每一行都会有一个“删除”按钮:
当用户单击“删除”按钮时,将显示一个警报,要求他们删除给定的行:
如果用户选择删除,将删除该行,并将重绘表:
数据绑定表视图
通过在 Xamarin.Mac 应用程序中使用键值编码和数据绑定技术,可以大大减少必须编写和维护的代码量,以填充和使用 UI 元素。 还可以从前端用户界面 (Model-View-Controller)进一步分离支持数据(数据模型),从而更轻松地维护、更灵活的应用程序设计。
键值编码 (KVC) 是间接访问对象属性的机制,使用键(特殊格式的字符串)来标识属性,而不是通过实例变量或访问器方法 (get/set
) 访问它们。 通过在 Xamarin.Mac 应用程序中实现符合键值编码的访问器,可以访问其他 macOS 功能,例如键值观察 (KVO)、数据绑定、核心数据、Cocoa 绑定和可脚本性。
有关详细信息,请参阅“数据绑定和键值编码”文档的“表视图数据绑定”部分。
总结
本文详细介绍了如何使用 Xamarin.Mac 应用程序中的表视图。 我们了解了表视图的不同类型和用法,如何在 Xcode 的 Interface Builder 中创建和维护表视图,以及如何在 C# 代码中使用表视图。