Flex 样式编程:处理动态创建的控件
本文演示了如何通过使用接口和数据绑定来简化动态创建的 Flex 控件的工作。
引言
在设计您自己的 Flex 应用程序时,您会遇到各种类型的对象属性,例如字符串、日期、枚举、数字和布尔值。这些类型中的每一种都可以在用户界面中以不同的方式显示,例如,boolean
类型可以表示为标签、复选框、一对单选按钮或下拉列表。对于这些不同的可视化表示,可以使用不同的控件。
在本文中,我们将通过一些示例,学习如何使用接口和数据绑定来简化处理 Flex 中动态创建控件时遇到的任务。
想象一下,我们有一个包含多个控件的面板,这些控件显示对象的某个属性。这些控件在面板启动时动态加载。每个控件都可以提供自己的属性可视化样式。
创建控件
首先,我们将为显示值的组件创建一个通用接口(当我们需要添加新类型的控件时,该接口将简化我们的工作)。为了简化示例,我们假设所有组件都操作单个值。
package controls
{
public interface IDetailsControl
{
function set value(value: Object): void;
}
}
现在,我们创建一个实现该接口并显示提供值的控件。这将是一个简单的控件,它扩展了 mx.controls.Label
的功能。
package controls
{
import mx.controls.Label;
public class ELable extends Label implements IDetailsControl
{
public function set value(value: Object): void
{
if (value is String)
{
text = value as String;
}
else if (value != null)
{
text = "#object#";
}
else
{
text = "#null value#"
}
}
}
}
我们还将创建一个工厂,它将为我们提供控件。目前,此工厂仅操作 Elable
控件。
package controls
{
public class ControlFactory
{
public static function getControl(type: String): IDetailsControl
{
// receiving control class
var ctrlClass: Class = null;
switch (type)
{
case "label": ctrlClass= ELable;break;
}
// instantiating control class
var ctrl: IDetailsControl =
ctrlClass != null ? new ctrlClass : null;
return ctrl;
}
}
}
制作可视化
一旦我们有了接口、控件和工厂,就可以开始创建应用程序了。我们创建一个属性 obj
,它将作为由控件显示的值。我们还将添加一个 VBox
,其中包含一些按钮,这些按钮将修改 obj
属性。
<?xml version="1.0" encoding="utf-8"?>
<mx:WindowedApplication xmlns:mx="http://www.adobe.com/2006/mxml" layout="vertical">
<mx:Script>
<![CDATA[
public var obj: Object = "initial value";
]]>
</mx:Script>
<mx:VBox id="target" width="100%" height="100%" horizontalAlign="center">
<mx:Button label="String" click="obj = Math.random().toString()"/>
<mx:Button label="Object" click="obj = {}"/>
<mx:Button label="Null" click="obj = null"/>
</mx:VBox>
</mx:WindowedApplication>
我们的应用程序将显示多个控件。要显示的控件将在 conf
字符串数组中通过其名称定义。我们将使用我们的 ControlFactory
初始化控件。
var conf: Array = ["label", "label", "unknown control"];
for each (var controlType: String in conf)
{
var ctrl: IDetailsControl = ControlFactory.getControl(controlType);
}
然后,我们将控件添加到 VBox 布局 中,并将 obj
属性设置为控件的值(为简单起见,我们将对所有控件使用相同的值)。
if (ctrl is DisplayObject)
{
target.addChild(ctrl as DisplayObject);
ctrl.value=obj;
}
所有代码都将放在 creationComplete
处理程序中,该处理程序将在应用程序初始化后调用。
<?xml version="1.0" encoding="utf-8"?>
<mx:WindowedApplication xmlns:mx="http://www.adobe.com/2006/mxml" layout="vertical"
creationComplete="onCreationComplete(event)">
<mx:Script>
<![CDATA[
import mx.events.FlexEvent;
import controls.IDetailsControl;
import controls.ControlFactory;
public var obj: Object = "initial state";
private function onCreationComplete(event: FlexEvent): void
{
// specify controls we want to display
var conf: Array =
["label", "label", "unknown control"];
for each (var controlType: String in conf)
{
// receiving control
var ctrl: IDetailsControl =
ControlFactory.getControl(controlType);
// we work with DisplayObject only
if (ctrl is DisplayObject)
{
//adding a control to layout
target.addChild(ctrl as
DisplayObject);
//setting the value
//for the control
ctrl.value=obj;
}
}
}
]]>
</mx:Script>
<mx:VBox id="target" width="100%" height="100%" horizontalAlign="center">
<mx:Button label="String" click="obj = Math.random().toString()"/>
<mx:Button label="Object" click="obj = {}"/>
<mx:Button label="Null" click="obj = null"/>
</mx:VBox>
</mx:WindowedApplication>
现在我们可以运行我们的应用程序了。

如您所见,两个Label 控件显示了 obj
变量的初始值。
应用绑定
现在,通过使用数据绑定,我们将使标签能够监听 obj
变量所做的更改,因此当按下某个按钮时,我们就能在标签上看到 obj
属性的最新值。
首先,我们将 obj
属性设置为可绑定。
[Bindable]
public var obj: Object = "initial state";
然后,我们将简单的 ctrl.value=obj;
设置替换为绑定到setter 函数,这样每次 obj
属性更改时都会调用此函数。为此,我们将使用 mx.binding.utils.BindingUtils
中的 bindSetter
方法。
BindingUtils.bindSetter(createSetter(lbl),this,"obj");
我们传递给 bindSetter 方法
的setter 函数将如下所示。
private function createSetter(ctrl: IDetailsControl): Function
{
return function (value: Object): void
{
ctrl.value = value;
}
}
您可以看到,我们将一个 setter 函数包装在另一个函数闭包中。因此,对 ctrl
控件的引用将被存储在函数闭包中,并且在绑定过程中,setter 将为每个控件正确调用一次。如果我们直接绑定一个 setter,存储在 setter 范围内的 ctrl
变量(它会在前面定义的“for
”循环中发生变化)将在所有范围中包含对最后一个控件的引用,这并不是我们真正想要的。
我们的完整应用程序代码如下所示。
<?xml version="1.0" encoding="utf-8"?>
<mx:WindowedApplication xmlns:mx="http://www.adobe.com/2006/mxml" layout="vertical"
creationComplete="onCreationComplete(event)">
<mx:Script>
<![CDATA[
import controls.IDetailsControl;
import controls.ControlFactory;
import mx.events.FlexEvent;
import mx.binding.utils.BindingUtils;
[Bindable]
public var obj: Object = "initial state";
private function onCreationComplete(event: FlexEvent): void
{
// specify controls we want to display
var conf: Array =
["label", "label", "unknown control"];
for each (var controlType: String in conf)
{
// receiving control
var ctrl: IDetailsControl =
ControlFactory.getControl(controlType);
// adding a control to layout,
// we work with DisplayObject only
if (ctrl is DisplayObject)
{
target.addChild
(ctrl as DisplayObject);
// applying binding
BindingUtils.bindSetter(
createSetter(ctrl),
this,
"obj"
);
}
}
}
private function createSetter(ctrl: IDetailsControl): Function
{
return function (value: Object): void
{
ctrl.value = value;
}
}
]]>
</mx:Script>
<mx:VBox id="target" width="100%" height="100%" horizontalAlign="center">
<mx:Button label="String" click="obj = Math.random().toString()"/>
<mx:Button label="Object" click="obj = {}"/>
<mx:Button label="Null" click="obj = null"/>
</mx:VBox>
</mx:WindowedApplication>
现在我们可以运行我们的应用程序,点击按钮,并看到标签会响应 obj
属性的更改。

添加更多控件
因此,当对象更改时,会为每个控件执行一个 setter。当需要引入新控件时,我们只需使其实现 IDetailsControl
接口,在 ControlFactory
中注册它,并在配置中定义它。让我们再创建一个控件,这次它将扩展 mx.controls.TextInput
。
package controls
{
import mx.controls.TextInput;
public class ETextField extends TextInput implements IDetailsControl
{
public function set value(value: Object): void
{
if (value is String)
{
text = value as String;
}
else if (value != null)
{
text = "#object#";
}
else
{
text = "#null value#"
}
}
}
}
现在,我们将一个控件添加到 ControlFactory
。
// ...
switch (type)
{
case "text":
ctrlClass= ETextField; break;
case "label":
ctrlClass= ELable;break;
}
// ...
最后,我们在配置 conf
数组中定义它。
var conf: Array = ["label", "text", "label", "unknown control"];
现在我们可以打开应用程序,点击某个按钮,然后看到结果。

一切正常,所有控件(包括新的文本控件)都显示 obj
属性的最新值。
扩展 Setter 逻辑
上一个屏幕截图显示了我们点击“Null
”按钮时的应用程序状态。显示的数值不太好看,所以让我们现在实现当 obj
属性设置为null 值时隐藏控件的逻辑。
由于我们使用的是 IDetailsControl
接口,我们将在此处添加一个新的 visible
方法,因此接口将如下所示。
package controls
{
public interface IDetailsControl
{
function set value(value: Object): void;
function set visible(value: Boolean): void;
}
}
现在我们需要在 Elable
和 ETextField
控件中实现这个 visible
方法。好处是此方法已在我们扩展的控件(mx.controls.Label
和 mx.controls.TextInput
)的类中实现。
所以,我们只需开始使用这个新的接口方法。我们将它添加到 createSetter
函数中。
private function createSetter(ctrl: IDetailsControl): Function
{
return function (value: Object): void
{
ctrl.visible = value != null;
ctrl.value = value;
}
}
就是这样。当我们点击“Null
”按钮时,组件将隐藏。

在处理动态创建的控件时,使用接口和数据绑定可以简化开发人员的任务,减少代码量,并使模型易于扩展。
历史
- 2010 年 3 月 16 日:初始发布