65.9K
CodeProject 正在变化。 阅读更多。
Home

Flex 样式编程:处理动态创建的控件

starIconstarIconstarIconstarIconemptyStarIcon

4.00/5 (2投票s)

2010年3月16日

CPOL

5分钟阅读

viewsIcon

22706

downloadIcon

125

本文演示了如何通过使用接口和数据绑定来简化动态创建的 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;
	}
}

现在我们需要在 ElableETextField 控件中实现这个 visible 方法。好处是此方法已在我们扩展的控件(mx.controls.Labelmx.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 日:初始发布
© . All rights reserved.