底层模型概述
Spread控件提供了很多模型,这些模型提供了自定义控件的基础架构。同时,这些模型作为底层模板,派生出了更多通用的快捷对象。
在不使用Spread的底层模型的情况下,你可以完成许多任务。通过使用Spread设计器或者快捷对象(如单元格、列和行)的属性,你可以在表单上实现许多改变。但是因为表单模型是所有快捷对象的基础,因此在通常情况下,使用表单模型要比使用快捷对象的速度要快。例如,在代码中使用快捷对象设置一个属性值:
fpSpread1.Sheets[0].Cells[0, 0].Value = "Test";
这与下面使用底层数据模型的方式等价:
fpSpread1.Sheets[0].Models.Data.SetValue(fpSpread1.Sheets[0].GetModelRowFromViewRow(0), fpSpread1.Sheets[0].GetModelColumnFromViewColumn(0), "Test");
快捷对象访问的是底层模型。当你使用快捷对象时,你实际上在使用控件的模型。作为一名开发人员,如果你想完全理解Spread的工作方式,想使用那些为你提供的丰富特性和自定义功能,你就需要理解底层模型的使用方法。例如,你可以用这种方法为你公司所有的开发者创建一个模板控件。基于这些模型中的一种创建你自己的类,你可以自定义该类并提供给所有的开发者使用。
使用底层模型有以下好处:
- 更好的性能:如果你设置了多种属性,例如,如果你为对象设置一些属性,并且将对象指派给Spread,你的应用将会运行的更快。
- 专业的特性:如果你想创建自定义特性,例如如果你想扩展数据模型来导入一个制表符定界文件,你可以通过扩展基础表单数据模型(BaseSheetDataModel)来达到该目的。如果你想创建你自己的单元格类型或自定义用户选择单元格的行为,同样可以通过使用模型来到达到目的。
- 开发的一致性:如果你的开发团队想在一些自定义风格和行为上保持一致,只需在模型上做一些改变即可达到目的。
- 更完整的理解产品:如果你在使用控件的多种特性,自定义控件最有效的方法就是首先理解了对象所基于模型的工作原理。
表单模型是一个集合,包含了所有对象的基础设置以及某个特定表单的设置项。如果在Spread控件中有多个表单,那么每一个表单都有一个它自己的模型集合。
控件中表单的某些部分是由底层模型管理的,下图对模型做了概念性的描述。
想要把使用模型描述清楚并不容易,因为涉及到许多接口。每一个模型类都实现了许多接口,并且每一个模型都要实现一个特定的“模型”接口,使其作为该特定模型的合法实现。所有对模型类的引用都是通过接口实现的,并且不要猜想每一个模型中都实现了哪些接口(除了“模型”接口必须暴露出来之外)。如果模型类没有实现一个特定的接口,那么该功能就不能在表单中使用(例如,如果SheetView.Models.Data没有实现IDataSourceSupport接口,那么DataSource和DataMember属性将不起作用)。如果想获取这些接口的完整列表,你可以在线帮助文档中查看相关信息http://www.gcpowertools.com.cn/docs/spreadwin5help/ 。
表单模型的类型
Spread控件提供了如下模型,这些模型提供了许多可以在控件上使用的自定义设置项。
表单模型
类和接口
描述
轴模型
BaseSheetAxisModel
DefaultSheetAxisModel
ISheetAxisModel
该模型是表单中的单元格如何按行或列组织的基础。
数据模型
BaseSheetDataModel
DefaultSheetDataModel
ISheetDataModel
该模型是表单单元格数据操作的基础。
选择模型
BaseSheetSelectionModel
DefaultSheetSelectionModel
ISheetSelectionModel
该模型是表单中被选中的单元格交互与行为的基础。
合并模型
BaseSheetSpanModel
DefaultSheetSpanModel
ISheetSpanModel
该模型是单元格如何进行合并的基础。
样式模型
BaseSheetStyleModel
DefaultSheetStyleModel
ISheetStyleModel
该模型是表单单元格外观样式的基础。
表单(SheetView对象)可以看作是五个底层模型(轴、数据、选择、合并和样式)的组合:
- 轴模型处理列和行的所有操作(例如列宽、行高以及某个行和列是否可见)。
- 数据模型处理所有与数据相关的操作(例如值、公式以及单元格中任何可选的注释和标记)并包括表单中的数据。
- 选择模型处理所有被选中的单元格范围。
- 合并模型处理所有合并的单元格。
- 样式模型处理单元格的外观设置(例如,背景色、字体以及单元格类型)。
因此,你对模型做的所有操作都会自动的在表单中进行更新,并且大部分的表单修改也会在模型中进行更新。对于单元格、行和列对象的设置也都是如此。对这些对象的大部分修改都会自动更新到相应的表单模型设置中,反之亦然。如果你在数据模型中添加了一些列,它们也会被添加到表单中。甚至对于参数也同样如此,例如,只要表单未经过排序,数据模型GetValue和SetValue方法中的行和列参数,与表单中行和列的参数索引就是相同的。
并非所有Spread名字空间的内容都包含在模型中。例如,控件的某些部分、表单标签、表单背景色还有网格线,都没有包含在模型中。但是对一个指定的单元格来说,有意义的信息,如单元格的数据以及单元格的外观,都被包含在模型中。表单的数据区域有自己的模型集;同样的,行标题和列标题是另外两个具有自己模型集的分组,表角是另外一个具有自己模型集的分组。
每一个模型都包含一个基础模型类,一 默认模型类和一个接口。默认模型是你在开发时最可能用到的模型;它提供了控件的默认特性,并且可以用来对模型进行小范围的自定义设置。基础模型是创建默认模型的基础,也可以通过它创建自定义模型。基础模型包含最少的内置特性,默认模型对基础模型进行了扩展。如果你想为你的应用程序提供不同的功能特性或者自定义它的外观和行为,你可以通过扩展基础模型创建一个新类来实现。例如,你可以通过以上的方法为你公司的所有开发人员创建一个模板控件。基于基础模型创建自己的类,你可以创建自定义类,并把它提供给其他开发者使用。一般情况下,如果你在编辑模型,请使用默认模型类。但是如果你想(从头开始)创建一个自定义模型,请使用基础模型类。
每一个默认模型类不仅实现了该模型类特有的接口,还实现了其他可选的接口。在模型类中,大多数功能(例如,公式、数据绑定、XML序列化等等)都是可选的,并且是在与主模型接口(如ISheetDataModel)不同的接口中实现的。因此如果你想实现自己的模型类,你可以自主选择想要实现哪些功能。
模型与模型之间保持同步是很重要的,所以在组成表单的模型中,行数和列数是需要保持一致的。SheetView对象通过SheetView.DocumentModels.Data属性监听ISheetDataModel.Change事件,并且当行数或者列数通过以下方式改变时,会相应的对其他模型进行更新:
- 直接在RowCount或ColumnCount属性中进行设置
- 通过IRangeSupport接口进行插入或删除行/列操作
- 使用新的模型替换整个数据模型
如果模型之间同步失败,程序将抛出一个索引out-of-range异常,并尝试获取不存在的行或列的信息。
理解数据模型
数据模型包含了单元格的内容,不管它是数值还是公式,或者是单元格的注释或标记。数据模型在表单的数据域内包含了单元格的Value属性,数据绑定表单的database属性,以及其他与单元格内容相关的属性。
数据模型是你在使用Spread控件时最有可能进行自定义设置的模型。相对于其他模型,数据模型实现了更多的接口,提供了更多可选择的功能。例如,如果你想要实现类似于ActiveX Spread控件的未绑定虚拟模型功能,自定义数据模型就可以实现。
数据模型对象
数据模型是一个为单元格提供值的对象,这些值显示在表单中。大多数情况下,创建时表单所创建的默认数据模型就能满足你的需求。
默认数据模型DefaultSheetDataModel可以创建用来存储注释、公式、标签和值的对象。这些对象主要设计用来在内存使用和速度之间进行平衡,内存使用的大小和存取速度的快慢与数据模型的大小以及其中数据的稀疏程度相关。如果你不使用注释、公式以及标签,这样就不会占用很多内存,因为数据非常的稀疏。事实上这些对象并不会为数据申请内存,除非真的需要。所以只要没有在模型中设置注释、公式或标签,内存占用会一直很少。
默认数据模型可以在未绑定模式或绑定模式下使用。在未绑定模式下,数据模型的表现像是一个储存单元格值的二维数组。在绑定模式下,数据模型封装了所提供的DataSource;如果需要,还可提供DataSource中没有的额外设置,例如,单元格公式,以及未绑定行或者列。
设置和添加数据模型
SetModelDataColumn方法与AddColumn方法的不同地方在于,你可以在数据模型中指定哪一个数据域绑定到哪一列上。
如果你在模型中添加了一些列,那么这些列也会被添加到表单中。只要表单未经过排序,数据模型GetValue和SetValue方法中的行和列参数,与表单中行和列的参数索引就是相同的。如果对表单的行或列进行了排序,那么视图坐标必须通过SheetView.GetModelRowFromViewRow和 SheetView.GetModelColumnFromViewColumn方法与模型坐标进行映射。
在数据模型中,SheetView.GetValue方法和SheetView.SetValue方法经常用来获取或设置数据。(这与调用SheetView.Models.Data.GetValue和SheetView.Models.Data.SetValue等价。) 在SpreadView的SheetView中,当单元格处于编辑模式时,Cell.Value属性返回editor控件中单元格的值。当单元格结束编辑模式时,单元格的值就会在数据模型中进行更新。但是,你可以通过代码手动把值更新到数据模型中:
SheetView.SetValue(row, column, SheetView.Cells[row, column].Value);
实现的接口
当数据模型实现了IDataSourceSupport接口并被绑定到一个数据源时,数据模型中被绑定的部分就可以直接从数据源中获取或设置数据。如果在数据模型绑定数据源之后,使用AddColumns方法向其加入了一些列(对于这些列,IDataSourceSupport.IsColumnBound返回false),那么这些列也可以是未绑定的。这些未绑定列的数据将会保存在数据模型中,而不是在数据源中。
如果数据模型也实现了IUnboundRowSupport接口,那么数据模型中的行也可以是未绑定状态的,并且这些行的数据也将保存在数据模型中而不是在数据源中。这些行可以通过调用 IUnboundRowSupport.AddRowToDataSource函数转换成绑定行,并且如果autoFill参数被设置为True,未绑定的行中已绑定列的数据将以一条新的记录或一个新的元素被添加到数据源中,假设数据源允许这样的操作(如果它不允许这样的操作时,你将会得到一个异常),这样一个未绑定的行就转换成了绑定行。
默认的数据模型类,DefaultSheetDataModel,实现了所有的接口,以及许多与计算、层次和序列化相关的接口。
查看以下的代码段,可以看到默认的数据模型和表单上的对象有什么不同。这段代码把表单绑定到一个叫MyData的数据源上。
fpSpread1.Sheets[0].DataSource = MyData.Tables[0];
以及
FarPoint.Win.Spread.Model.DefaultSheetDataModel model = new FarPoint.Win.Spread.Model.DefaultSheetDataModel(MyData, strTable);
fpSpread1.Sheets[0].Models.Data = model;
在第一个代码段中,开发者使用现有的数据模型,并把它转化为一个数据源; 在第二个代码段中,开发者使用一个新的数据模型替换老的模型,并且丢弃老的数据模型。两种实现方式的结果是相同的,但是第一种方式将导致老的模型变成垃圾,并进行回收。通常你可能不想进行数据模型替换,除非你想创建属于自己的数据模型类。一般情况下,没有必要使用其他的DefaultSheetDataModel替换数据模型,因为已经有一个在使用了。
速度和性能的平衡
如果你从DefaultSheetDataModel 上派生,并使用GetValue和SetValue的实现来存储数据,那么它将通过我们对稀疏数组和矩阵的实现在内存使用和访问速度之间进行平衡。设计它的目的是为了实现快速创建一个很大的模型(2亿行*乘以2亿列),并且能够以合理的速度进行数据的获取和设置,直到数据量变得很大(这种情况下,不管怎样你都将会耗尽内存)。当模型很大,并且很稀疏时(例如有至少三分之二是空的),访问速度会变得很慢(需要使用二分查找法),并且内存使用效率也会降低。在模型不是很大的情况下(少于32K行和列时),并且不稀疏(至少三分之一是满的),访问速度会很快(不需要使用二分查找法)并且内存使用效率很高。
你可以先创建一个在窗体上使用Spread控件的测试工程,然后在该工程上运行一些简单的测试,把表单的ColumnCount和RowCount属性设置为一个很大的值,你不会发现任何延迟;这是因为内存是基于实际数据项的大小来分配的。如果你开始在表单中填入大量数据,不久你就会感觉到延迟,尤其当可用内存变小并且系统开始使用页面文件来进行虚拟内存的交换的时候(仅在有大量数据时,才会发生这种现象)。
如果你需要获取更多的细节,请参阅BaseSheetDataModel 类,DefaultSheetDataModel 类和ISheetDataModel 接口。
创建一个自定义的表单模型
你可以以表单模型为模板来创建一个新的定制模型。例如,设想创建一个自定义数据模型。使用自定义数据模型,需要创建一个类并实现ISheetDataModel, 并在SheetView.Models.Data属性中设置该类的实例。
假设你不需要任何可选的接口,那么 ISheetDataModel 是唯一要求实现的接口。所有可选的接口都在DefaultSheetDataModel 中实现了。所以,当你想在自己的数据模型中实现它们时,可以很容易的仅仅实现 DefaultSheetDataModel的子类。
在BaseSheetDataModel 中,Changed事件也需要你来实现。
在产品实例Samples\CS\FreeCell文件夹中,有一个自定义数据模型的示例。这是FreeCell游戏在数据模型中的实现。
在少数情况下,因为性能原因,你可能需要创建自己的自定义数据模型。例如,假设你想要显示一个有一百万行十列组成的大表,并且要计算它的值(如加法或者乘法)。如果使用默认的表单数据模型,那么你需要计算和保存所有一千万个值,这将会耗费大量的时间和内存。下面是一个代码实例。
for (r = 0; r < 1000000; r++)
for ( c = 0; c < 10; c++)
spread.Sheets[0].Cells[r,c].Value = r + c;
class ComputedDataModel : BaseSheetDataModel
{
public override int RowCount
{
get { return 1000000; }
}
public override int ColumnCount
{
get { return 10; }
}
public override object GetValue(int row, int column)
{
return row + column;
}
}