Меньше года назад меня вовлекли в проект, для которого необходимо было писать клиента на Flex. Так как я был новичком в этом деле, то в процессе работы я находил что-то новое и совершенно неизвестное мне. В то время я и открыл для себя Flex Data Binding (связыванием данных). Я думаю, что каждый, кто работает с Flex очень скоро сталкивается с Data Binding.
Связывание данных заключается в том, что мы можем с легкостью связывать два объекта (источника данных) между собой, что позволяет поддерживать их синхронизацию. Примером может служить связывание между со��ой элементов пользовательского интерфейса путем создания некоторых правил поведения, что способствует созданию более интерактивного пользовательского интерфейса.
Имея некоторый опыт в этой области, я решил разобрать все типы механизма связывания данных во Flex. Думаю эта статья будет интересна не только новичкам, но и профессионалы почерпнут из неё что-нибудь для себя.

Простой data binding с использованием MXML


Все документы по data binding (которые я прочитал по крайней мере) начинаются именно с данного типа использования этой замечательной функции. К сожалению это практически и единственный метод, который везде наглядно описан. Итак, предположим, что мы имеем два текстовых поля:

<mx:TextInput id="sourceField" text="" />
<mx:TextInput id="destinationField" text="" />


Мы хотим, чтобы изменения в первом поле отображались и на другом поле. Для данной ситуации достаточно написать такой mxml код:

<mx:Binding destination="destinationField.text" source="sourceField.text"/>

Это приведет к тому, что текст, вписанный в первое текстовое поле будет автоматически устанавливаться во второе текстовое поле. В этом и есть смысл простого data binding. Код простого приложения с использованием data binding:

<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute">
   <mx:Binding destination="destinationField.text" source="sourceField.text"/>
   <mx:VBox>
      <mx:TextInput id="sourceField" text="" />
      <mx:TextInput id="destinationField" text="" />
   </mx:VBox>
</mx:Application>


Простой data binding с использованием ActionScript


Пример описанный выше легко переписать на ActionScript. Смысл данной конструкции заключается в том, что мы можем использовать её для динамически создаваемых элементов. Итак, всё те же два текстовых поля:

public var sourceField : TextInput = new TextInput();
public var destinationField : TextInput = new TextInput();


Data binding для этих полей будет выглядеть так:

BindingUtils.bindProperty(destinationField, "text", sourceField, "text");

Первым параметром функции bindProperty является объект «пункт назначения», вторым параметром является строка с именем свойства данного объекта, третьим параметром является объект «пункт отправления» и четвертым параметром является объект-цепочка, который при упрощении может являться строкой с именем свойства объекта «пункта отправления» (о данном параметре будет оговорено более подробно ниже). Код приложения с использованием данного метода data binding:

<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute"
initialize="init()">
   <mx:Script>
      <![CDATA[
         import mx.controls.TextInput;
         import mx.binding.utils.BindingUtils;
      
         public var sourceField : TextInput = new TextInput();
         public var destinationField : TextInput = new TextInput();
         public function init():void
         {
            sourceField.text = "";
            parentContainer.addChild(sourceField);
            
            destinationField.text = "";
            parentContainer.addChild(destinationField);
            
            BindingUtils.bindProperty(destinationField, "text", sourceField, "text");
         }
               
      ]]>
   </mx:Script>
   <mx:VBox id="parentContainer"/>
</mx:Application>


Объект-цепочка в методе bindProperty класса BindingUtils.


Рассмотрим более подробно объект-цепочку, о котором говорилось ранее. Данный объект используется в качестве 4-го параметра функции bindProperty в классе BindingUtils. Данный объект описывает, каким образом и к какому параметру применить data binding в объекте «пункт отправления». Данный объект может быть представлен в трех различных видах:

Строка

Строка – хранит в себе имя свойства объекта «пункт отправления». То есть, имея текстовое поле с именем «sourceField» мы можем в качестве объекта-цепочки использовать имя опции «text». Об этом виде объекта-цепочки говорилось выше. Дополнительные разъяснения будут лишними.

Массив строк

Массив строк – хранит в иерархию доступа к внутреннему свойству объекта «пункт отправления». Назначение этого вида объекта-цепочки очень легко понять, разобрав простой пример, в котором используются всего лишь два объекта:

package src
{
   public class SimpleObject
   {
      public var myText:String = new String();
      public function SimpleObject():void
      {
         myText = "empty";
      }
   }
}

package src
{
   public class ComplexObject
   {
      public var simpleInstance: SimpleObject = new SimpleObject();
      public function ComplexObject():void
      {
         //some logic
      }
   }
}


Допустим, у нас есть экземпляр класса ComplexObject, с которым нам надо связать текстовое поле, а именно свойство myText (которое находится во внутреннем объекте класса ComplexObject) необходимо связать со свойством text текстового поля. Код инициализации:

public var destinationField : TextInput = new TextInput();
public var complexInstance : ComplexObject = new ComplexObject();


Как было сказано выше, нам необходимо связать complexInstance.simpleInstance.myText с destinationField.text. При помощи data binding это делается так:
BindingUtils.bindProperty(
		destinationField, 
		"text", 
		complexInstance, 
		["simpleInstance","myText"]);


В качестве объекта-цепочки в данном примере используется массив иерархии. В дальнейшем все элементы массива объединяются через точку в одну строку, которая используется в качестве свойства объекта «пункт отправления». Полный код приложения использующего данный метод data binding:

<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute"
initialize="init()">
   <mx:Script>
      <![CDATA[
         import src.ComplexObject;
         import mx.controls.TextInput;
         import mx.binding.utils.BindingUtils;
      
         public var destinationField : TextInput = new TextInput();
         public var complexInstance :  ComplexObject = new ComplexObject();
         public function init():void
         {
            destinationField.text = "";
            parentContainer.addChild(destinationField);
            
            BindingUtils.bindProperty(destinationField, 
				"text",
				complexInstance, 
				["simpleInstance","myText"]);
         }
      ]]>
   </mx:Script>
   <mx:VBox id="parentContainer"/>
</mx:Application>


Составной объект

Составной объект – в этом случае объект-цепочка представляет из себя объект типа:

var object : Object = {name:<имя свойства>, getter: <функция получения значения>};

имя свойства — есть строка, в которой содержится имя свойства «объекта отправления»
функция получения значения – данная функция имеет вид:

function (host:<тип объекта «пункта отправления» >) : <тип возвращаемого значения>
{
	//тело функции в которой мы можем обращаться к параметру host,
	// зная что это объект «пункт отправления»
}


Для понимания данного вида объекта-цепочки можно использовать очень простой пример data binding. Например, у нас есть экземпляр класс ArrayCollection и текстовое поле. Условием нашей задачи будет отключение текстового поля при достижении количества объектов содержащихся в нашей коллекции 10 штук. Объявление необходимых переменных:

public var array : ArrayCollection = new ArrayCollection();
public var destinationField : TextInput = new TextInput();


Использование data binding в данном случае:
BindingUtils.bindProperty(
		destinationField, 
		"enabled", 
		array, 
		{
			name:"length",
			getter : function (host : ArrayCollection):Boolean 
						{ return host.length<10; } 
		});


Как мы видим, код прост и понятен. В данном случае data binding будет работать так, как нам необходимо: отключать текстовое поле при количестве элементов в коллекции больше либо равным 10 и соответственно включать при изменении количества элементов в меньшую сторону. Полный код приложения использующего данный метод data binding:

<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute"
initialize="init()">
   <mx:Script>
      <![CDATA[
         import mx.collections.ArrayCollection;
         import mx.controls.TextInput;
         import mx.binding.utils.BindingUtils;
      
         public var destinationField : TextInput = new TextInput();
         public var array : ArrayCollection = new ArrayCollection();
         public function init():void
         {
            destinationField.text = "some text";
            parentContainer.addChild(destinationField);
            
            BindingUtils.bindProperty(
				destinationField, 
				"enabled", 
				array, 
				{
					name:"length",
					getter : function (host : ArrayCollection):Boolean 
								{ return host.length>=10; } 
				});
         }
      ]]>
   </mx:Script>
   <mx:VBox id="parentContainer">
      <mx:Button label="add element" click="array.addItem(0);"/>
      <mx:Text text="array length: {array.length}"/>
   </mx:VBox>
</mx:Application>


Метод bindSetter класса BindingUtils.


Рассмотрим другой способ data binding. Данный способ заключается в том, что мы можем вызывать подобие call back функции при изменении свойств объекта «пункта отправления».
Рассмотрим простой пример. Допустим, у нас есть коллекция и текстовое поле. Условия задачи таковы: в коллекции хранятся числовые значения, данные значения добавляются в коллекцию извне. Нам необходимо отображать сумму всех элементов коллекции в текстовом поле (причем сумма должна быть всегда актуальной). Т.е. с точки зрения data binding мы должны, при изменении длинны коллекции, пересчитывать сумму всех её элементов. В данном случае используется метод bindSetter класса BindingUtils.

Для начала инициализация переменных:

public var destinationField : TextInput = new TextInput();
public var array : ArrayCollection = new ArrayCollection();


А теперь опишем data binding операцию:

BindingUtils.bindSetter(calculateSum , array, "length");
Где calculateSum функция вида
function  ( input : <тип свойства объекта «пункт отправления»>):void
{
	//тело функции   
}


В нашем случае реализация функции calculateSum будет такова:

public function calculateSum(input : Number):void
{
	var sum:Number = 0;
	for (var i:int = 0; i<input; i++)
	{
		sum += array[i];
	}
	destinationField.text =  sum.toString();
}


Т.е. все как мы и предполагали. При изменении длинны коллекции, пересчитывается сумма
её элементов и выводится в текстовое поле.
Полный код приложения использующего данный метод data binding:

<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute"
initialize="init()">
   <mx:Script>
      <![CDATA[
         import mx.collections.ArrayCollection;
         import mx.controls.TextInput;
         import mx.binding.utils.BindingUtils;
      
         public var destinationField : TextInput = new TextInput();
         public var array : ArrayCollection = new ArrayCollection();
         public function init():void
         {
            destinationField.text = "some text";
            parentContainer.addChild(destinationField);
            
            BindingUtils.bindSetter(calculateSum , array, "length");
         }
         
         public function calculateSum(input : Number):void
         {
            var sum:Number = 0;
            for (var i:int = 0; i<input; i++)
            {
               sum += array[i];
            }
            destinationField.text =  sum.toString();
         }

      ]]>
   </mx:Script>
   <mx:VBox id="parentContainer">
      <mx:Button label="add element" click="array.addItem(Math.random());"/>
      <mx:List width="100" height="200" dataProvider="{array}"/>
   </mx:VBox>
</mx:Application>


Использование метода bindSetter с составным видом объекта-цепочки


Это самый сложный вид data binding, но это не значит, что он очень сложен для понимания. Традиционно вернемся к постановке задачи.
Необходимо написать класс-коллекционер который собирает данные о изменениях произошедших с наблюдаемым компонентом. Класс будет хранить массив из информационных объектов (с шириной, высотой и позицией наблюдаемого компонента).
Напишем для начала класс:

package src
{
   import mx.collections.ArrayCollection;  
   public class Collector
   {
      public var array : ArrayCollection;      
      public function Collector():void
      {
         array = new ArrayCollection();
      }      
      public function collect(str:String):void
      {
         array.addItem(str);
      }
   }
}


Приложение будет выглядеть так:

<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute"
initialize="init()">
   <mx:Script>
      <![CDATA[
         import mx.core.UIComponent;
         import mx.binding.utils.BindingUtils;
         import src.Collector;
         public var collector : Collector = new Collector();
         
         public function init():void
         {
            BindingUtils.bindSetter(collector.collect, button, {name:"width", getter: returnInfo});
            data.dataProvider = collector.array;
         }
         
         public function returnInfo(host:UIComponent):String
         {
            return "width:" + host.width + "; height:" + host.height;
         }
         
         public function changeButton():void
         {
            button.width = Math.round(Math.random() * 200);
            button.height = Math.round(Math.random() * 100);   
         }
      ]]>
   </mx:Script>
   <mx:VBox>
   <mx:Button label="change" click="changeButton();"/>
   <mx:List id="data" width="200" height="400" />
   <mx:Button id="button" width="200" height="100" label="mybutton"/>
   </mx:VBox>
</mx:Application>


Обращаем внимание на функцию init() а именно строку

BindingUtils.bindSetter(collector.collect, button, {name:"width", getter: returnInfo});

Она несет в себе информацию о том, что при изменении свойства «width» компонента button у нас будет вызываться функция returnInfo, которая сформирует данные о кнопке, эти данные будут автоматически переданы в функцию collector.collect() в которой они будут соответствующим образом обработаны.

Вот собственно и всё, что я хотел рассказать.

P.S. Топик специально писался для блога Adobe Flex.
Я думаю, он туда обязательно попадёт.