简介
为了减轻非 IT 专业人员(比如业务顾问)的负担,我们将构建带有类似于 Microsoft? PowerPoint 的演示特性的原型建模工具,以帮助业务顾问组织大量非结构化数据,并为客户准备演示文档。我们的工具的第一个版本基于 Eclipse。不过,为了利用更佳的图表功能,我们决定转向 Rich Internet Application 技术。 我们考虑了流行的 Ajax 和 Adobe Flex 的优缺点,最终决定使用 Flex。
我们的基于 Flex 的应用程序允许用户使用各种可视化辅助工具创建幻灯片,包括项列表、图表编辑器、标记云、关系搜索器和表。在本文中,我们将解释一个特殊的辅助工具,它允许用户排列、分类和比较大量数据。我们将这个辅助工具称为 “Juxtaposition Table” 或 JTable。借助 JTable,用户可以将数据可视化为二维视图,并且在运行时交互地更改水平和垂直数据集。该工具支持从一个简单视图中包含的各种用户定义透视图检查数据库或信息空间。
在查看代码之前,我们先介绍基础的场景,我们在该场景中扮演一个调查中心,负责研究公众喜欢的图书、电影、歌曲和站点的品味。一个调查组可以包含任意数量的参与者和可扩展的调查类别列表。为简单起见,我们采用很小的调查组。参与者是:John、Jennifer 和 Ivan。在完成调查之后,我们将结果传输到一个简单的数据库中。现在,我们希望这些结果能够显示在我们的 Web 站点中以供查阅。我们可以提供一个很大的 HTML 表来储存调查结果,但它在用户眼前会非常杂乱,因为表太大导致信息非常密集。我们也可以提供多个表,以从多个预定义的透视图中显示数据。不过,这种方法也不理想,因为用户被局限在我们预定义的表中,并且在多个表之间滚动可能令人烦恼。
采用 JTable 可以实现一个出色的解决方案。如果我们在 JTable 中显示数据,用户就可以更改水平和垂直的数据集,从而根据自己的信息需求选择定制的透视图。例如,用户可能希望了解 John 最喜欢的图书;这样,就可以选择 “John” 作为表的水平维度,选择 “Favorite Books” 作为表的垂直维度。用户也许希望了解调查参与者最喜欢的电影。类似地,可以选择 “All People” 作为表的水平维度,选择 “Favorite Movies” 作为表的垂直维度。通过使用 JTable,用户可以在一个方便紧凑的视图中定制自己的透视图。要运行 JTable 应用程序,可以在下面的?下载表?中下载DataGridDeveloperWorksExample1。运行 JTable 应用程序之后,您就可以以各种组合方式查看调查的结果。
我们使用 Flex 的?AdvancedDataGrid
?组件来实现 JTable。在这个分为两部分的系列文章中,我们将演示如何定制和扩展在 Flex 3 数据可视化包中提供的?AdvancedDataGrid
?组件。您可以通过?参考资料?部分提供的链接更多地了解该组件。在本文中,我们还演示如何使用水平和垂直控制条在运行时修改 JTable 的内容,以及如何将表的第一列和第一行转变成列标题栏和行标题栏。在第 2 部分中,我们将演示在单元格中流畅地扩展和收缩项,以及以拖放的方式在单元格之间移动项。
本系列文章假设读者熟悉 Flex 编程环境和 ActionScript 语言。
通过运行示例比较模拟表和 JTable
在查看 JTable 应用程序的实现解决方案之前,我们先了解从调查中收集到的数据的模拟透视图,然后再概述我们的 JTable。
我们需要实现这样一个表,它包含一个用于显示一个人的姓名或所有人的姓名的水平标题栏,以及一个用于显示一个类别或喜欢的项的所有类别的垂直标题栏。表 1 模拟用户对水平维度选择 “All People” 并对垂直维度选择 “All questions” 之后的表。
表 1. 显示调查组最喜欢的项的 2D 模拟表
? | John | Jennifer | Ivan |
---|---|---|---|
Favorite book | Book - 1 | Book - 2 | Book - 3 |
Favorite movie | Movie - 1 | Movie - 2 | Movie - 3 |
Favorite song | Song - 1 | Song - 2 | Song - 3 |
Favorite site | Site - 1 | Site - 2 | Site - 3 |
图 1 显示了使用 JTable 实现的概念。
图 1. 显示调查组最喜欢的项的 2D JTable
图 1 是一个二维的数据查找图。从该图可以看到,通常出现在表中的标题栏标签没有出现在这里(在表的顶部稍微可以看到标题栏,因为包含垂直表线的窄栏允许用户改变列的大小)。您还可以观察到表体之外有两个外观一样的标题栏,它们分别是垂直和水平的。在本文的后面,我们将使用两个术语:标题栏单元格?和?内容单元格。我们将标题栏单元格定义为顶部标题栏或侧边标题栏的单元格;并将内容单元格定义为在表体中而不是标题栏中的单元格。如果再仔细查看图 1,您将看到每个标题栏单元格仅包含一个包围在椭圆形中的项,而内容单元格包含好几个项,并且每个项都有更深色的背景。另外,图中还显示了两个菜单按钮,第一个在水平标题栏的上边,第二个在垂直标题栏的左边。这两个按钮的下拉菜单允许用户动态地配置标题栏的内容,并且表的内容将随之而变。
在查看 JTable 应用程序的实现解决方案之前,我们先展示如何生成运行 JTable 应用程序所需的数据。
生成数据
在真实的场景中,调查数据很可能来自服务器的数据库。不过,为了配合这个例子,我们使用简单的?DataBase
?类,并使用调查组成员给出的答案填充它。问题本身被存储为类别。我们将数据库当作一个单实例对象,以将?DataBase
?类限制为 1 个实例。下面给出类访问该实例的代码行:
var fRowSet = DataBase.instance.getQuestions();
我们将所有?参与者的答案储存在一个储存库中,并将参与者的答案分别与对应的姓名关联起来。答题者的姓名作为查询储存库的键。我们将每个问题的答案储存在一个独立的库中,每个储存库的键与问题相同,比如 “Favorite Book”、“Favorite Movie”、“Favorite Song” 和 “Favorite Site”。使用答案填充每个参与者的储存库之后,我们将把他们的答案添加到所有答案储存库(fMap)中。
清单 1. 设置 John 的答案
var fMap:Hashmap = new Hashmap(); var johnsAnswers:Hashmap = new Hashmap(); var johnsBooks:Array = new Array("The Human Stain","One More Year","The Painted Bird"); johnsAnswers.put("Favorite Books",johnsBooks); … fMap.put("John",johnsAnswers);
我们以类似的方式将 Jennifer 和 Ivan 的答案储存到所有答案储存库中。然后,我们使用?DataBase
?类中的 3 个方法获取数据:
清单 2. 获取数据
public function getPeople():Array public function getQuestions():Array public function getAnswers(person:String = null,question:String = null):Array
getPeople
?方法将所有调查参与者所在的数组作为字符串返回。getQuestions
?方法将所有调查类别所在的数组作为字符串返回。最后,getAnswers
?方法接受两个参数作为输入(人名和调查类别)并将答案数组作为字符串返回。我们使用这些方法返回的数组结构构建我们的数据模型。可以使用 null 人名或问题参数调用?getAnswers
?操作。
本文的剩余部分将讨论我们的 JTable 设计和实现解决方案,包括我们对?AdvancedDataGrid
?组件进行的必要修改和我们创建的其他支持,从而让 JTable 能够像例子所展示的那样运行。
建模数据
JTable 数据提供程序构建在行和单元格的基础之上。因为表是由行组成的,并且每个行包含多个单元格,所以我们首先定义?Row
?类和?Cell
类,以实现数据提供程序和表之间的映射。
如这些类的名称所示,Row
?类存储每个行的对象,而?Cell
?类用于储存每个行的单元格的内容,见下面的清单 3。每个单元格都包含一个泛型数据变量。我们将在该数据变量中储存包含两个元素的数组:单元格的水平标题栏名和垂直标题栏名。
清单 3. Cell 类
public class Cell { private var fData:*; public function Cell() { } public function get data():* { return fData; } public function set data(value:*):void { fData = value; } }
我们创建一个称为?HeaderCell
?的包装器类,用于区分标题栏单元格和内容单元格。此外,我们还创建了另一个包装器类?NullCell
,用于表示左上角中既不是 “标题栏” 单元格又不是 “内容” 单元格的单元格。
行内容由其?Cell
?对象定义。Row
?类是?Cell
?对象的包含者,如清单 4 所示。Row
?类是动态的。Flex 中的动态对象充当散列映射(hashmap)。当 cell 对象属于向其添加的行时,将在运行时修改?Row
?类。Cell
?对象通过类索引插入到?Row
?对象中。因此,Cell
?对象通过?Row
?对象中的列索引进行散列化。
清单 4. Row 类
public dynamic class Row { private var fColumIndex:int = 0; public function Row() { } public function putCell(cell:Cell):void { this[fColumIndex++] = cell; } public function getCellAt(index:int):Cell { return this[index] as Cell; } }
查看了?Cell
?和?Row
?类之后,我们将进入 JTable 应用程序的另一个实现解决方案。首先,我们展示如何保护 JTable 的 UI 代码不因任何数据变化而遭到更改。我们通过将数据与用户界面分离来实现该目的。
我们在?DefaultDataModel
?类中定义表的数据模型。该类的?createDataPovider
?函数为表提供内容。这个函数接受两个参数:“行数组” 和 “列数组”。这些参数被传递到?DefaultDataModel class
?构造器,如清单 5 中的代码所示。
清单 5. 将参数传递到 DefaultDataModel 类
private var fRowSet:Array; private var fColumnSet:Array; public function DefaultDataModel(rowSet:Array,columnSet:Array) { fRowSet = rowSet; fColumnSet = columnSet; }
每次用户想要通过选择菜单选项(水平或垂直弹出菜单按钮)改变表的外观时,将按照清单 6 的代码修改表背后的数据模型。
清单 6. 更改数据模型
public function get dataProvider():ArrayCollection { if(!fDataProvider) fDataProvider = createDataProvider(); return fDataProvider; }
从清单 7 的代码可以看到,createDataProvider
?函数将代码分成 3 个其他函数:createRowAndColumnDataProvider
、createColumnOnlyDataProvider
?和createRowOnlyDataProvider
。createRowAndColumnDataProvider
?创建一个为 2D 表提供内容的数据提供程序。createColumnOnlyDataProvider
?和?createRowAndColumnDataProvider
?创建为 1D 表提供内容的数据提供程序。
当用户选择 “Clear” 选项时,将在水平菜单按钮或垂直菜单按钮中显示一个 1D 表(见图 2 和图 3)。
清单 7. createDataProvider
protected function createDataProvider():ArrayCollection { if (fRowSet && fColumnSet) return createRowAndColumnDataProvider(); if (fColumnSet && !fRowSet) return createColumnOnlyDataProvider(); if (fRowSet && !fColumnSet) return createRowOnlyDataProvider(); return new ArrayCollection(); }
在下一个代码片段中(清单 8),我们将展示 3 个 “Create” 数据提供程序函数之一,即?createRowAndColumnDataProvider
。这个函数为 2D 表提供内容。首先,我们实例化一个标题行并向其添加一个左上角单元格。然后,我们将遍历列(第一次循环)并将其余的标题栏单元格添加到该行,并在遍历结束之后将该行添加到提供程序。第二次循环在行上进行。对于?fRowSet
?数组中的每个元素都实例化一个新行。向行添加的第一个单元格是一个水平标题栏单元格;向行添加的其他单元格是内容单元格。
清单 8. DefaultDataModel 类:createRowAndColumnDataProvider 函数
protected function createRowAndColumnDataProvider():ArrayCollection { var provider:ArrayCollection = new ArrayCollection(); var headerRow:Row = new Row(); // Put in the top corner cell headerRow.putCell(new NullCell()); // Populate the row header for each(var item:String in fColumnSet) { // Create the header cells var cell:Cell = new HeaderCell; cell.data = item; headerRow.putCell(cell); } provider.addItem(headerRow); // Populate the contents and column header for each(var rowItem:String in fRowSet) { // Content row var row:Row = new Row(); // Column header cell var headerCell:Cell = new HeaderCell(); headerCell.data = rowItem; row.putCell(headerCell); for each(var columnItem:String in fColumnSet) { // Create the content cells var cell:Cell = new Cell(); cell.data = new Array(rowItem,columnItem); row.putCell(cell); } provider.addItem(row); } return provider; }
从?createRowAndColumnDataProvider
?函数返回的数组集合将通过表的 dataProvider 属性分配给表。
我们构建另一个数据模型为控制标题栏和表的内容的水平和垂直菜单按钮提供内容。我们在?PopUpButtonsDataModel
?类中定义该模型。这个类仅有两个函数:getRowMenu
?和?getColumnMenu
。您可以在下面的代码片段(清单 9)中看到这两个函数之一。我们首先向菜单数组集合添加 “Clear” 和 “All Questions” 菜单项,然后在一个简单的循环中添加调查类别。我们储存在每个菜单项对象中的数据是一个仅包含一个元素的数组:类别名。从?getColumnMenu
?返回的?MenuItem
?对象的数组集合将通过它们的?dataProvider
?属性分配给菜单按钮。
清单 9. PopUpButtonsDataModel class: getRowMenu 函数
public function getRowMenu(): ArrayCollection { var menu:ArrayCollection = new ArrayCollection(); var questions:Array = DataBase.instance.getQuestions(); var menuItem:MenuItem = new MenuItem("Clear"); menu.addItem(menuItem); var menuItem:MenuItem = new MenuItem("All questions"); menuItem.data = questions; menu.addItem(menuItem); for each(var question:String in questions) { var menuItem:MenuItem = new MenuItem(question); menuItem.data = new Array(question); menu.addItem(menuItem); } return menu; }
构建 UI
在图 1、2 和 3 中可见的 UI 是一个画布容器,它们包含 3 个用户界面元素:表和两个菜单按钮。我们的画布类DynamicAdvancedDataGridCanvas
?扩展了 Flex 的?Canvas
?类。我们在?createChildren
?函数中将表和两个菜单按钮添加到画布容器中(清单 11),该函数将覆盖基类中的相同函数。我们使用菜单按钮允许用户通过从按钮下拉菜单中选择选项来查看调查。(这里的按钮为水平和垂直控制条)。我们通过将行逆时针旋转 90 度角创建垂直控制条,如下面的清单 11 所示。不过,这意味着需要嵌入字体,因为标准的字体不能显示在垂直按钮中。清单 10 显示了在应用程序的 DataGridDeveloperExample1.mxml 文件中包含嵌入字体声明所需的代码。
清单 10. 在 DataGridDeveloperExample1.mxml 中嵌入字体所需的代码
[Embed(systemFont='Verdana',fontWeight="bold",fontName='embeddedFont',mimeType='application/x-font',advancedAntiAliasing="true")]
清单 11. DynamicAdvancedDataGridCanvas 类:createChildren 函数
override protected function createChildren():void { super.createChildren(); fColPopupMenuButton = new SuperPopUpMenuButton(); ... PopupMenuButton.addEventListener(MenuEvent.ITEM_CLICK,columnClick); fColPopupMenuButton.addEventListener(DropdownEvent.OPEN,colPopupOpen); fColPopupMenuButton.dataProvider = fPopUpButtonsDataModel.getColumnMenu().toArray(); addChild(fColPopupMenuButton); fRowPopupMenuButton = new SuperPopUpMenuButton(); ... fRowPopupMenuButton.rotation = -90; ... fRowPopupMenuButton.addEventListener(MenuEvent.ITEM_CLICK,rowClick); fRowPopupMenuButton.addEventListener(DropdownEvent.OPEN,rowPopupOpen); ... fRowPopupMenuButton.dataProvider = fPopUpButtonsDataModel.getRowMenu().toArray(); addChild(fRowPopupMenuButton); fAdvancedDataGrid = new DynamicAdvancedDataGrid(); addChild(fAdvancedDataGrid); ... }
我们向画布添加的菜单按钮是受事件驱动的,这意味着当用户从下拉菜单中打开和选择选项时,canvas 类将处理生成的事件(DropdownEvent.OPEN
?和?MenuEvent.ITEM_CLICK
?事件)。
图 2. 显示调查组最喜欢的项的 1D 表,打开了垂直菜单
图 3. 显示调查组最喜欢的项的 1D 表,打开了水平菜单
在下一个代码片段中(清单 12),我们将显示如何处理?DropdownEvent
。通过菜单按钮的?dataProvider
?属性来分配数据提供程序。您有必要了解如何将垂直菜单按钮的?popUp
?窗口的 y 坐标从局部坐标转换成全局坐标(见代码片段的最后一行)。水平菜单按钮不需要进行此类转换。
清单 12. DropdownEvent 处理
public function rowPopupOpen(event:DropdownEvent):void { if(fRowMenuInvalid) { fRowPopupMenuButton.dataProvider = fPopUpButtonsDataModel.getRowMenu().toArray(); } fRowPopupMenuButton.popUp.y = localToGlobal(new Point(0,fRowPopupMenuButton.y)).y - fRowPopupMenuButton.width; }
在下面的清单 13 中,我们将显示如何处理第二个事件?MenuEvent
。第一行代码更改按钮的标签,以在下拉菜单中显示选择的标签。第二行代码从事件获取菜单数据对象。该函数的最后一行代码刷新表。
清单 13. MenuEvent 处理
private function rowClick(event:MenuEvent):void { fRowPopupMenuButton.label = event.label; fRowSet = (event.item as MenuItem).data; refresh(); }
清单 14 显示了?refresh
?函数。
清单 14. Refresh 函数
public function refresh():void { var model:DefaultDataModel = new DefaultDataModel(fRowSet,fColumnSet); fAdvancedDataGrid.dataProvider = model.dataProvider; }
现在,让我们更加详细地探索表本身的创建工作。我们在?DynamicAdvancedDataGrid
?类中定义表,该类扩展 Flex 的?AdvancedDataGrid
?类。
Flex?AdvancedDataGrid
?是一个包含在 Flex 数据可视化包中的 UI 包,它能够显示多列信息。AdvancedDataGrid
?组件的每个列都由一个AdvancedDataGridColumn
?对象表示。headerText
?和?dataField
?属性是定义一个列的两个主要属性。headerText
?是显示为列标题的名称,而dataField
?表示来自数据提供程序的数据将显示在该字段中。由对象集合组成的数据通过?dataProvider
?属性分配给?AdvancedDataGrid
?组件。
清单 15 显示了创建包含?AdvancedDataGrid
?组件的应用程序的最简单例子。在该代码片段中,我们将变量 dataModel 绑定到 dataProvider 属性。
清单 15. 使用 AdvancedDataGrid 创建一个简单的应用程序
private var dataModel:ArrayCollection = new ArrayCollection([ {Person :"John",Favorite Book: "The Human Stain"},{Person :"Jennifer",Favorite Book: "One More Year"},{Person :"Ivan",Favorite Book: "The Painted Bird"}]); fAdvancedDataGrid = new AdvancedDataGrid(); fAdvancedDataGrid.dataProvider = dataModel; fAdvancedDataGrid.percentWidth = 100; fAdvancedDataGrid.percentHeight = 100; addChild(fAdvancedDataGrid);
要运行这个简单的例子,请从下面的?下载表?下载?DeveloperWorksDataGridBaseExample。在这个例子中,根据数据提供程序的格式和内容在表中显示了两个列(Person
?和?Favorite Book
)。
列会自动从数据提供程序获取 “键”?Favorite Book
,以确定在显示信息时应该使用该数据提供程序的哪个字段。尽管使用这些代码和默认的?AdvancedDataGrid
?就可以创建一个应用程序,但是该组件在实现我们的 JTable 时会受到限制。我们发现的 3 大限制是:数据提供程序的原始格式、列数固定和缺少单元格着色支持。原始数据提供程序不能满足经常改变的数据的需求,因为组件不能收到数据变更通知。我们对 JTable 使用的解决方案是在每次用户从按钮的下拉菜单选择新的选项时,通过重新分配数据提供程序来刷新表。我们在数据发现变化时动态地重新构建列,从而解决了固定列数的限制。如您即将在清单 16 中见到的一样,我们在显示数据之前使用呈现器定制它,并为单元格中的各个项着色。
在这个小节开始时,我们展示如何从?DynamicAdvancedDataGridCanvas
?的?createChildren
?函数向?DynamicAdvancedDataGrid
?发出一个创建表的调用。我们还展示了在每次刷新表之后,如何给表分配重新创建的数据提供程序。向表分配数据提供程序的操作发生在DynamicAdvancedDataGrid
?类的 “set” 函数中。这个 “set” 函数覆盖基类中的 “set” 函数。该函数的最后一行是对?createColumns
?函数的调用,后者在表中动态地创建列:
清单 16. 定制数据的呈现器
override public function set dataProvider(value:Object):void { super.dataProvider = value; createColumns(dataProvider as ArrayCollection); }
以数组集合的形式存在的表内容被作为参数传递给?createColumns
?函数。数组集合包含的所有行都将显示在表中。每个行包含的列对象与将显示在表中的列一样多。对于?createColumn
?函数,我们首先需要做的就是找出一个行有多少个列。这通过计算第一行包含的列对象数获得,我们将得到的计数作为参数传递到?DynamicAdvancedDataGrid
?类的?getNumberOfProperties
?函数。在确定了列的数量之后,我们将遍历该计数,并使用 Flex 的?AdvancedDataGridColumn
?类创建列。从?createColumns
?函数的代码片段(清单 17)可以看到,列的?dataField
?被分配一个序号,它表示列对象出现在数据提供程序集合的行的次序。我们还为每个列分配其他属性(比如 “resizable” 和 “visible”),并将列放入列数组 “theColumns” 中。当遍历结束之后,我们将列数组分配给表的 “columns” 属性。这由?createColumn
?函数的最后一个语句完成。
清单 17. DynamicAdvancedDataGrid 类:createColumns 函数
private function createColumns(dp:ArrayCollection): void { if(dp.length > 0) { var numColumns:int = getNumberOfProperties(dp[0]); var theColumns:Array = new Array(); for(var i:int = 0; i < numColumns; i++) { var dgc:AdvancedDataGridColumn = new AdvancedDataGridColumn(); dgc.dataField = i.toString(); ... theColumns.push(dgc); } columns = theColumns; } }
在进入 UI 实现的最后部分(定制项呈现器)之前,我们需要指出的是,在?DynamicAdvancedDataGrid
?类的构造器中,我们将定制项呈现器(包装在?ClassFactory
?对象中的?DynamicAdvancedDataGridItemRenderer
)分配给表的?itemRenderer
?属性:
itemRenderer = new ClassFactory( DynamicAdvancedDataGridItemRenderer);
项呈现器允许显示定制数据。在向用户显示数据之前,我们通过创建自己的项呈现器来定制数据。我们的呈现器基于 Flex 的?HBox
?组件,并覆盖该组件的 set 函数。我们在当前的行数据对象传递到项呈现器之后修改它。
如前所述,内容单元格的主体中可以包含多个项。John 可以有 3 本最喜欢的图书,Ivan 可以有两部最喜欢的电影。在与 “Favorite books” 和 “John” 相关联的单元格中必须显示 3 个项,而与 “Favorite Movies” 和 “Ivan” 相关联的单元格中必须显示 2 个项。
因此,我们决定通过我们称之为 “项查看器(item viewer)” 的工具在单元格中显示每个元素。“项查看器” 是一个包含文本字段的 “框”。项呈现器通过它的 set 函数在单元格中创建数量与单元格中的答案一样多的项查看器。ItemViewer
?类扩展?Box
?类。它包含一个 UI 组件SuperUITextField
,该组件继承?UITextField
。
下面是?DynamicAdvancedDataGridItemRenderer
?的 set 函数的代码片段。注意,在该代码片段(清单 18)中,将查询数据库中属于某个内容单元格的所有答案。在获取答案之后,将通过遍历这些答案创建项查看器。
清单 18. DynamicAdvancedDataGridItemRenderer 类:来自 set 函数的代码片段
if(data && data is Row) { var cell:Cell = (data as Row).getCellAt(listData.columnIndex); if (cell is NullCell) return; if(cell is HeaderCell) { var viewer:ItemViewer = new ItemViewer(); viewer.styleName = header; ... viewer.title = cell.data as String; addChild(viewer); } else { // content cell var parameters:Array = cell.data as Array; var answers:Array = DataBase.instance.getAnswers(parameters[1],parameters[0]) for each(var answer:String in answers) { var viewer:ItemViewer = new ItemViewer(); viewer.styleName = "content"; viewer.title = answer; addChild(viewer); } } }
我们想提及的最后实现细节是查看器的?styleName
?属性,它用于调整查看器的颜色和形状。在以上代码片段中,为每个查看器分配了一个styleName
?属性。如果恰好在标题栏单元格中创建查看器,那么查看器的?styleName
?为 “header”。对于其他情况,styleName
?为 “content”。“header” 和 “content” 查看器的背景颜色、圆角半径、边框和透明度都在外部 CSS 文件 example.css 的类选择器中定义(见清单 19)。
清单 19. 定制 header 和 content 查看器的样式
.header { borderStyle : solid; backgroundColor: #FFFFFF; cornerRadius: 8; borderThickness: 1; } .content { backgroundColor: #AABBCC; cornerRadius: 8; }
结束语
现在,您了解了我们如何创建基于 Flex 的并置表,希望您也尝试通过改变水平和垂直数据集来创建自己的定制透视图。如果您的应用程序需要动态地创建列、显示定制数据或使用独特的单元格外观和颜色,那么可以应用本文介绍的理念和实现。
在下一篇文章中,我们将展示如何流畅地扩展和收缩单元格中的项,以及如何以拖放的方式在单元格之间移动项。
http://www.ibm.com/developerworks/cn/web/wa-juxtaposition1/