共用方式為


Xamarin.Mac 中的表格檢視

本文涵蓋在 Xamarin.Mac 應用程式中使用資料表檢視。 它描述如何在 Xcode 和 Interface Builder 中建立數據表檢視,並在程式代碼中與其互動。

在 Xamarin.Mac 應用程式中使用 C# 和 .NET 時,您可以存取開發人員在 和 XcodeObjective-C運作的相同資料表檢視。 由於 Xamarin.Mac 直接與 Xcode 整合,因此您可以使用 Xcode 的 Interface Builder 來建立和維護您的數據表檢視(或選擇性地直接在 C# 程式代碼中建立它們)。

數據表檢視會以表格式格式顯示數據,其中包含多個數據列中一或多個信息的數據行。 根據所建立的數據表檢視類型,用戶可以依數據行排序、重新組織數據行、加入數據行、移除數據行或編輯數據表中包含的數據。

範例數據表

在本文中,我們將討論在 Xamarin.Mac 應用程式中使用數據表檢視的基本概念。 強烈建議您先完成 Hello,Mac 文章,特別是 Xcode 和 Interface Builder 和 Outlets 和 Actions 簡介小節,因為它涵蓋我們將在本文中使用的重要概念和技術。

您可能也想要查看 Xamarin.Mac Internals 檔的公開 C# 類別/方法Objective-C一節,它也會說明 Register 用來將 C# 類別連接至Objective-C物件和 UI 元素的 和 Export 命令。

數據表檢視簡介

數據表檢視會以表格式格式顯示數據,其中包含多個數據列中一或多個信息的數據行。 數據表檢視會顯示在卷動檢視 (NSScrollView) 內,從 macOS 10.7 開始,您可以使用任何 NSView 而非 Cells (NSCell) 來同時顯示數據列和數據行。 也就是說,您仍然可以使用 NSCell ,您通常會使用子類別 NSTableCellView ,並建立您的自定義數據列和數據行。

數據表檢視不會儲存自己的數據,而是依賴數據源 (NSTableViewDataSource) 視需要提供所需的數據列和數據行。

您可以藉由提供數據表檢視委派的子類別來自定義數據表檢視的行為,NSTableViewDelegate以支援數據表數據行管理、輸入以選取功能、數據列選取和編輯、自定義追蹤,以及個別數據行和數據列的自定義檢視。

建立數據表檢視時,Apple 會建議下列各項:

  • 允許使用者按兩下資料列標頭來排序資料表。
  • 建立名詞或簡短名詞詞組的數據行標頭,以描述該數據行中顯示的數據。

如需詳細資訊,請參閱 Apple OS X Human Interface Guidelines 的內容檢視一節。

在 Xcode 中建立和維護數據表檢視

當您建立新的 Xamarin.Mac Cocoa 應用程式時,預設會取得標準空白視窗。 這個視窗會在項目中自動包含的檔案中定義 .storyboard 。 若要編輯您的 Windows 設計,請在 方案總管 中按兩下Main.storyboard檔案:

選取主分鏡腳本

這會在 Xcode 的 Interface Builder 中開啟視窗設計:

在 Xcode 中編輯 UI

在連結庫偵測器的搜尋方塊中輸入 table ,讓尋找資料表檢視控制項更容易:

從文檔庫選取數據表檢視

將數據表檢視拖曳到 [介面編輯器] 中的 [檢視控制器],使其填滿檢視控制器的內容區域,並將其設定為 [條件約束編輯器] 中的視窗壓縮和成長的位置:

編輯條件約束

選取 [介面階層] 中的 [數據表檢視],屬性偵測器提供下列屬性:

此螢幕快照顯示屬性偵測器中可用的屬性。

  • 內容模式 - 可讓您使用 [檢視] 或NSView [儲存格] 來NSCell顯示資料列和資料行中的數據。 從 macOS 10.7 開始,您應該使用 Views。
  • 浮點數群組數據列 - 如果 true,數據表檢視會繪製群組儲存格,就像是浮動一樣。
  • 數據列 - 定義顯示的數據列數目。
  • 標頭 - 如果true為 ,則數據行會有標頭。
  • 重新排序 - 如果 true,使用者將能夠拖曳重新排序數據表中的數據行。
  • 重設大小 - 如果 true,使用者將能夠拖曳數據行標頭以調整數據行大小。
  • 數據行重設大小 - 控制數據表如何自動調整數據行大小。
  • 醒目提示 - 控制選取儲存格時,醒目提示表格所使用的類型。
  • 替代資料列 - 如果 true為 ,則其他資料列將有不同的背景色彩。
  • 水準方格 - 選取水平儲存格之間繪製的框線類型。
  • 垂直格線 - 選取垂直儲存格之間繪製的框線類型。
  • 網格線色彩 - 設定儲存格框線色彩。
  • 背景 - 設定儲存格背景色彩。
  • 選取 - 可讓您控制使用者如何選取表格中的數據格:
    • Multiple - 如果 true為 ,則使用者可以選取多個數據列和數據行。
    • 數據列 - 如果 true為 ,用戶可以選取資料行。
    • 輸入 Select - 如果 true,使用者可以輸入字元來選取資料列。
    • 空白 - 如果使用者 true不需要選取資料列或數據行,則數據表完全不允許選取。
  • 自動儲存 - 資料表格式的名稱會自動儲存在底下。
  • 數據行資訊 - 如果 true為 ,則會自動儲存資料行的順序和寬度。
  • 換行符 - 選取儲存格如何處理換行符。
  • 截斷最後一個可見行 - 如果 true,數據格將會在數據中截斷,無法容納在數據界限內。

重要

除非您維護舊版 Xamarin.Mac 應用程式, NSView 否則應該使用 NSCell 以數據表檢視為基礎的數據表檢視。 NSCell 會被視為舊版,且未來可能不受支援。

在介面階層選取數據表數據行,屬性偵測器提供下列屬性:

此螢幕快照顯示屬性偵測器中數據表數據行可用的屬性。

  • Title - 設定數據列的標題。
  • 對齊 - 設定儲存格內文字的對齊方式。
  • 標題字型 - 選取儲存格標題文字的字型。
  • 排序索引鍵 - 這是用來排序數據行中數據的索引鍵 。 如果使用者無法排序此資料行,請保留空白。
  • 選取器 - 這是 用來執行排序的動作 。 如果使用者無法排序此資料行,請保留空白。
  • Order - 這是數據行數據的排序順序。
  • 重設大小 - 選取資料行的大小調整類型。
  • 編輯 - 如果 true為 ,用戶可以編輯以儲存格為基礎的表格中的儲存格。
  • Hidden - 如果 true為 ,則會隱藏資料行。

您也可以拖曳資料行的控點(垂直置中於數據行右側)向左或向右來調整數據行的大小。

讓我們選取數據表檢視中的每個數據行,並提供第一個數據行的Product標題和第二欄Details

在介面階層選取數據表資料格檢視 (NSTableViewCell),屬性偵測器提供下列屬性:

此螢幕快照顯示屬性偵測器中數據表數據格檢視可用的屬性。

這些是標準檢視的所有屬性。 您也可以在這裡選擇調整此資料列大小。

在 [介面階層] 中選取數據表檢視儲存格(預設為 aNSTextField),而且屬性偵測器提供下列屬性:

此螢幕快照顯示屬性偵測器中數據表檢視儲存格可用的屬性。

您將擁有標準文字欄位的所有屬性,可在這裡設定。 根據預設,標準文字欄位是用來顯示數據行中儲存格的數據。

在介面階層選取數據表資料格檢視 (NSTableFieldCell),屬性偵測器提供下列屬性:

此螢幕快照顯示屬性偵測器中不同數據表檢視儲存格可用的屬性。

以下是最重要的設定:

  • 版面配置 - 選取此資料列中的數據格配置方式。
  • 使用單行模式 - 如果 true為 ,儲存格會限制為單行。
  • 第一個運行時間版面配置寬度 - 如果 true為 ,單元格會偏好在應用程式第一次執行時設定的寬度(手動或自動)。
  • 動作 - 控制儲存格的編輯 動作 何時傳送。
  • 行為 - 定義儲存格是否可選取或可編輯。
  • RTF - 如果 true為 ,則儲存格可以顯示格式化和樣式的文字。
  • 復原 - 如果true為 ,單元格會承擔復原行為的責任。

選取 [介面階層] 中數據表數據行底部的 [數據表數據格檢視NSTableFieldCell]:[

選取數據表單元格檢視

這可讓您編輯做為指定數據行所建立之所有儲存格的基底 模式 的數據表單元格檢視。

新增動作和輸出

就像任何其他Cocoa UI控件一樣,我們需要公開數據表檢視,而且它是使用Actions和 Outlets 的 C# 程式代碼數據行和儲存格(根據所需的功能)。

對於我們想要公開的任何數據表檢視專案而言,此程式都相同:

  1. 切換至助理 編輯器 ,並確定 ViewController.h 已選取檔案:

    助理編輯器

  2. [介面階層] 選取 [數據表檢視],按兩下控件並拖曳至 ViewController.h 檔案。

  3. 建立 資料表檢視的輸出 ,稱為 ProductTable

    此螢幕快照顯示針對名為 ProductTable 的數據表檢視所建立的輸出連線。

  4. 建立資料表ProductColumn資料列的輸出,也稱為 和 DetailsColumn

    此螢幕快照顯示為其他數據表檢視建立的輸出連線。

  5. 儲存變更並返回 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 負責傳回檢視 (data) 以顯示指定資料行和數據列的數據格。 如果可能的話,如果不是必須建立新的檢視,則會重複使用現有的檢視來顯示單元格。

若要填入數據表,讓我們編輯 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);
}

如果我們執行應用程式,則會顯示下列專案:

此螢幕快照顯示名為 Product Table 的視窗,其中包含三個專案。

依數據行排序

讓我們允許使用者按兩下數據列標頭來排序數據表中的數據。 首先,按兩下 Main.storyboard 檔案以開啟檔案,以在介面產生器中編輯。 選取資料Product行,針對 [排序索引鍵],compare:針對 [選取器] 輸入 Title ,然後選取 Ascending [訂單]:

此螢幕快照顯示介面產生器,您可以在其中設定 Product 資料行的排序索引鍵。

選取資料Details行,針對 [排序索引鍵],compare:針對 [選取器] 輸入 Description ,然後選取 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) - 選取指定的數據列。 傳遞 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 ,並傳回具有該字串的第一個 Product 資料列為 Title

如果我們執行應用程式並輸入字元,則會選取一個資料列:

此螢幕快照顯示執行應用程式的結果。

重新排序數據行

如果您想要允許使用者在 [數據表檢視] 中拖曳重新排序數據行,請 Main.storyboard 按兩下檔案以在介面產生器中編輯。 選取 [介面階層] 中的 [數據表檢視],然後核取 [屬性偵測器] 中的 [重新排序] 複選框:

顯示 [介面產生器] 的螢幕快照,您可以在 [屬性偵測器] 中選取 [重新設定]。

如果我們為 [自動儲存] 屬性提供值並檢查 [資料行資訊] 字段,我們對數據表配置所做的任何變更都會自動儲存給我們,並在下次執行應用程式時還原。

儲存變更並返回 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影像,您必須變更資料表檢視NSTableViewDelegate'sGetViewForItem方法傳回資料的方式,以使用 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)。 接下來,將 新資料列的標題 變更為 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 動作是根據 Column 的名稱而採取的。 針對前兩個數據行(ProductDetails),會呼叫新的 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 屬性用來保存目前正在處理的 Row 數目。 當使用者要求在 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以指向目前的 Row。

有了這些變更,當應用程式執行時,每個數據列都會有 [ 刪除 ] 按鈕:

含有刪除按鈕的數據表檢視

當使用者按下 [ 刪除 ] 按鈕時,會顯示警示,要求他們刪除指定的數據列:

刪除數據列警示

如果使用者選擇刪除,將會移除資料列,並將重新繪製資料表:

刪除數據列之後的數據表

數據系結數據表檢視

藉由在 Xamarin.Mac 應用程式中使用索引鍵/值編碼和數據系結技術,您可以大幅減少您必須撰寫和維護的程式碼數量,以填入和使用 UI 元素。 您也可以從前端使用者介面(Model-View-Controller)進一步分離備份數據(數據模型),進而更輕鬆地維護、更有彈性的應用程式設計。

索引鍵/值編碼 (KVC) 是間接存取物件屬性的機制,使用索引鍵(特別格式化的字串)來識別屬性,而不是透過實例變數或存取子方法存取它們。get/set 藉由在 Xamarin.Mac 應用程式中實作 Key-Value Code 相容存取子,您可以存取其他 macOS 功能,例如 Key-Value Observing (KVO)、數據系結、核心數據、Cocoa 系結和可腳本性。

如需詳細資訊,請參閱數據系結和索引鍵/值編碼文件的數據表檢視數據系結一節。

摘要

本文已詳細探討在 Xamarin.Mac 應用程式中使用資料表檢視。 我們看到數據表檢視的不同類型和用法、如何在 Xcode 的介面產生器中建立和維護數據表檢視,以及如何在 C# 程式代碼中使用數據表檢視。