Xamarin.Mac 中的表视图
本文介绍如何在 Xamarin.Mac 应用程序中使用表视图。 它介绍如何在 Xcode 和 Interface Builder 中创建表视图,并在代码中与其交互。
在 Xamarin.Mac 应用程序中使用 C# 和 .NET 时,可以访问与使用 Objective-CXcode 的开发人员相同的表视图。 由于 Xamarin.Mac 与 Xcode 直接集成,因此可以使用 Xcode 的 Interface Builder 创建和维护表视图 (或选择直接在 C# 代码) 中创建表视图。
表视图以表格格式显示数据,其中包含多行中的一列或多列信息。 根据创建的表视图的类型,用户可以按列排序、重新组织列、添加列、删除列或编辑表中包含的数据。
本文介绍在 Xamarin.Mac 应用程序中使用表视图的基础知识。 强烈建议先完成 Hello, Mac 一文,特别是 Xcode 和接口生成器简介 以及 输出口和操作 部分,因为它涵盖了我们将在本文中使用的关键概念和技术。
你可能还想要查看 Xamarin.Mac Internals 文档的向 Objective-CC# 类/方法公开部分,其中介绍了Register
用于将 C# 类Objective-C连接到对象和 UI 元素的 和 Export
命令。
表视图简介
表视图以表格格式显示数据,其中包含多行中的一列或多列信息。 表视图显示在滚动视图 (NSScrollView
) ,从 macOS 10.7 开始,可以使用任意 NSView
单元格 (NSCell
) 来显示行和列。 也就是说,你仍然可以使用 NSCell
,但是,你通常会使用子类 NSTableCellView
并创建自定义行和列。
表视图不存储自己的数据,而是依赖于数据源 (NSTableViewDataSource
) 根据需要提供所需的行和列。
可以通过提供表视图委托 () NSTableViewDelegate
的子类来自定义表视图的行为,以支持表列管理、键入以选择功能、行选择和编辑、自定义跟踪以及单个列和行的自定义视图。
创建表视图时,Apple 会提出以下建议:
- 允许用户通过单击列标题对表进行排序。
- 创建作为名词或短名词短语的列标题,用于描述该列中显示的数据。
有关详细信息,请参阅 Apple OS X 人机接口指南的内容视图部分。
在 Xcode 中创建和维护表视图
创建新的 Xamarin.Mac Cocoa 应用程序时,默认情况下会获得一个标准空白窗口。 此窗口在 .storyboard
项目中自动包含的文件中定义。 若要编辑 Windows 设计,请在解决方案资源管理器中双击 Main.storyboard
文件:
这将在 Xcode 的 Interface Builder 中打开窗口设计:
在库检查器的搜索框中键入 table
,以便更轻松地查找表视图控件:
将表视图拖到 接口编辑器中的视图控制器上,使其填充视图控制器的内容区域,并将其设置为随 约束编辑器中的窗口一起收缩和增长的位置:
在 “接口层次结构 ”中选择“表视图”, 属性检查器中提供了以下属性:
- 内容模式 - 允许使用视图 (
NSView
) 或单元格 (NSCell
) 显示行和列中的数据。 从 macOS 10.7 开始,应使用视图。 - 浮点分组行 - 如果
true
为 ,则表视图将绘制分组单元格,就像它们浮动一样。 - 列 - 定义显示的列数。
- 标头 - 如果
true
为 ,则列将具有标头。 - 重新排序 - 如果
true
为 ,则用户将能够拖动对表中的列重新排序。 - 调整大小 - 如果
true
为 ,用户将能够拖动列标题以调整列的大小。 - 列大小调整 - 控制表如何自动调整列的大小。
- 突出显示 - 控制选定单元格时表使用的突出显示类型。
- 备用行 - 如果
true
为 ,则其他行将具有不同的背景色。 - 水平网格 - 选择水平在单元格之间绘制的边框类型。
- 垂直网格 - 选择在单元格之间垂直绘制的边框类型。
- 网格颜色 - 设置单元格边框颜色。
- 背景 - 设置单元格背景色。
- 选择 - 允许你控制用户如何按以下方式选择表中的单元格:
- 多 - 如果
true
为 ,则用户可以选择多个行和列。 - 列 - 如果
true
为 ,则用户可以选择列。 - 键入 Select - 如果
true
为 ,则用户可以键入字符以选择行。 - 空 - 如果
true
为 ,则用户不需要选择行或列,则表根本不允许选择。
- 多 - 如果
- 自动保存 - 表格式自动保存的名称。
- 列信息 - 如果
true
为 ,将自动保存列的顺序和宽度。 - 换行符 - 选择单元格处理换行符的方式。
- 截断最后一条可见行 - 如果
true
为 ,则单元格将被截断,数据不能容纳在它的边界内。
重要
除非维护旧版 Xamarin.Mac 应用程序, NSView
否则应使用基于表视图的 NSCell
表视图。 NSCell
被视为旧版,以后可能不再受支持。
在 接口层次结构 中选择一个表列, 属性检查器中提供了以下属性:
- 标题 - 设置列的标题。
- 对齐 方式 - 设置单元格内文本的对齐方式。
- 标题字体 - 选择单元格标题文本的字体。
- 排序键 - 用于对列中的数据进行排序的键。 如果用户无法对此列进行排序,则留空。
- 选择器 - 用于执行排序 的操作 。 如果用户无法对此列进行排序,则留空。
- Order - 列数据的排序顺序。
- 调整大小 - 选择列的大小调整类型。
- 可编辑 - 如果
true
为 ,则用户可以编辑基于单元格的表中的单元格。 - Hidden - 如果
true
为 ,则隐藏列。
还可以通过将列的手柄 (垂直居中) 向左或向右拖动列来调整列的大小。
让我们在表视图中选择每个列,并为第一列指定 标题 , Product
为第二列 Details
指定标题。
选择“表单元格视图” (NSTableViewCell
接口层次结构 中的) ,属性 检查器中提供了以下属性:
这些是标准视图的所有属性。 还可以在此处选择调整此列的行大小。
默认情况下,选择表视图单元格 (,这是接口层次结构中的) NSTextField
,属性检查器中提供以下属性:
此处将设置标准文本字段的所有属性。 默认情况下,标准文本字段用于显示列中单元格的数据。
选择“表单元格视图” (NSTableFieldCell
接口层次结构 中的) ,属性 检查器中提供了以下属性:
此处最重要的设置包括:
- 布局 - 选择此列中单元格的布局方式。
- 使用单行模式 - 如果
true
为 ,则单元格限制为单行。 - 第一个运行时布局宽度 - 如果
true
为 ,则单元格将首选为其设置的宽度, (首次运行应用程序时手动或自动) 。 - 操作 - 控制何时为单元格发送编辑 操作 。
- 行为 - 定义单元格是可选择的还是可编辑的。
- RTF - 如果
true
为 ,则单元格可以显示带格式和样式的文本。 - 撤消 - 如果
true
为 ,则单元格对其撤消行为负责。
选择“表单元格视图” (NSTableFieldCell
接口层次结构中表列底部的) :
这样,您可以编辑用作为给定列创建的所有单元格的基本 模式 的表单元格视图。
添加操作和出口
与任何其他 Cocoa UI 控件一样,我们需要根据) 所需的功能,使用 操作 和 输出口 (向 C# 代码公开表视图及其列和单元格。
对于我们想要公开的任何表视图元素,此过程都是相同的:
切换到 “助理编辑器” ,并确保
ViewController.h
已选择该文件:从“ 接口层次结构”中选择“表视图”,按住 control 并单击并拖动到
ViewController.h
该文件。为表视图创建名为 的
ProductTable
出口:为表列创建 出口 ,也称为
ProductColumn
和DetailsColumn
:保存更改并返回到 Visual Studio for Mac 以与 Xcode 同步。
接下来,我们将编写代码,在应用程序运行时显示表的一些数据。
填充表视图
由于表视图在接口生成器中设计并通过 输出口公开,接下来需要创建 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 中设置的 Key 值以及该列的排序顺序。
如果我们运行应用程序并单击“列标题”,则行将按该列排序:
行选择
如果要允许用户选择单行,请 Main.storyboard
双击该文件以将其打开,以便在 Interface Builder 中编辑。 在“接口层次结构”中选择“表视图”,然后取消选中“属性检查器”中的“多个”复选框:
保存更改并返回到 Visual Studio for Mac 以与 Xcode 同步。
接下来,编辑 ProductTableDelegate.cs
文件并添加以下方法:
public override bool ShouldSelectRow (NSTableView tableView, nint row)
{
return true;
}
这将允许用户选择表视图中的任意一行。 ShouldSelectRow
对于不希望用户能够选择的任何行,返回 false
;如果不希望用户能够选择false
任何行,则返回每行的 。
表视图 (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;
}
这将允许用户选择表视图中的任意一行。 ShouldSelectRow
对于不希望用户能够选择的任何行,返回 false
;如果不希望用户能够选择false
任何行,则返回每行的 。
表视图 (NSTableView
) 包含以下用于处理行选择的方法:
DeselectAll(NSObject)
- 取消选择表中的所有行。 使用this
在对象中发送执行选择的第一个参数。DeselectRow(nint)
- 取消选择表中的给定行。SelectAll(NSobject)
- 选择表中的所有行。 使用this
在对象中发送执行选择的第一个参数。SelectRow(nint,bool)
- 选择给定行。 第二个参数的 Passfalse
清除所选内容并仅选择一行,传递true
以扩展所选内容并包含此行。SelectRows(NSIndexSet,bool)
- 选择给定的行集。 第二个参数的 Passfalse
清除所选内容并仅选择这些行,传递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
的 ,并返回中具有该字符串的第一个 Product
的 Title
行。
如果运行应用程序并键入字符,则会选择一行:
对列重新排序
如果要允许用户在表视图中拖动重新排序列,请 Main.storyboard
双击该文件以将其打开,以便在 Interface Builder 中编辑。 在“接口层次结构”中选择“表视图”,检查属性检查器中的“重新排序”复选框:
如果我们为“自动保存”属性指定值并检查“列信息”字段,则我们对表布局所做的任何更改将自动保存,并在下次运行应用程序时还原。
保存更改并返回到 Visual Studio for Mac 以与 Xcode 同步。
现在,让我们编辑 ProductTableDelegate.cs
文件并添加以下方法:
public override bool ShouldReorder (NSTableView tableView, nint columnIndex, nint newColumnIndex)
{
return true;
}
方法 ShouldReorder
应返回 true
它希望允许拖动到 中重新排序 newColumnIndex
的任何列,否则返回 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
,需要更改表视图GetViewForItem
NSTableViewDelegate's
的 方法返回数据的方式,以使用 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 的接口生成器中编辑 Main.storyboard
,选择表视图并将列数增加到 3 (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
完成的所有文本视图配置,并将其置于单个可调用位置 (,因为表的最后一列不包含文本视图,而是包含 Button) 。
最后,编辑 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);
Button 的 Tag
属性用于保存当前正在处理的行的编号。 稍后当用户请求在 Button 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 应用程序中使用Key-Value编码和数据绑定技术,可以大大减少填充和使用 UI 元素而必须编写和维护的代码量。 还可以进一步将后备数据 (数据模型) 与前端用户界面 (模型-视图-控制器) 分离,从而简化维护、更灵活的应用程序设计。
Key-Value 编码 (KVC) 是一种间接访问对象的属性的机制,使用键 (特殊格式的字符串) 来标识属性,而不是通过实例变量或访问器方法 (get/set
) 访问它们。 通过在 Xamarin.Mac 应用程序中实现Key-Value编码合规访问器,可以访问其他 macOS 功能,例如Key-Value观察 (KVO) 、数据绑定、核心数据、Cocoa 绑定和可脚本性。
有关详细信息,请参阅数据绑定和Key-Value编码文档的表视图数据绑定部分。
总结
本文详细介绍了在 Xamarin.Mac 应用程序中使用表视图。 我们了解了表视图的不同类型和用途,如何在 Xcode 的接口生成器中创建和维护表视图,以及如何在 C# 代码中使用表视图。