Xamarin.Mac 中的数据绑定和键值编码
本文介绍如何使用键值编码和键值观察来实现将数据绑定到 Xcode Interface Builder 中的 UI 元素。
概述
在 Xamarin.Mac 应用程序中使用 C# 和 .NET 时,可以使用 Objective-C 和 Xcode 开发人员所用的相同键值编码和数据绑定技术。 由于 Xamarin.Mac 直接与 Xcode 集成,因此你可以使用 Xcode 的 Interface Builder 来与 UI 元素进行数据绑定,而无需编写代码。
通过在 Xamarin.Mac 应用程序中使用键值编码和数据绑定技术,可大大减少为填充和使用 UI 元素而必须编写和维护的代码量。 还可以从前端用户界面(模型-视图-控制器)进一步分离支持数据(数据模型),从而更轻松地维护、更灵活的应用程序设计。
在本文中,我们将介绍在 Xamarin.Mac 应用程序中进行键值编码和数据绑定的基础知识。 强烈建议先阅读 Hello, Mac 一文,特别是 Xcode 和 Interface Builder 简介和输出口和操作部分,因为其中介绍了我们将在本文中使用的关键概念和技术。
你可能还需要查看 Xamarin.Mac 内部机制文档的向 Objective-C 公开 C# 类/方法部分,其中介绍了用于将 C# 类连接到 Objective-C 对象和 UI 元素的 Register
和 Export
特性。
什么是键值编码
键值编码 (KVC) 是间接访问对象属性的机制,使用键(特殊格式的字符串)来标识属性,而不是通过实例变量或访问器方法 (get/set
) 访问它们。 通过在 Xamarin.Mac 应用程序中实现符合键值编码的访问器,可以访问其他 macOS(以前称为 OS X)功能,例如键值观察 (KVO)、数据绑定、核心数据、Cocoa 绑定和可脚本性。
通过在 Xamarin.Mac 应用程序中使用键值编码和数据绑定技术,可大大减少为填充和使用 UI 元素而必须编写和维护的代码量。 还可以从前端用户界面(模型-视图-控制器)进一步分离支持数据(数据模型),从而更轻松地维护、更灵活的应用程序设计。
例如,让我们看看以下 KVC 兼容对象的类定义:
using System;
using Foundation;
namespace MacDatabinding
{
[Register("PersonModel")]
public class PersonModel : NSObject
{
private string _name = "";
[Export("Name")]
public string Name {
get { return _name; }
set {
WillChangeValue ("Name");
_name = value;
DidChangeValue ("Name");
}
}
public PersonModel ()
{
}
}
}
首先,[Register("PersonModel")]
属性注册该类并将其公开给 Objective-C。 然后,该类需要从 NSObject
(或从 NSObject
继承的子类)继承,这会添加几个允许该类兼容 KVC 的基方法。 接下来,[Export("Name")]
属性公开 Name
属性并定义稍后将用于通过 KVC 和 KVO 技术访问该属性的键值。
最后,为了能够通过键值观察属性值的更改,访问器必须将对其值的更改包装在 WillChangeValue
和 DidChangeValue
方法调用中(指定与 Export
属性相同的键)。 例如:
set {
WillChangeValue ("Name");
_name = value;
DidChangeValue ("Name");
}
此步骤对于 Xcode Interface Builder 中的数据绑定非常重要(本文稍后将予介绍)。
有关详细信息,请参阅 Apple 的键值编码编程指南。
键和键路径
键是标识对象特定属性的字符串。 通常,键对应于键值编码兼容对象中的访问器方法名称。 键必须使用 ASCII 编码,通常以小写字母开头,并且不能包含空格。 因此,在上面的示例中,Name
将是 PersonModel
类的 Name
属性的键值。 它们公开的密钥和属性名称不必相同,但在大多数情况下是相同的。
键路径是点分隔的键的字符串,用于指定要遍历的对象属性的层次结构。 序列中第一个键的属性相对于接收方,每个后续键都是相对于前一个属性的值进行评估的。 同样,你可以使用点表示法来遍历 C# 类中的对象及其属性。
例如,如果扩展了 PersonModel
类并添加了 Child
属性:
using System;
using Foundation;
namespace MacDatabinding
{
[Register("PersonModel")]
public class PersonModel : NSObject
{
private string _name = "";
private PersonModel _child = new PersonModel();
[Export("Name")]
public string Name {
get { return _name; }
set {
WillChangeValue ("Name");
_name = value;
DidChangeValue ("Name");
}
}
[Export("Child")]
public PersonModel Child {
get { return _child; }
set {
WillChangeValue ("Child");
_child = value;
DidChangeValue ("Child");
}
}
public PersonModel ()
{
}
}
}
子项名称的键路径可以是 self.Child.Name
或简单的 Child.Name
(取决于键值的使用方式)。
使用键值编码获取值
ValueForKey
方法返回指定的键的值(作为 NSString
),该值相对于接收请求的 KVC 类的实例。 例如,如果 Person
是上面定义的 PersonModel
类的实例:
// Read value
var name = Person.ValueForKey (new NSString("Name"));
这会返回该 PersonModel
实例的 Name
属性值。
使用键值编码设置值
类似地,SetValueForKey
设置指定的键的值(作为 NSString
),该值相对于接收请求的 KVC 类的实例。 再次使用 PersonModel
类的实例,如下所示:
// Write value
Person.SetValueForKey(new NSString("Jane Doe"), new NSString("Name"));
会将 Name
属性的值更改为 Jane Doe
。
观察值的变化
使用键值观察 (KVO),可以将观察器附加到 KVC 兼容类的特定键,并在该键的值发生修改时收到通知(使用 KVC 技术或直接访问 C# 代码中的给定属性)。 例如:
// Watch for the name value changing
Person.AddObserver ("Name", NSKeyValueObservingOptions.New, (sender) => {
// Inform caller of selection change
Console.WriteLine("New Name: {0}", Person.Name)
});
现在,只要修改 PersonModel
类的 Person
实例的 Name
属性,新值就会写出到控制台。
有关详细信息,请参阅 Apple 的键值观察编程简介指南。
数据绑定
以下部分将介绍如何使用键值编码和键值观察兼容类将数据绑定到 Xcode Interface Builder 中的 UI 元素,而不是使用 C# 代码读取和写入值。 这样,就可以将数据模型与用于显示它们的视图分开,使 Xamarin.Mac 应用程序更加灵活且更易于维护。 此外,还可以大大减少需要编写的代码量。
定义数据模型
在 Interface Builder 中对 UI 元素进行数据绑定之前,必须在 Xamarin.Mac 应用程序中定义一个 KVC/KVO 兼容的类,以充当绑定的数据模型。 数据模型提供将在用户界面中显示的所有数据,并接收用户在运行应用程序时在 UI 中所做的任何修改。
例如,如果你正在编写一个管理一组员工的应用程序,可以使用以下类来定义数据模型:
using System;
using Foundation;
using AppKit;
namespace MacDatabinding
{
[Register("PersonModel")]
public class PersonModel : NSObject
{
#region Private Variables
private string _name = "";
private string _occupation = "";
private bool _isManager = false;
private NSMutableArray _people = new NSMutableArray();
#endregion
#region Computed Properties
[Export("Name")]
public string Name {
get { return _name; }
set {
WillChangeValue ("Name");
_name = value;
DidChangeValue ("Name");
}
}
[Export("Occupation")]
public string Occupation {
get { return _occupation; }
set {
WillChangeValue ("Occupation");
_occupation = value;
DidChangeValue ("Occupation");
}
}
[Export("isManager")]
public bool isManager {
get { return _isManager; }
set {
WillChangeValue ("isManager");
WillChangeValue ("Icon");
_isManager = value;
DidChangeValue ("isManager");
DidChangeValue ("Icon");
}
}
[Export("isEmployee")]
public bool isEmployee {
get { return (NumberOfEmployees == 0); }
}
[Export("Icon")]
public NSImage Icon {
get {
if (isManager) {
return NSImage.ImageNamed ("group.png");
} else {
return NSImage.ImageNamed ("user.png");
}
}
}
[Export("personModelArray")]
public NSArray People {
get { return _people; }
}
[Export("NumberOfEmployees")]
public nint NumberOfEmployees {
get { return (nint)_people.Count; }
}
#endregion
#region Constructors
public PersonModel ()
{
}
public PersonModel (string name, string occupation)
{
// Initialize
this.Name = name;
this.Occupation = occupation;
}
public PersonModel (string name, string occupation, bool manager)
{
// Initialize
this.Name = name;
this.Occupation = occupation;
this.isManager = manager;
}
#endregion
#region Array Controller Methods
[Export("addObject:")]
public void AddPerson(PersonModel person) {
WillChangeValue ("personModelArray");
isManager = true;
_people.Add (person);
DidChangeValue ("personModelArray");
}
[Export("insertObject:inPersonModelArrayAtIndex:")]
public void InsertPerson(PersonModel person, nint index) {
WillChangeValue ("personModelArray");
_people.Insert (person, index);
DidChangeValue ("personModelArray");
}
[Export("removeObjectFromPersonModelArrayAtIndex:")]
public void RemovePerson(nint index) {
WillChangeValue ("personModelArray");
_people.RemoveObject (index);
DidChangeValue ("personModelArray");
}
[Export("setPersonModelArray:")]
public void SetPeople(NSMutableArray array) {
WillChangeValue ("personModelArray");
_people = array;
DidChangeValue ("personModelArray");
}
#endregion
}
}
前面的什么是键值编码部分介绍了此类的大部分功能。 不过,让我们看看一些特定的元素和一些补充内容,它们使此类能够充当数组控制器和树控制器(稍后将用于对树视图、大纲视图和集合视图进行数据绑定)的数据模型。
首先,由于员工可能是经理,因此我们使用了 NSArray
(具体而言,使用的是 NSMutableArray
,以便可以修改值)来允许他们管理的员工附加到他们:
private NSMutableArray _people = new NSMutableArray();
...
[Export("personModelArray")]
public NSArray People {
get { return _people; }
}
此处需要注意两点:
- 我们使用了
NSMutableArray
而不是标准 C# 数组或集合,因为这是数据绑定到 AppKit 控件(例如表视图、大纲视图和集合)的要求。 - 我们通过将员工数组强制转换为
NSArray
来实现数据绑定,并将其 C# 格式名称People
更改为数据绑定所需的名称,personModelArray
格式为 {class_name}Array(请注意,第一个字符已设为小写)。
接下来,我们需要添加一些特殊名称的公共方法来支持数组控制器和树控制器:
[Export("addObject:")]
public void AddPerson(PersonModel person) {
WillChangeValue ("personModelArray");
isManager = true;
_people.Add (person);
DidChangeValue ("personModelArray");
}
[Export("insertObject:inPersonModelArrayAtIndex:")]
public void InsertPerson(PersonModel person, nint index) {
WillChangeValue ("personModelArray");
_people.Insert (person, index);
DidChangeValue ("personModelArray");
}
[Export("removeObjectFromPersonModelArrayAtIndex:")]
public void RemovePerson(nint index) {
WillChangeValue ("personModelArray");
_people.RemoveObject (index);
DidChangeValue ("personModelArray");
}
[Export("setPersonModelArray:")]
public void SetPeople(NSMutableArray array) {
WillChangeValue ("personModelArray");
_people = array;
DidChangeValue ("personModelArray");
}
这些方法允许控制器请求和修改它们显示的数据。 与上面公开的 NSArray
类似,它们具有非常具体的命名约定(与典型的 C# 命名约定不同):
addObject:
- 将对象添加到数组中。insertObject:in{class_name}ArrayAtIndex:
- 其中{class_name}
是类的名称。 此方法将一个对象插入到数组中给定的索引处。removeObjectFrom{class_name}ArrayAtIndex:
- 其中{class_name}
是类的名称。 此方法删除数组中给定索引处的对象。set{class_name}Array:
- 其中{class_name}
是类的名称。 此方法允许将现有载体替换为新载体。
在这些方法内部,我们将对数组的更改包装在 WillChangeValue
和 DidChangeValue
消息中,以实现 KVO 合规性。
最后,由于 Icon
属性依赖于 isManager
属性的值,因此对 isManager
属性的更改可能不会反映在数据绑定 UI 元素的 Icon
中(在 KVO 期间):
[Export("Icon")]
public NSImage Icon {
get {
if (isManager) {
return NSImage.ImageNamed ("group.png");
} else {
return NSImage.ImageNamed ("user.png");
}
}
}
为了纠正此问题,我们使用了以下代码:
[Export("isManager")]
public bool isManager {
get { return _isManager; }
set {
WillChangeValue ("isManager");
WillChangeValue ("Icon");
_isManager = value;
DidChangeValue ("isManager");
DidChangeValue ("Icon");
}
}
请注意,除了其自身的键之外,isManager
访问器还会发送 Icon
键的 WillChangeValue
和 DidChangeValue
消息,因此它也会看到更改。
我们将在本文的其余部分使用 PersonModel
数据模型。
简单数据绑定
定义数据模型后,让我们看看 Xcode 的 Interface Builder 中数据绑定的简单示例。 例如,让我们向 Xamarin.Mac 应用程序添加一个表单,该表单可用于编辑上面定义的 PersonModel
。 我们将添加一些文本字段和一个复选框来显示和编辑模型的属性。
首先,我们将一个新的视图控制器添加到 Interface Builder 中的 Main.storyboard 文件中,并将其类命名为 SimpleViewController
:
接下来,返回到 Visual Studio for Mac,编辑 SimpleViewController.cs 文件(自动添加到我们的项目中)并公开我们将表单数据绑定到的 PersonModel
实例。 添加以下代码:
private PersonModel _person = new PersonModel();
...
[Export("Person")]
public PersonModel Person {
get {return _person; }
set {
WillChangeValue ("Person");
_person = value;
DidChangeValue ("Person");
}
}
接下来,加载视图后,我们创建 PersonModel
的实例并使用以下代码来填充它:
public override void ViewDidLoad ()
{
base.AwakeFromNib ();
// Set a default person
var Craig = new PersonModel ("Craig Dunn", "Documentation Manager");
Craig.AddPerson (new PersonModel ("Amy Burns", "Technical Writer"));
Craig.AddPerson (new PersonModel ("Joel Martinez", "Web & Infrastructure"));
Craig.AddPerson (new PersonModel ("Kevin Mullins", "Technical Writer"));
Craig.AddPerson (new PersonModel ("Mark McLemore", "Technical Writer"));
Craig.AddPerson (new PersonModel ("Tom Opgenorth", "Technical Writer"));
Person = Craig;
}
现在我们需要创建表单:双击 Main.storyboard 文件将其打开,以便在 Interface Builder 中进行编辑。 如下所示布局表单:
若要将表单数据绑定到我们通过 Person
键公开的 PersonModel
,请执行以下操作:
选择“员工姓名”文本字段并切换到“绑定检查器”。
选中“绑定到”框并从下拉列表中选择“简单视图控制器”。 接下来,为“键路径”输入
self.Person.Name
:选择“职位”文本字段,选中“绑定到”框,然后从下拉列表中选择“简单视图控制器”。 接下来,为“键路径”输入
self.Person.Occupation
:选中“员工是经理”复选框,选中“绑定到”框,然后从下拉列表中选择“简单视图控制器”。 接下来,为“键路径”输入
self.Person.isManager
:选择“管理的员工数量”文本字段,选中“绑定到”框,然后从下拉列表中选择“简单视图控制器”。 接下来,为“键路径”输入
self.Person.NumberOfEmployees
:如果员工不是经理,则需要隐藏“管理的员工数量”标签和文本字段。
选择“管理的员工数量”标签,展开“隐藏”下箭头图标,选中“绑定到”框,然后从下拉列表中选择“简单视图控制器”。 接下来,为“键路径”输入
self.Person.isManager
:从“值转换器”下拉列表中选择
NSNegateBoolean
:这会告知数据绑定,如果
isManager
属性的值为false
,则会隐藏该标签。针对“管理的员工数量”文本字段重复步骤 7 和 8。
保存更改并返回到 Visual Studio for Mac 以与 Xcode 同步。
如果你运行该应用程序,Person
属性中的值将自动填充我们的表单:
用户对表单所做的任何更改都将写回视图控制器中的 Person
属性。 例如,取消选择“员工是经理”会更新 PersonModel
的 Person
实例,并且“管理的员工数量”标签和文本字段会自动隐藏(通过数据绑定):
表视图数据绑定
现在我们已经掌握了数据绑定的基础知识,接下来我们通过使用数组控制器和将数据绑定到表视图来了解更复杂的数据绑定任务。 有关使用表视图的详细信息,请参阅表视图文档。
首先,我们将一个新的视图控制器添加到 Interface Builder 中的 Main.storyboard 文件中,并将其类命名为 TableViewController
:
接下来,编辑 TableViewController.cs 文件(该文件已自动添加到项目中)并公开 PersonModel
类的数组 (NSArray
),我们要将表单数据绑定到该类。 添加以下代码:
private NSMutableArray _people = new NSMutableArray();
...
[Export("personModelArray")]
public NSArray People {
get { return _people; }
}
...
[Export("addObject:")]
public void AddPerson(PersonModel person) {
WillChangeValue ("personModelArray");
_people.Add (person);
DidChangeValue ("personModelArray");
}
[Export("insertObject:inPersonModelArrayAtIndex:")]
public void InsertPerson(PersonModel person, nint index) {
WillChangeValue ("personModelArray");
_people.Insert (person, index);
DidChangeValue ("personModelArray");
}
[Export("removeObjectFromPersonModelArrayAtIndex:")]
public void RemovePerson(nint index) {
WillChangeValue ("personModelArray");
_people.RemoveObject (index);
DidChangeValue ("personModelArray");
}
[Export("setPersonModelArray:")]
public void SetPeople(NSMutableArray array) {
WillChangeValue ("personModelArray");
_people = array;
DidChangeValue ("personModelArray");
}
与在定义数据模型部分对 PersonModel
类所做的那样,我们公开了四个专门命名的公共方法,以便数组控制器从 PersonModels
集合中读取和写入数据。
接下来,加载视图后,我们需要使用以下代码填充数组:
public override void AwakeFromNib ()
{
base.AwakeFromNib ();
// Build list of employees
AddPerson (new PersonModel ("Craig Dunn", "Documentation Manager", true));
AddPerson (new PersonModel ("Amy Burns", "Technical Writer"));
AddPerson (new PersonModel ("Joel Martinez", "Web & Infrastructure"));
AddPerson (new PersonModel ("Kevin Mullins", "Technical Writer"));
AddPerson (new PersonModel ("Mark McLemore", "Technical Writer"));
AddPerson (new PersonModel ("Tom Opgenorth", "Technical Writer"));
AddPerson (new PersonModel ("Larry O'Brien", "API Documentation Manager", true));
AddPerson (new PersonModel ("Mike Norman", "API Documenter"));
}
现在我们需要创建表视图:双击 Main.storyboard 文件将其打开,以便在 Interface Builder 中进行编辑。 如下所示设置表的布局:
我们需要添加一个数组控制器来向表提供绑定数据,因此请执行以下操作:
将一个数组控制器从“库检查器”拖放到“界面编辑器”上:
在“界面层次结构”中选择“数组控制器”,然后切换到“属性检查器”:
输入
PersonModel
作为类名,单击“加号”按钮并添加三个键。 将它们分别命名为Name
、Occupation
和isManager
:这会告知数组控制器它正在管理哪个数组,以及它应该公开哪些属性(通过键)。
切换到“绑定检查器”,然后在“内容数组”下选择“绑定到”和“表视图控制器”。 输入
self.personModelArray
的模型键路径:这会将数组控制器绑定到我们在视图控制器上公开的
PersonModels
数组。
现在我们需要将表视图绑定到数组控制器,因此请执行以下操作:
选择“表视图”和“绑定检查器”:
在“表内容”下箭头图标下,选择“绑定到”和“数组控制器”。 在“控制器键”字段中输入
arrangedObjects
:选择“员工”列下的“表视图单元格”。 在“绑定检查器”中的“值”下箭头图标下,选择“绑定到”和“表单元格视图”。 为“模型键路径”输入
objectValue.Name
:objectValue
是数组控制器管理的数组中的当前PersonModel
。选择“职位”列下的“表视图单元格”。 在“绑定检查器”中的“值”下箭头图标下,选择“绑定到”和“表单元格视图”。 为“模型键路径”输入
objectValue.Occupation
:保存更改并返回到 Visual Studio for Mac 以与 Xcode 同步。
如果我们运行该应用程序,该表中将填充 PersonModels
的数组:
大纲视图数据绑定
针对大纲视图的数据绑定与针对表视图的绑定非常相似。 主要差别在于,我们将使用树控制器而不是数组控制器来向大纲视图提供绑定数据。 有关使用大纲视图的详细信息,请参阅我们的大纲视图文档。
首先,我们将一个新的视图控制器添加到 Interface Builder 中的 Main.storyboard 文件中,并将其类命名为 OutlineViewController
:
接下来,编辑 OutlineViewController.cs 文件(该文件已自动添加到项目中)并公开 PersonModel
类的数组 (NSArray
),我们要将表单数据绑定到该类。 添加以下代码:
private NSMutableArray _people = new NSMutableArray();
...
[Export("personModelArray")]
public NSArray People {
get { return _people; }
}
...
[Export("addObject:")]
public void AddPerson(PersonModel person) {
WillChangeValue ("personModelArray");
_people.Add (person);
DidChangeValue ("personModelArray");
}
[Export("insertObject:inPersonModelArrayAtIndex:")]
public void InsertPerson(PersonModel person, nint index) {
WillChangeValue ("personModelArray");
_people.Insert (person, index);
DidChangeValue ("personModelArray");
}
[Export("removeObjectFromPersonModelArrayAtIndex:")]
public void RemovePerson(nint index) {
WillChangeValue ("personModelArray");
_people.RemoveObject (index);
DidChangeValue ("personModelArray");
}
[Export("setPersonModelArray:")]
public void SetPeople(NSMutableArray array) {
WillChangeValue ("personModelArray");
_people = array;
DidChangeValue ("personModelArray");
}
与在定义数据模型部分对 PersonModel
类所做的那样,我们公开了四个专门命名的公共方法,以便树控制器从 PersonModels
集合中读取和写入数据。
接下来,加载视图后,我们需要使用以下代码填充数组:
public override void AwakeFromNib ()
{
base.AwakeFromNib ();
// Build list of employees
var Craig = new PersonModel ("Craig Dunn", "Documentation Manager");
Craig.AddPerson (new PersonModel ("Amy Burns", "Technical Writer"));
Craig.AddPerson (new PersonModel ("Joel Martinez", "Web & Infrastructure"));
Craig.AddPerson (new PersonModel ("Kevin Mullins", "Technical Writer"));
Craig.AddPerson (new PersonModel ("Mark McLemore", "Technical Writer"));
Craig.AddPerson (new PersonModel ("Tom Opgenorth", "Technical Writer"));
AddPerson (Craig);
var Larry = new PersonModel ("Larry O'Brien", "API Documentation Manager");
Larry.AddPerson (new PersonModel ("Mike Norman", "API Documenter"));
AddPerson (Larry);
}
现在我们需要创建大纲视图:双击 Main.storyboard 文件将其打开,以便在 Interface Builder 中进行编辑。 如下所示设置表的布局:
我们需要添加一个树控制器来为大纲提供绑定数据,因此请执行以下操作:
将一个树控制器从“库检查器”拖放到“界面编辑器”上:
在“界面层次结构”中选择“树控制器”,然后切换到“属性检查器”:
输入
PersonModel
作为类名,单击“加号”按钮并添加三个键。 将它们分别命名为Name
、Occupation
和isManager
:这会告知树控制器它正在管理哪个数组,以及它应该公开哪些属性(通过键)。
在“树控制器”部分下,为“子项”输入
personModelArray
,在“计数”下输入NumberOfEmployees
,在“叶”下输入isEmployee
:这会告知树控制器在何处可以找到任何子节点,有多少个子节点,以及当前节点是否包含子节点。
切换到“绑定检查器”,然后在“内容数组”下选择“绑定到”和“文件所有者”。 输入
self.personModelArray
的模型键路径:这会将树控制器绑定到我们在视图控制器上公开的
PersonModels
数组。
现在我们需要将大纲视图绑定到树控制器,因此请执行以下操作:
选择“大纲视图”,然后在“绑定检查器”中选择:
在“大纲视图内容”下箭头图标下,选择“绑定到”和“树控制器”。 在“控制器键”字段中输入
arrangedObjects
:选择“员工”列下的“表视图单元格”。 在“绑定检查器”中的“值”下箭头图标下,选择“绑定到”和“表单元格视图”。 为“模型键路径”输入
objectValue.Name
:objectValue
是树控制器管理的数组中的当前PersonModel
。选择“职位”列下的“表视图单元格”。 在“绑定检查器”中的“值”下箭头图标下,选择“绑定到”和“表单元格视图”。 为“模型键路径”输入
objectValue.Occupation
:保存更改并返回到 Visual Studio for Mac 以与 Xcode 同步。
如果我们运行该应用程序,该大纲中将填充 PersonModels
的数组:
集合视图数据绑定
对集合视图进行数据绑定与绑定表视图的操作非常类似,因为数组控制器用于为集合提供数据。 由于集合视图没有预设的显示格式,因此需要执行更多操作来提供用户交互反馈并跟踪用户选择。
重要
由于 Xcode 7 和 macOS 10.11(及更高版本)中存在的一个问题,集合视图无法在情节提要 (.storyboard) 文件内使用。 因此,需要继续使用 .xib 文件来定义 Xamarin.Mac 应用的集合视图。 有关详细信息,请参阅我们的集合视图文档。
调试本机崩溃
数据绑定中出错可能会导致非托管代码发生本机崩溃,并导致 Xamarin.Mac 应用程序完全失败并出现 SIGABRT
错误:
数据绑定期间发生本机崩溃通常有四个主要原因:
- 数据模型不是继承自
NSObject
或NSObject
的子类。 - 未使用
[Export("key-name")]
特性向 Objective-C 公开属性。 - 未在
WillChangeValue
和DidChangeValue
方法调用中包装对访问器值的更改(指定与Export
特性相同的键)。 - Interface Builder 中的“绑定检查器”中的键错误或键入有误。
解码崩溃
让我们在数据绑定中引发本机崩溃,以便演示如何查找和修复这种问题。 在 Interface Builder 中,我们将“集合视图”示例中第一个标签的绑定从 Name
更改为 Title
:
保存更改,切换回 Visual Studio for Mac 以便与 Xcode 同步,并运行我们的应用程序。 显示集合视图时,应用程序会立即崩溃并出现 SIGABRT
错误(如 Visual Studio for Mac 中的“应用程序输出”所示),因为 PersonModel
未公开包含键 Title
的属性:
如果滚动到“应用程序输出”中显示的错误的最顶部,我们可以看到解决问题的关键所在:
此行告诉我们,要绑定到的对象中不存在 Title
键。 如果我们在 Interface Builder 中将绑定更改回 Name
,然后保存、同步、重新生成并运行,则应用程序将按预期运行,而不会出现任何问题。
总结
本文详细介绍了如何在 Xamarin.Mac 应用程序中进行数据绑定和键值绑定编码。 首先,它介绍了如何使用键值编码 (KVC) 和键值观察 (KVO) 向 Objective-C 公开 C# 类。 接下来,它演示了如何使用符合 KVO 的类和数据将其绑定到 Xcode Interface Builder 中的 UI 元素。 最后,演示了使用数组控制器和树控制器的复杂数据绑定。