Xamarin.Mac 中的表格檢視
本文涵蓋在 Xamarin.Mac 應用程式中使用資料表檢視。 它描述如何在 Xcode 和 Interface Builder 中建立數據表檢視,並在程式代碼中與其互動。
在 Xamarin.Mac 應用程式中使用 C# 和 .NET 時,您可以存取開發人員在 和 Xcode 中Objective-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 中開啟視窗設計:
在連結庫偵測器的搜尋方塊中輸入 table
,讓尋找資料表檢視控制項更容易:
將數據表檢視拖曳到 [介面編輯器] 中的 [檢視控制器],使其填滿檢視控制器的內容區域,並將其設定為 [條件約束編輯器] 中的視窗壓縮和成長的位置:
選取 [介面階層] 中的 [數據表檢視],屬性偵測器中提供下列屬性:
- 內容模式 - 可讓您使用 [檢視] 或
NSView
[儲存格] 來NSCell
顯示資料列和資料行中的數據。 從 macOS 10.7 開始,您應該使用 Views。 - 浮點數群組數據列 - 如果
true
,數據表檢視會繪製群組儲存格,就像是浮動一樣。 - 數據列 - 定義顯示的數據列數目。
- 標頭 - 如果
true
為 ,則數據行會有標頭。 - 重新排序 - 如果
true
,使用者將能夠拖曳重新排序數據表中的數據行。 - 重設大小 - 如果
true
,使用者將能夠拖曳數據行標頭以調整數據行大小。 - 數據行重設大小 - 控制數據表如何自動調整數據行大小。
- 醒目提示 - 控制選取儲存格時,醒目提示表格所使用的類型。
- 替代資料列 - 如果
true
為 ,則其他資料列將有不同的背景色彩。 - 水準方格 - 選取水平儲存格之間繪製的框線類型。
- 垂直格線 - 選取垂直儲存格之間繪製的框線類型。
- 網格線色彩 - 設定儲存格框線色彩。
- 背景 - 設定儲存格背景色彩。
- 選取 - 可讓您控制使用者如何選取表格中的數據格:
- Multiple - 如果
true
為 ,則使用者可以選取多個數據列和數據行。 - 數據列 - 如果
true
為 ,用戶可以選取資料行。 - 輸入 Select - 如果
true
,使用者可以輸入字元來選取資料列。 - 空白 - 如果使用者
true
不需要選取資料列或數據行,則數據表完全不允許選取。
- Multiple - 如果
- 自動儲存 - 資料表格式的名稱會自動儲存在底下。
- 數據行資訊 - 如果
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# 程式代碼數據行和儲存格(根據所需的功能)。
對於我們想要公開的任何數據表檢視專案而言,此程式都相同:
切換至助理 編輯器 ,並確定
ViewController.h
已選取檔案:從 [介面階層] 選取 [數據表檢視],按兩下控件並拖曳至
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
負責傳回檢視 (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);
}
如果我們執行應用程式,則會顯示下列專案:
依數據行排序
讓我們允許使用者按兩下數據列標頭來排序數據表中的數據。 首先,按兩下 Main.storyboard
檔案以開啟檔案,以在介面產生器中編輯。 選取資料Product
行,針對 [排序索引鍵],compare:
針對 [選取器] 輸入 Title
,然後選取 Ascending
[訂單]:
選取資料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'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 的介面產生器中編輯 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 的名稱而採取的。 針對前兩個數據行(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
屬性用來保存目前正在處理的 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# 程式代碼中使用數據表檢視。