第十一章:可绑定的基础结构(一)
C#最基本的语言结构之一是称为属性的类成员。 在我们第一次遇到C#的时候,我们所有人都学会了定义属性的一般例程。 该属性通常由私有字段支持,并包含引用pri?vate字段的set和get访问器,并使用新值执行某些操作:
{
double quality;
public double Quality
{
set
{
quality = value;
// Do something with the new value
}
get
{
return quality;
}
}
}
属性有时被称为智能字段。从语法上讲,访问属性的代码重新组合访问字段的代码。然而,当访问prop?erty时,属性可以执行一些自己的代码。
属性也类似于方法。实际上,C#代码被编译成中间语言,用一对名为set_Quality和get_Qual?ity的方法来实现诸如Quality之类的属性。然而,尽管属性和一对set和get方法之间具有密切的功能相似性,但是当从代码移动到标记时,属性语法显示它更适合。很难想象XAML是在缺少属性的底层API上构建的。
因此,您可能会惊讶地发现Xamarin.Forms实现了一个基于C#属性构建的增强属性定义。或者也许你不会感到惊讶。如果您已经拥有Microsoft基于XAML的平台的经验,那么您将在本章中遇到一些熟悉的概念。
上面显示的属性定义称为CLR属性,因为.NET公共语言运行库支持它。 Xamarin.Forms中的增强属性定义构建在CLR属性之上,称为可绑定属性,由BindableProperty类封装并由BindableObject类提供。
Xamarin.Forms类层次结构
在探索重要的BindableObject类的细节之前,让我们首先通过构造类层次结构来发现Binda?bleObject如何适应整个Xamarin.Forms体系结构。
在面向对象的编程框架(如Xamarin.Forms)中,类层次结构通常可以揭示环境的重要内部结构。类层次结构显示了各个类如何相互关联以及它们共享的属性,方法和事件,包括如何支持可绑定属性。
您可以通过艰难地浏览在线文档并记录从其他类派生的类来构建这样的类层次结构。或者您可以编写一个Xamarin.Forms专业版来为您完成工作并在手机上显示类层次结构。这样的程序利用.NET反射来获取Xama?rin.Forms.Core和Xamarin.Forms.Xaml程序集中的所有公共类,结构和枚举,并将它们排列在树中。 ClassHierarchy
应用演示了这种技术。
像往常一样,ClassHierarchy项目包含一个派生自ContentPage的类,名为ClassHierarchyPage,但它还包含另外两个类,名为TypeInformation和ClassAndSubclasses。
该程序为Xamarin.Forms.Core和Xamarin.Forms.Xaml程序集中的每个公共类(以及结构和枚举)创建一个TypeInformation实例,以及任何作为任何Xamarin.Forms类的基类的.NET类。 ,Object除外。 (这些.NET类是Attribute,Delegate,Enum,EventArgs,Exception,MulticastDelegate和ValueType。)TypeInformation构造函数需要一个标识类型的Type对象,但也获取一些其他信息:
class TypeInformation
{
bool isBaseGenericType;
Type baseGenericTypeDef;
public TypeInformation(Type type, bool isXamarinForms)
{
Type = type;
IsXamarinForms = isXamarinForms;
TypeInfo typeInfo = type.GetTypeInfo();
BaseType = typeInfo.BaseType;
if (BaseType != null)
{
TypeInfo baseTypeInfo = BaseType.GetTypeInfo();
isBaseGenericType = baseTypeInfo.IsGenericType;
if (isBaseGenericType)
{
baseGenericTypeDef = baseTypeInfo.GetGenericTypeDefinition();
}
}
}
public Type Type { private set; get; }
public Type BaseType { private set; get; }
public bool IsXamarinForms { private set; get; }
public bool IsDerivedDirectlyFrom(Type parentType)
{
if (BaseType != null && isBaseGenericType)
{
if (baseGenericTypeDef == parentType)
{
return true;
}
}
else if (BaseType == parentType)
{
return true;
}
return false;
}
}
此类的一个非常重要的部分是IsDerivedDirectlyFrom方法,如果传递的是此类型的基类型参数,则返回true。 如果涉及泛型类,则此确定很复杂,并且该问题在很大程度上解释了类的复杂性。
ClassAndSubclasses类相当短:
class ClassAndSubclasses
{
public ClassAndSubclasses(Type parent, bool isXamarinForms)
{
Type = parent;
IsXamarinForms = isXamarinForms;
Subclasses = new List<ClassAndSubclasses>();
}
public Type Type { private set; get; }
public bool IsXamarinForms { private set; get; }
public List<ClassAndSubclasses> Subclasses { private set; get; }
}
程序为类层次结构中显示的每个Type创建此类的一个实例,包括Object,因此程序创建的ClassAndSubclasses实例多于TypeInformation实例的数量。 与Object关联的ClassAndSubclasses实例包含直接从Object派生的所有类的集合,并且每个ClassAndSubclasses实例包含从该派生派生的所有类的集合,依此类推,用于层次结构树的剩余文件。
ClassHierarchyPage类由XAML文件和代码隐藏文件组成,但XAML文件只包含一些可滚动的StackLayout,可用于某些Label元素:
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="ClassHierarchy.ClassHierarchyPage">
<ContentPage.Padding>
<OnPlatform x:TypeArguments="Thickness"
iOS="5, 20, 0, 0"
Android="5, 0, 0, 0"
WinPhone="5, 0, 0, 0" />
</ContentPage.Padding>
<ScrollView>
<StackLayout x:Name="stackLayout"
Spacing="0" />
</ScrollView>
</ContentPage>
代码隐藏文件获取对两个Xamarin.Forms程序集对象的引用,然后在classList集合中累积所有公共类,结构和枚举。 然后它检查是否有必要从.NET程序集中包含任何基类,对结果进行排序,然后调用两个递归方法AddChildrenToParent和AddItemToStackLayout:
public partial class ClassHierarchyPage : ContentPage
{
public ClassHierarchyPage()
{
InitializeComponent();
List<TypeInformation> classList = new List<TypeInformation>();
// Get types in Xamarin.Forms.Core assembly.
GetPublicTypes(typeof(View).GetTypeInfo().Assembly, classList);
// Get types in Xamarin.Forms.Xaml assembly.
GetPublicTypes(typeof(Extensions).GetTypeInfo().Assembly, classList);
// Ensure that all classes have a base type in the list.
// (i.e., add Attribute, ValueType, Enum, EventArgs, etc.)
int index = 0;
// Watch out! Loops through expanding classList!
do
{
// Get a child type from the list.
TypeInformation childType = classList[index];
if (childType.Type != typeof(Object))
{
bool hasBaseType = false;
// Loop through the list looking for a base type.
foreach (TypeInformation parentType in classList)
{
if (childType.IsDerivedDirectlyFrom(parentType.Type))
{
hasBaseType = true;
}
}
// If there's no base type, add it.
if (!hasBaseType && childType.BaseType != typeof(Object))
{
classList.Add(new TypeInformation(childType.BaseType, false));
}
}
index++;
}
while (index < classList.Count);
// Now sort the list.
classList.Sort((t1, t2) =>
{
return String.Compare(t1.Type.Name, t2.Type.Name);
});
// Start the display with System.Object.
ClassAndSubclasses rootClass = new ClassAndSubclasses(typeof(Object), false);
// Recursive method to build the hierarchy tree.
AddChildrenToParent(rootClass, classList);
// Recursive method for adding items to StackLayout.
AddItemToStackLayout(rootClass, 0);
}
void GetPublicTypes(Assembly assembly,
List<TypeInformation> classList)
{
// Loop through all the types.
foreach (Type type in assembly.ExportedTypes)
{
TypeInfo typeInfo = type.GetTypeInfo();
// Public types only but exclude interfaces.
if (typeInfo.IsPublic && !typeInfo.IsInterface)
{
// Add type to list.
classList.Add(new TypeInformation(type, true));
}
}
}
void AddChildrenToParent(ClassAndSubclasses parentClass,
List<TypeInformation> classList)
{
foreach (TypeInformation typeInformation in classList)
{
if (typeInformation.IsDerivedDirectlyFrom(parentClass.Type))
{
ClassAndSubclasses subClass =
new ClassAndSubclasses(typeInformation.Type,
typeInformation.IsXamarinForms);
parentClass.Subclasses.Add(subClass);
AddChildrenToParent(subClass, classList);
}
}
}
void AddItemToStackLayout(ClassAndSubclasses parentClass, int level)
{
// If assembly is not Xamarin.Forms, display full name.
string name = parentClass.IsXamarinForms ? parentClass.Type.Name :
parentClass.Type.FullName;
TypeInfo typeInfo = parentClass.Type.GetTypeInfo();
// If generic, display angle brackets and parameters.
if (typeInfo.IsGenericType)
{
Type[] parameters = typeInfo.GenericTypeParameters;
name = name.Substring(0, name.Length - 2);
name += "<";
for (int i = 0; i < parameters.Length; i++)
{
name += parameters[i].Name;
if (i < parameters.Length - 1)
{
name += ", ";
}
}
name += ">";
}
// Create Label and add to StackLayout.
Label label = new Label
{
Text = String.Format("{0}{1}", new string(' ', 4 * level), name),
TextColor = parentClass.Type.GetTypeInfo().IsAbstract ?
Color.Accent : Color.Default
};
stackLayout.Children.Add(label);
// Now display nested types.
foreach (ClassAndSubclasses subclass in parentClass.Subclasses)
{
AddItemToStackLayout(subclass, level + 1);
}
}
}
递归的AddChildrenToParent方法从flat classList集合中组装ClassAndSubclasses实例的链接列表。 AddItemToStackLayout方法也是递归的,因为它负责通过为每个类创建一个Label视图,将ClassesAndSubclasses链表添加到StackLayout对象中,在开头有一些空格用于正确的缩进。 该方法仅显示类名称的Xamarin.Forms类型,但显示.NET
types包括用于区分它们的完全限定名称。 该方法将平台强调颜色用于不可实例化的类,因为它们是抽象的或静态的:
总的来说,您将看到Xamarin.Forms可视元素具有以下一般层次结构:
System.Object
BindableObject
Element
VisualElement
View
...
Layout
...
Layout<T>
...
Page
...
Object之外,此缩写类层次结构中的所有类都在Xamarin.Forms.Core.dll程序集中实现,并与Xamarin.Forms的命名空间相关联。
让我们详细研究一下这些主要类。
正如BindableObject类的名称所暗示的,此类的主要功能是支持数据绑定 - 两个对象的两个属性的链接,以便它们保持相同的值。但是BindableObject也支持样式和DynamicResource标记扩展。它以两种方式实现:通过BindableProperty对象形式的BindableObject属性定义,以及实现.NET INotifyPropertyChanged接口。所有这些都将在本章和后续章节中进行更详细的讨论。
让我们沿着层次结构继续下去:正如您所见,Xamarin.Forms中的用户界面对象通常以父子层次结构排列在页面上,而Element类包括对父子关系的支持。
VisualElement是Xamarin.Forms中非常重要的类。视觉元素是占据屏幕区域的Xamarin.Forms中的任何内容。 VisualElement类定义了28个与大小,位置,背景颜色以及其他视觉和功能特征相关的公共属性,例如IsEnabled和IsVisible。
在Xamarin.Forms中,单词视图通常用于引用单个可视对象,如按钮,滑块和文本输入框,但您可以看到View类也是布局类的父级。有趣的是,View只将三个公共成员添加到它从VisualElement继承的内容中。这些是HorizontalOptions和VerticalOptions - 这是有意义的,因为这些属性不适用于页面 - 和GestureRecognizers支持触摸输入。
布局的后代能够具有儿童视图。子视图在其父级边界内可视地出现在屏幕上。从Layout派生的类只能有一个View类型的子类,但通用的Layout 类定义一个Children属性,它是多个子视图的集合,包括其他布局。您已经看过StackLayout,它将子项排列在水平或垂直堆栈中。尽管Layout类派生自View,但布局在Xamarin.Forms中非常重要,因此它们通常被认为是一个类别。
ClassHierarchy列出了Xama?rin.Forms.Core和Xamarin.Forms.Xaml程序集中定义的所有公共类,结构和枚举,但它没有列出接口。这些也是重要的,但你只需要自己探索它们。 (或者增强程序以列出它们。)
ClassHierarchy也没有列出许多有助于在各种平台上实现Xamarin.Forms的公共类。在本书的最后一章中,您将看到一个版本