Xamarin 中的表视图Table Views in Xamarin.Mac

本文介绍如何在 Xamarin. Mac 应用程序中使用表视图。它介绍了如何在 Xcode 中创建表视图,并 Interface Builder 在代码中与它们进行交互。This article covers working with table views in a Xamarin.Mac application. It describes creating table views in Xcode and Interface Builder and interacting with them in code.

在 Xamarin 应用C# 程序中使用和 .net 时,您可以访问与在Xcode 和中工作的开发人员相同的表视图。When working with C# and .NET in a Xamarin.Mac application, you have access to the same Table Views that a developer working in Objective-C and Xcode does. 因为 Xamarin 与 Xcode 直接集成,你可以使用 Xcode 的_Interface Builder_来创建和维护你的表视图(或者可以选择直接在代码中C#创建)。Because Xamarin.Mac integrates directly with Xcode, you can use Xcode's Interface Builder to create and maintain your Table Views (or optionally create them directly in C# code).

表格视图以表格格式显示数据,其中包含多个行中的一列或多列信息。A Table View displays data in a tabular format containing one or more columns of information in multiple rows. 根据所创建的表视图的类型,用户可以按列排序、重新组织列、添加列、删除列或编辑表中包含的数据。Based on the type of Table View being created, the user can sort by column, reorganize columns, add columns, remove columns or edit the data contained within the table.

在本文中,我们将介绍在 Xamarin. Mac 应用程序中使用表视图的基础知识。In this article, we'll cover the basics of working with Table Views in a Xamarin.Mac application. 强烈建议您先完成Hello,Mac一文,特别是Xcode 和 Interface Builder输出口和操作部分的简介,因为它涵盖了我们将在本文。It is highly suggested that you work through the Hello, Mac article first, specifically the Introduction to Xcode and Interface Builder and Outlets and Actions sections, as it covers key concepts and techniques that we'll be using in this article.

你可能想要查看Xamarin示例文档的 " C# 公开C#类/方法到目标-c " 部分,并说明用于将类连接到目标的RegisterExport命令-c对象和 UI 元素。You may want to take a look at the Exposing C# classes / methods to Objective-C section of the Xamarin.Mac Internals document as well, it explains the Register and Export commands used to wire-up your C# classes to Objective-C objects and UI Elements.

表视图简介Introduction to Table Views

表格视图以表格格式显示数据,其中包含多个行中的一列或多列信息。A Table View displays data in a tabular format containing one or more columns of information in multiple rows. 表视图显示在滚动视图(NSScrollView)中,从 macOS 10.7 开始,可以使用任意 NSView 而不是单元格(NSCell)显示行和列。Table Views are displayed inside of Scroll Views (NSScrollView) and starting with macOS 10.7, you can use any NSView instead of Cells (NSCell) to display both rows and columns. 也就是说,您仍然可以使用 NSCell 不过,您通常 NSTableCellView 并创建自定义行和列。That said, you can still use NSCell however, you'll typically subclass NSTableCellView and create your custom rows and columns.

表视图不存储它自己的数据,而是依赖于数据源(NSTableViewDataSource)根据需要提供所需的行和列。A Table View does not store it's own data, instead it relies on a Data Source (NSTableViewDataSource) to provide both the rows and columns required, on a as-needed basis.

可以通过提供表视图委托(NSTableViewDelegate)的子类来自定义表视图的行为,以支持表列管理、键入以选择功能、行选择和编辑、自定义跟踪以及单独的列和行的自定义视图。A Table View's behavior can be customized by providing a subclass of the Table View Delegate (NSTableViewDelegate) to support table column management, type to select functionality, row selection and editing, custom tracking, and custom views for individual columns and rows.

创建表视图时,Apple 建议以下内容:When creating Table Views, Apple suggests the following:

  • 允许用户通过单击列标题对表进行排序。Allow the user to sort the table by clicking on a Column Headers.
  • 创建用名词或短名词短语描述列中显示的数据的列标题。Create Column Headers that are nouns or short noun phrases that describe the data being shown in that column.

有关详细信息,请参阅 Apple OS X 人体学接口准则内容视图部分。For more information, please see the Content Views section of Apple's OS X Human Interface Guidelines.

在 Xcode 中创建和维护表视图Creating and Maintaining Table Views in Xcode

创建新的 Xamarin Cocoa 应用程序时,默认情况下会获得一个标准空白窗口。When you create a new Xamarin.Mac Cocoa application, you get a standard blank, window by default. 此窗口在项目中自动包含的 .storyboard 文件中定义。This windows is defined in a .storyboard file automatically included in the project. 若要编辑 windows 设计,请在 "解决方案资源管理器中,双击 Main.storyboard 文件:To edit your windows design, in the Solution Explorer, double click the Main.storyboard file:

这将在 Xcode 的 Interface Builder 中打开窗口设计:This will open the window design in Xcode's Interface Builder:

table 键入到库检查器的搜索框中,以便更轻松地查找表视图控件:Type table into the Library Inspector's Search Box to make it easier to find the Table View controls:

将表视图拖到 "界面编辑器" 中的视图控制器上,使其填充视图控制器的内容区域,并将其设置为在 "约束编辑器" 中的窗口缩小和增长的位置:Drag a Table View onto the View Controller in the Interface Editor, make it fill the content area of the View Controller and set it to where it shrinks and grows with the window in the Constraint Editor:

选择接口层次结构中的表视图,属性检查器中提供以下属性:Select the Table View in the Interface Hierarchy and the following properties are available in the Attribute Inspector:

  • 内容模式-允许使用视图(NSView)或单元格(NSCell)显示行和列中的数据。Content Mode - Allows you to use either Views (NSView) or Cells (NSCell) to display the data in the rows and columns. 从 macOS 10.7 开始,应使用视图。Starting with macOS 10.7, you should use Views.
  • 浮动组行-如果 true,表视图将绘制已分组的单元格,就好像它们是浮动的。Floats Group Rows - If true, the Table View will draw grouped cells as if they are floating.
  • -定义显示的列数。Columns - Defines the number of columns displayed.
  • 标头-如果 true,列将具有标头。Headers - If true, the columns will have Headers.
  • 重新排序-如果 true,用户将能够在表中拖动列的顺序。Reordering - If true, the user will be able to drag reorder the columns in the table.
  • 调整大小-如果 true,用户将能够拖动列标题以调整列的大小。Resizing - If true, the user will be able to drag column Headers to resize columns.
  • 列大小调整-控制表自动调整列大小的方式。Column Sizing - Controls how the table will auto size columns.
  • 突出显示-控制在选择单元格时表使用的突出显示类型。Highlight - Controls the type of highlighting the table uses when a cell is selected.
  • 替换行-如果 true,则其他行将具有不同的背景色。Alternate Rows - If true, ever other row will have a different background color.
  • 水平网格-选择在单元格之间水平绘制的边框类型。Horizontal Grid - Selects the type of border drawn between cells horizontally.
  • 垂直网格-选择在单元格之间垂直绘制的边框类型。Vertical Grid - Selects the type of border drawn between cells vertically.
  • 网格颜色-设置单元格边框颜色。Grid Color - Sets the cell border color.
  • 背景-设置单元格背景色。Background - Sets the cell background color.
  • 选择-允许你控制用户如何选择表中的单元格,如下所示:Selection - Allow you to control how the user can select cells in the table as:
    • -如果 true,则用户可以选择多个行和列。Multiple - If true, the user can select multiple rows and columns.
    • -如果 true,则用户可以选择列。Column - If true,the user can select columns.
    • 键入 select -如果 true,则用户可以键入一个字符来选择行。Type Select - If true, the user can type a character to select a row.
    • -如果 true,则不要求用户选择行或列,表根本不允许任何选择。Empty - If true, the user is not required to select a row or column, the table allows for no selection at all.
  • 自动保存-表格式将自动保存在下面的名称。Autosave - The name that the tables format is automatically save under.
  • 列信息-如果 true,列的顺序和宽度将自动保存。Column Information - If true, the order and width of the columns will be automatically saved.
  • 换行符-选择单元格如何处理分行符。Line Breaks - Select how the cell handles line breaks.
  • 截断最后一个可见行-如果 true,则数据中的单元格将被截断,而不能容纳在它的边界内。Truncates Last Visible Line - If true, the cell will be truncated in the data can not fit inside it's bounds.

重要

除非您维护的是旧的 Xamarin Mac 应用程序,否则基于 NSCell 的表视图应使用基于 NSView 的表视图。Unless you are maintaining a legacy Xamarin.Mac application, NSView based Table Views should be used over NSCell based Table Views. NSCell 被视为旧的,可能不会受到支持。NSCell is considered legacy and may not be supported going forward.

选择接口层次结构中的表列,"属性检查器" 中提供以下属性:Select a Table Column in the Interface Hierarchy and the following properties are available in the Attribute Inspector:

  • 标题-设置列的标题。Title - Sets the title of the column.
  • 对齐方式-设置单元格中的文本对齐方式。Alignment - Set the alignment of the text within the cells.
  • 标题字体-选择单元格标题文本的字体。Title Font - Selects the font for the cell's Header text.
  • 排序键-用于对列中的数据进行排序的键。Sort Key - Is the key used to sort data in the column. 如果用户不能对此列进行排序,则保留为空。Leave blank if the user cannot sort this column.
  • 选择器-用于执行排序的操作Selector - Is the Action used to perform the sort. 如果用户不能对此列进行排序,则保留为空。Leave blank if the user cannot sort this column.
  • Order -列数据的排序顺序。Order - Is the sort order for the columns data.
  • 调整大小-选择列的调整大小类型。Resizing - Selects the type of resizing for the column.
  • 可编辑-如果 true,用户可以在基于单元格的表中编辑单元格。Editable - If true, the user can edit cells in a cell based table.
  • Hidden -如果 true,则隐藏列。Hidden - If true, the column is hidden.

还可以通过将列的控点拖到左侧或右侧,来调整列的大小。You can also resize the column by dragging it's handle (vertically centered on the column's right side) left or right.

让我们选择表视图中的每一列,并为第一列指定标题Product 第二列 DetailsLet's select the each Column in our Table View and give the first column a Title of Product and the second one Details.

接口层次结构中选择一个表单元视图(NSTableViewCell),属性检查器中提供以下属性:Select a Table Cell View (NSTableViewCell) in the Interface Hierarchy and the following properties are available in the Attribute Inspector:

这些是标准视图的所有属性。These are all of the properties of a standard View. 你还可以选择在此处调整此列的行的大小。You also have the option of resizing the rows for this column here.

选择表视图单元(默认情况下,这是 NSTextField)在接口层次结构中,特性检查器中提供以下属性:Select a Table View Cell (by default, this is a NSTextField) in the Interface Hierarchy and the following properties are available in the Attribute Inspector:

你将在此处设置标准文本字段的所有属性。You'll have all the properties of a standard text field to set here. 默认情况下,标准文本字段用于显示列中单元格的数据。By default, a standard Text Field is used to display data for a cell in a column.

接口层次结构中选择一个表单元视图(NSTableFieldCell),属性检查器中提供以下属性:Select a Table Cell View (NSTableFieldCell) in the Interface Hierarchy and the following properties are available in the Attribute Inspector:

最重要的设置如下:The most important settings here are:

  • 布局-选择此列中的单元格的布局方式。Layout - Select how cells in this column are laid out.
  • 使用单行模式-如果 true,则单元格限制为单独的一行。Uses Single Line Mode - If true, the cell is limited to a single line.
  • 第一次运行时布局宽度-如果 true,则首次运行应用程序时,该单元格将更喜欢它的宽度设置(手动或自动)。First Runtime Layout Width - If true, the cell will prefer the width set for it (either manually or automatically) when it is displayed the first time the application is run.
  • 操作-控制向单元格发送编辑操作的时间。Action - Controls when the Edit Action is sent for the cell.
  • 行为-定义单元是否可选择或可编辑。Behavior - Defines if a cell is selectable or editable.
  • 格式文本-如果 true,则单元格可以显示已设置格式的文本。Rich Text - If true, the cell can display formatted and styled text.
  • 撤消-如果 true,单元将承担其撤消行为的责任。Undo - If true, the cell assumes responsibility for it's undo behavior.

选择接口层次结构中表列底部的表单元视图(NSTableFieldCell):Select the Table Cell View (NSTableFieldCell) at the bottom of a Table Column in the Interface Hierarchy:

这允许您编辑作为给定列创建的所有单元格的基本_模式_的表单元视图。This allows you to edit the Table Cell View used as the base Pattern for all cells created for the given column.

添加操作和插座Adding Actions and Outlets

就像任何其他 Cocoa UI 控件一样,我们需要将表视图及其列和单元格公开给使用C# 操作插座进行编码(基于所需的功能)。Just like any other Cocoa UI control, we need to expose our Table View and it's columns and cells to C# code using Actions and Outlets (based on the functionality required).

对于要公开的任何表视图元素,此过程都是相同的:The process is the same for any Table View element that we want to expose:

  1. 切换到 "助手编辑器" 并确保选中 ViewController.h 文件:Switch to the Assistant Editor and ensure that the ViewController.h file is selected:

  2. 接口层次结构中选择表视图,然后单击并拖动到 ViewController.h 文件中。Select the Table View from the Interface Hierarchy, control-click and drag to the ViewController.h file.

  3. 为名为 ProductTable的表视图创建输出口Create an Outlet for the Table View called ProductTable:

  4. 为表列创建插座,并将其称为 ProductColumnDetailsColumnCreate Outlets for the tables columns as well called ProductColumn and DetailsColumn:

  5. 保存更改并返回到 Visual Studio for Mac 以与 Xcode 同步。Save you changes and return to Visual Studio for Mac to sync with Xcode.

接下来,我们将编写代码,以便在运行应用程序时显示表的一些数据。Next, we'll write the code display some data for the table when the application is run.

填充表视图Populating the Table View

使用在 Interface Builder 中设计的表视图并通过插座公开后,接下来需要创建C#代码来填充它。With our Table View designed in Interface Builder and exposed via an Outlet, next we need to create the C# code to populate it.

首先,让我们创建一个新的 Product 类来保存各个行的信息。First, let's create a new Product class to hold the information for the individual rows. 解决方案资源管理器中,右键单击项目,然后选择 "添加 > 新文件 ... "选择 "常规" > 空类",在"名称"中输入 Product,然后单击"新建"按钮:In the Solution Explorer, right-click the Project and select Add > New File... Select General > Empty Class, enter Product for the Name and click the New button:

使 Product.cs 文件如下所示:Make the Product.cs file look like the following:

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 的子类,以便在请求表时为其提供数据。Next, we need to create a subclass of NSTableDataSource to provide the data for our table as it is requested. 解决方案资源管理器中,右键单击项目,然后选择 "添加 > 新文件 ... "选择 "常规" > 空类",在"名称"中输入 ProductTableDataSource,然后单击"新建"按钮。In the Solution Explorer, right-click the Project and select Add > New File... Select General > Empty Class, enter ProductTableDataSource for the Name and click the New button.

编辑 ProductTableDataSource.cs 文件,使其类似于以下内容:Edit the ProductTableDataSource.cs file and make it look like the following:

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 以返回表中的行数。This class has storage for our Table View's items and overrides the GetRowCount to return the number of rows in the table.

最后,我们需要创建 NSTableDelegate 的子类,以提供表的行为。Finally, we need to create a subclass of NSTableDelegate to provide the behavior for our table. 解决方案资源管理器中,右键单击项目,然后选择 "添加 > 新文件 ... "选择 "常规" > 空类",在"名称"中输入 ProductTableDelegate,然后单击"新建"按钮。In the Solution Explorer, right-click the Project and select Add > New File... Select General > Empty Class, enter ProductTableDelegate for the Name and click the New button.

编辑 ProductTableDelegate.cs 文件,使其类似于以下内容:Edit the ProductTableDelegate.cs file and make it look like the following:

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 的实例。When we create an instance of the ProductTableDelegate, we also pass in an instance of the ProductTableDataSource that provides the data for the table. GetViewForItem 方法负责返回一个视图(数据),以显示给定列和行的单元格。The GetViewForItem method is responsible for returning a view (data) to display the cell for a give column and row. 如果可能,如果不创建新视图,则将重复使用现有视图来显示该单元。If possible, an existing view will be reused to display the cell, if not a new view must be created.

若要填充表,请编辑 ViewController.cs 文件,使 AwakeFromNib 方法如下所示:To populate the table, let's edit the ViewController.cs file and make the AwakeFromNib method look like the following:

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);
}

如果运行该应用程序,将显示以下内容:If we run the application, the following is displayed:

按列排序Sorting by Column

允许用户通过单击列标题来对表中的数据进行排序。Let's allow the user to sort the data in the table by clicking on a Column Header. 首先,双击 Main.storyboard 文件以将其打开,以便在 Interface Builder 中进行编辑。First, double-click the Main.storyboard file to open it for editing in Interface Builder. 选择 "Product" 列,输入 "排序关键字" Title,为选择器compare:,然后选择订单AscendingSelect the Product column, enter Title for the Sort Key, compare: for the Selector and select Ascending for the Order:

选择 "Details" 列,输入 "排序关键字" Description,为选择器compare:,然后选择订单AscendingSelect the Details column, enter Description for the Sort Key, compare: for the Selector and select Ascending for the Order:

保存更改并返回到 Visual Studio for Mac 以与 Xcode 同步。Save your changes and return to Visual Studio for Mac to sync with Xcode.

现在,让我们编辑 ProductTableDataSource.cs 文件,并添加以下方法:Now let's edit the ProductTableDataSource.cs file and add the following methods:

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 类字段按升序或降序对数据源中的数据进行排序。The Sort method allow us to sort the data in the Data Source based on a given Product class field in either ascending or descending order. 每次单击列标题时,都将调用重写的 SortDescriptorsChanged 方法。The overridden SortDescriptorsChanged method will be called every time the use clicks on a Column Heading. 它将被传递到 Interface Builder 中设置的键值和该列的排序顺序。It will be passed the Key value that we set in Interface Builder and the sort order for that column.

如果运行应用程序,并单击列标题中的行,则将按该列对行进行排序:If we run the application and click in the Column Headers, the rows will be sorted by that column:

行选择Row Selection

如果要允许用户选择单个行,请双击 Main.storyboard 文件以将其打开,以便在 Interface Builder 中进行编辑。If you want to allow the user to select a single row, double-click the Main.storyboard file to open it for editing in Interface Builder. 选择接口层次结构中的表视图,并取消选中 "属性检查器" 中的 "多个" 复选框:Select the Table View in the Interface Hierarchy and uncheck the Multiple checkbox in the Attribute Inspector:

保存更改并返回到 Visual Studio for Mac 以与 Xcode 同步。Save your changes and return to Visual Studio for Mac to sync with Xcode.

接下来,编辑 ProductTableDelegate.cs 文件并添加以下方法:Next, edit the ProductTableDelegate.cs file and add the following method:

public override bool ShouldSelectRow (NSTableView tableView, nint row)
{
  return true;
}

这将允许用户在表视图中选择任何单个行。This will allow the user to select any single row in the Table View. 如果你不希望用户能够选择任何行,则为你不希望用户能够为每行选择或 false 的任何行返回 ShouldSelectRow falseReturn false for the ShouldSelectRow for any row that you don't want the user to be able to select or false for every row if you don't want the user to be able to select any rows.

表格视图(NSTableView)包含以下用于处理行选择的方法:The Table View (NSTableView) contains the following methods for working with row selection:

  • DeselectRow(nint) 在表中取消选择给定行。DeselectRow(nint) - Deselects the given row in the table.
  • SelectRow(nint,bool)-选择给定行。SelectRow(nint,bool) - Selects the given row. 将第二个参数的 false 传递给一次只选择一行。Pass false for the second parameter to select only one row at a time.
  • SelectedRow-返回在表中选择的当前行。SelectedRow - Returns the current row selected in the table.
  • IsRowSelected(nint)-如果选择了给定行,则返回 trueIsRowSelected(nint) - Returns true if the given row is selected.

多行选择Multiple Row Selection

如果要允许用户选择多个行,请双击 Main.storyboard 文件以将其打开,以便在 Interface Builder 中进行编辑。If you want to allow the user to select a multiple rows, double-click the Main.storyboard file to open it for editing in Interface Builder. 选择接口层次结构中的表视图,并选中 "属性检查器" 中的 "多个" 复选框:Select the Table View in the Interface Hierarchy and check the Multiple checkbox in the Attribute Inspector:

保存更改并返回到 Visual Studio for Mac 以与 Xcode 同步。Save your changes and return to Visual Studio for Mac to sync with Xcode.

接下来,编辑 ProductTableDelegate.cs 文件并添加以下方法:Next, edit the ProductTableDelegate.cs file and add the following method:

public override bool ShouldSelectRow (NSTableView tableView, nint row)
{
  return true;
}

这将允许用户在表视图中选择任何单个行。This will allow the user to select any single row in the Table View. 如果你不希望用户能够选择任何行,则为你不希望用户能够为每行选择或 false 的任何行返回 ShouldSelectRow falseReturn false for the ShouldSelectRow for any row that you don't want the user to be able to select or false for every row if you don't want the user to be able to select any rows.

表格视图(NSTableView)包含以下用于处理行选择的方法:The Table View (NSTableView) contains the following methods for working with row selection:

  • DeselectAll(NSObject)-取消选择表中的所有行。DeselectAll(NSObject) - Deselects all rows in the table. 使用 this 在执行选择的对象中发送的第一个参数。Use this for the first parameter to send in the object doing the selecting.
  • DeselectRow(nint) 在表中取消选择给定行。DeselectRow(nint) - Deselects the given row in the table.
  • SelectAll(NSobject)-选择表中的所有行。SelectAll(NSobject) - Selects all rows in the table. 使用 this 在执行选择的对象中发送的第一个参数。Use this for the first parameter to send in the object doing the selecting.
  • SelectRow(nint,bool)-选择给定行。SelectRow(nint,bool) - Selects the given row. 为第二个参数传递 false 清除所选内容并仅选择单个行,传递 true 以扩展选定内容并包含该行。Pass false for the second parameter clear the selection and select only a single row, pass true to extend the selection and include this row.
  • SelectRows(NSIndexSet,bool)-选择给定的行集。SelectRows(NSIndexSet,bool) - Selects the given set of rows. 为第二个参数传递 false 清除所选内容并仅选择这些行,传递 true 以扩展选定内容并包含这些行。Pass false for the second parameter clear the selection and select only a these rows, pass true to extend the selection and include these rows.
  • SelectedRow-返回在表中选择的当前行。SelectedRow - Returns the current row selected in the table.
  • SelectedRows-返回一个 NSIndexSet,其中包含所选行的索引。SelectedRows - Returns a NSIndexSet containing the indexes of the selected rows.
  • SelectedRowCount-返回选定行的数目。SelectedRowCount - Returns the number of selected rows.
  • IsRowSelected(nint)-如果选择了给定行,则返回 trueIsRowSelected(nint) - Returns true if the given row is selected.

键入以选择行Type to Select Row

如果要允许用户在选中表视图的情况下键入字符并选择包含该字符的第一行,请双击 Main.storyboard 文件以将其打开,以便在 Interface Builder 中进行编辑。If you want to allow the user to type a character with the Table View selected and select the first row that has that character, double-click the Main.storyboard file to open it for editing in Interface Builder. 选择接口层次结构中的表视图,并选中 "属性检查器" 中的 "类型选择" 复选框:Select the Table View in the Interface Hierarchy and check the Type Select checkbox in the Attribute Inspector:

保存更改并返回到 Visual Studio for Mac 以与 Xcode 同步。Save your changes and return to Visual Studio for Mac to sync with Xcode.

现在,让我们编辑 ProductTableDelegate.cs 文件,并添加以下方法:Now let's edit the ProductTableDelegate.cs file and add the following method:

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 的行。The GetNextTypeSelectMatch method takes the given searchString and returns the row of the first Product that has that string in it's Title.

如果运行应用程序并键入一个字符,则将选择一行:If we run the application and type a character, a row is selected:

重新排序列Reordering Columns

如果要允许用户在表视图中拖动重新排序列,请双击 Main.storyboard 文件以将其打开,以便在 Interface Builder 中进行编辑。If you want to allow the user to drag reorder columns in the Table View, double-click the Main.storyboard file to open it for editing in Interface Builder. 选择接口层次结构中的表视图,并选中 "属性检查器" 中的 "重新排序" 复选框:Select the Table View in the Interface Hierarchy and check the Reordering checkbox in the Attribute Inspector:

如果我们为 "自动保存" 属性提供一个值,并选中 "列信息" 字段,则对该表的布局所做的任何更改都将自动保存,并在下次运行应用程序时还原。If we give a value for the Autosave property and check the Column Information field, any changes we make to the table's layout will automatically be saved for us and restored the next time the application is run.

保存更改并返回到 Visual Studio for Mac 以与 Xcode 同步。Save your changes and return to Visual Studio for Mac to sync with Xcode.

现在,让我们编辑 ProductTableDelegate.cs 文件,并添加以下方法:Now let's edit the ProductTableDelegate.cs file and add the following method:

public override bool ShouldReorder (NSTableView tableView, nint columnIndex, nint newColumnIndex)
{
  return true;
}

ShouldReorder 方法应为要允许拖放到 newColumnIndex中的任何列返回 true,否则返回 false;The ShouldReorder method should return true for any column that it want to allow to be drag reordered into the newColumnIndex, else return false;

如果运行应用程序,我们可以拖动列标题以对列进行重新排序:If we run the application, we can drag Column Headers around to reorder our columns:

编辑单元格Editing Cells

如果希望允许用户编辑给定单元的值,请编辑 ProductTableDelegate.cs 文件,并按如下所示更改 GetViewForItem 方法:If you want to allow the user to edit the values for a given cell, edit the ProductTableDelegate.cs file and change the GetViewForItem method as follows:

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;
}

现在,如果我们运行应用程序,则用户可以在表视图中编辑单元:Now if we run the application, the user can edit the cells in the Table View:

在表视图中使用图像Using Images in Table Views

若要在 NSTableView 中包括图像作为单元的一部分,您需要更改表视图的 NSTableViewDelegate's GetViewForItem 方法返回数据的方式,以使用 NSTableCellView 而不是典型的 NSTextFieldTo include an image as part of the cell in a NSTableView, you'll need to change how the data is returned by the Table View's NSTableViewDelegate's GetViewForItem method to use a NSTableCellView instead of the typical NSTextField. 例如:For example:

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;
}

有关详细信息,请参阅使用图像文档中的使用映像和表视图部分。For more information, please see the Using Images with Table Views section of our Working with Image documentation.

向行中添加 "删除" 按钮Adding a Delete Button to a Row

根据应用程序的要求,在某些情况下,您可能需要为表中的每一行提供一个操作按钮。Based on the requirements of your app, there might be occasions where you need to supply an action button for each row in the table. 作为此示例的示例,让我们展开上面创建的表视图示例,在每一行上包含一个 "删除" 按钮。As an example of this, let's expand the Table View example created above to include a Delete button on each row.

首先,在 Xcode 的 Interface Builder 中编辑 Main.storyboard,选择表视图并将列数增加到三(3)。First, edit the Main.storyboard in Xcode's Interface Builder, select the Table View and increase the number of columns to three (3). 接下来,将新列的标题更改为 ActionNext, change the Title of the new column to Action:

保存对情节提要所做的更改并返回到 Visual Studio for Mac 以同步更改。Save the changes to the Storyboard and return to Visual Studio for Mac to sync the changes.

接下来,编辑 ViewController.cs 文件并添加以下公共方法:Next, edit the ViewController.cs file and add the following public method:

public void ReloadTable ()
{
  ProductTable.ReloadData ();
}

在同一文件中,按如下所示修改在 ViewDidLoad 方法中创建新的表视图委托:In the same file, modify the creation of the new Table View Delegate inside of ViewDidLoad method as follows:

// Populate the Product Table
ProductTable.DataSource = DataSource;
ProductTable.Delegate = new ProductTableDelegate (this, DataSource);

现在,编辑 ProductTableDelegate.cs 文件,使其包含与视图控制器的专用连接,并在创建新的委托实例时将控制器作为参数:Now, edit the ProductTableDelegate.cs file to include a private connection to the View Controller and to take the controller as a parameter when creating a new instance of the delegate:

#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

接下来,将以下新的私有方法添加到类:Next, add the following new private method to the class:

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 方法中完成的所有文本视图配置都置于一个可调用位置(因为表的最后一列不包含文本视图,而是一个按钮)。This takes all of the Text View configurations that were previously being done in the GetViewForItem method and places them in a single, callable location (since the last column of the table does not include a Text View but a Button).

最后,编辑 GetViewForItem 方法,使其类似于以下内容:Finally, edit the GetViewForItem method and make it look like the following:

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;
}

让我们更详细地了解此代码的多个部分。Let's look at several sections of this code in more detail. 首先,如果正在创建新的 NSTableViewCell,则会基于列名称执行操作。First, if a new NSTableViewCell is being created action is taken based on the name of the Column. 对于前两个列(产品详细信息),将调用新的 ConfigureTextField 方法。For the first two columns (Product and Details), the new ConfigureTextField method is called.

对于 "操作" 列,将创建一个新的 NSButton,并将其作为子视图添加到单元格中:For the Action column, a new NSButton is created and added to the Cell as a Sub View:

// 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 属性用于保存当前正在处理的行的编号。The Button's Tag property is used to hold the number of the Row that is currently being processed. 以后当用户请求在按钮的 Activated 事件中删除某行时,将使用此数字:This number will be used later when the user requests a row to be deleted in the Button's Activated event:

// 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 ();
    }
  });
};

在事件处理程序开始时,将获得给定表行上的按钮和产品。At the start of the event handler, we get the button and the product that is on the given table row. 然后向用户显示警报,确认行删除。Then an Alert is presented to the user confirming the row deletion. 如果用户选择删除该行,则从数据源中删除给定行,并重新加载该表:If the user chooses to delete the row, the given row is removed from the Data Source and the table is reloaded:

// Remove the given row from the dataset
DataSource.Products.RemoveAt((int)btn.Tag);
Controller.ReloadTable ();

最后,如果正在重用表视图单元,而不是创建新的,则以下代码将基于正在处理的列对其进行配置:Finally, if the Table View Cell is being reused instead of being created new, the following code configures it based on the Column being processed:

// 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 属性,使其指向当前行。For the Action column, all Sub Views are scanned until the NSButton is found, then it's Tag property is updated to point at the current Row.

进行这些更改后,在运行应用时,每行都有一个 "删除" 按钮:With these changes in place, when the app is run each row will have a Delete button:

当用户单击 "删除" 按钮时,将显示一个警报,要求他们删除给定行:When the user clicks a Delete button, an alert will be displayed asking them to delete the given Row:

如果用户选择 "删除",则将删除行并重新绘制该表:If the user chooses delete, the row will be removed and the table will be redrawn:

数据绑定表视图Data Binding Table Views

通过在 Xamarin 应用程序中使用键/值编码和数据绑定技术,可以极大地减少需要编写和维护的代码量,以填充和处理 UI 元素。By using Key-Value Coding and Data Binding techniques in your Xamarin.Mac application, you can greatly decrease the amount of code that you have to write and maintain to populate and work with UI elements. 您还可以从您的前端用户界面(模型-视图-控制器)进一步分离您的备份数据(数据模型),从而更易于维护,更灵活的应用程序设计。You also have the benefit of further decoupling your backing data (Data Model) from your front end User Interface (Model-View-Controller), leading to easier to maintain, more flexible application design.

键-值编码(KVC)是一种用于间接访问对象属性的机制,使用键(特殊格式的字符串)来标识属性,而不是通过实例变量或访问器方法(get/set)来访问这些属性。Key-Value Coding (KVC) is a mechanism for accessing an object’s properties indirectly, using keys (specially formatted strings) to identify properties instead of accessing them through instance variables or accessor methods (get/set). 通过在 Xamarin 应用程序中实现符合键值的代码访问器,你可以访问其他 macOS 的功能,例如键-值观察(KVO)、数据绑定、核心数据、Cocoa 绑定和 scriptability。By implementing Key-Value Coding compliant accessors in your Xamarin.Mac application, you gain access to other macOS features such as Key-Value Observing (KVO), Data Binding, Core Data, Cocoa bindings, and scriptability.

有关详细信息,请参阅数据绑定和键/值编码文档中的表视图数据绑定部分。For more information, please see the Table View Data Binding section of our Data Binding and Key-Value Coding documentation.

总结Summary

本文详细介绍了如何在 Xamarin. Mac 应用程序中使用表视图。This article has taken a detailed look at working with Table Views in a Xamarin.Mac application. 我们看到了不同的表视图类型和用法,如何在 Xcode 的 Interface Builder 中创建和维护表视图,以及如何在代码中C#使用表视图。We saw the different types and uses of Table Views, how to create and maintain Table Views in Xcode's Interface Builder and how to work with Table Views in C# code.