Меньше года назад меня вовлекли в проект, для которого необходимо было писать клиента на Flex. Так как я был новичком в этом деле, то в процессе работы я находил что-то новое и совершенно неизвестное мне. В то время я и открыл для себя Flex Data Binding (связыванием данных). Я думаю, что каждый, кто работает с Flex очень скоро сталкивается с Data Binding.
Связывание данных заключается в том, что мы можем с легкостью связывать два объекта (источника данных) между собой, что позволяет поддерживать их синхронизацию. Примером может служить связывание между со��ой элементов пользовательского интерфейса путем создания некоторых правил поведения, что способствует созданию более интерактивного пользовательского интерфейса.
Имея некоторый опыт в этой области, я решил разобрать все типы механизма связывания данных во Flex. Думаю эта статья будет интересна не только новичкам, но и профессионалы почерпнут из неё что-нибудь для себя.
Все документы по data binding (которые я прочитал по крайней мере) начинаются именно с данного типа использования этой замечательной функции. К сожалению это практически и единственный метод, который везде наглядно описан. Итак, предположим, что мы имеем два текстовых поля:
Мы хотим, чтобы изменения в первом поле отображались и на другом поле. Для данной ситуации достаточно написать такой mxml код:
Это приведет к тому, что текст, вписанный в первое текстовое поле будет автоматически устанавливаться во второе текстовое поле. В этом и есть смысл простого data binding. Код простого приложения с использованием data binding:
Пример описанный выше легко переписать на ActionScript. Смысл данной конструкции заключается в том, что мы можем использовать её для динамически создаваемых элементов. Итак, всё те же два текстовых поля:
Data binding для этих полей будет выглядеть так:
Первым параметром функции bindProperty является объект «пункт назначения», вторым параметром является строка с именем свойства данного объекта, третьим параметром является объект «пункт отправления» и четвертым параметром является объект-цепочка, который при упрощении может являться строкой с именем свойства объекта «пункта отправления» (о данном параметре будет оговорено более подробно ниже). Код приложения с использованием данного метода data binding:
Рассмотрим более подробно объект-цепочку, о котором говорилось ранее. Данный объект используется в качестве 4-го параметра функции bindProperty в классе BindingUtils. Данный объект описывает, каким образом и к какому параметру применить data binding в объекте «пункт отправления». Данный объект может быть представлен в трех различных видах:
Строка – хранит в себе имя свойства объекта «пункт отправления». То есть, имея текстовое поле с именем «sourceField» мы можем в качестве объекта-цепочки использовать имя опции «text». Об этом виде объекта-цепочки говорилось выше. Дополнительные разъяснения будут лишними.
Массив строк – хранит в иерархию доступа к внутреннему свойству объекта «пункт отправления». Назначение этого вида объекта-цепочки очень легко понять, разобрав простой пример, в котором используются всего лишь два объекта:
Допустим, у нас есть экземпляр класса ComplexObject, с которым нам надо связать текстовое поле, а именно свойство myText (которое находится во внутреннем объекте класса ComplexObject) необходимо связать со свойством text текстового поля. Код инициализации:
Как было сказано выше, нам необходимо связать complexInstance.simpleInstance.myText с destinationField.text. При помощи data binding это делается так:
В качестве объекта-цепочки в данном примере используется массив иерархии. В дальнейшем все элементы массива объединяются через точку в одну строку, которая используется в качестве свойства объекта «пункт отправления». Полный код приложения использующего данный метод data binding:
Составной объект – в этом случае объект-цепочка представляет из себя объект типа:
имя свойства — есть строка, в которой содержится имя свойства «объекта отправления»
функция получения значения – данная функция имеет вид:
Для понимания данного вида объекта-цепочки можно использовать очень простой пример data binding. Например, у нас есть экземпляр класс ArrayCollection и текстовое поле. Условием нашей задачи будет отключение текстового поля при достижении количества объектов содержащихся в нашей коллекции 10 штук. Объявление необходимых переменных:
Использование data binding в данном случае:
Как мы видим, код прост и понятен. В данном случае data binding будет работать так, как нам необходимо: отключать текстовое поле при количестве элементов в коллекции больше либо равным 10 и соответственно включать при изменении количества элементов в меньшую сторону. Полный код приложения использующего данный метод data binding:
Рассмотрим другой способ data binding. Данный способ заключается в том, что мы можем вызывать подобие call back функции при изменении свойств объекта «пункта отправления».
Рассмотрим простой пример. Допустим, у нас есть коллекция и текстовое поле. Условия задачи таковы: в коллекции хранятся числовые значения, данные значения добавляются в коллекцию извне. Нам необходимо отображать сумму всех элементов коллекции в текстовом поле (причем сумма должна быть всегда актуальной). Т.е. с точки зрения data binding мы должны, при изменении длинны коллекции, пересчитывать сумму всех её элементов. В данном случае используется метод bindSetter класса BindingUtils.
Для начала инициализация переменных:
А теперь опишем data binding операцию:
В нашем случае реализация функции calculateSum будет такова:
Т.е. все как мы и предполагали. При изменении длинны коллекции, пересчитывается сумма
её элементов и выводится в текстовое поле.
Полный код приложения использующего данный метод data binding:
Это самый сложный вид data binding, но это не значит, что он очень сложен для понимания. Традиционно вернемся к постановке задачи.
Необходимо написать класс-коллекционер который собирает данные о изменениях произошедших с наблюдаемым компонентом. Класс будет хранить массив из информационных объектов (с шириной, высотой и позицией наблюдаемого компонента).
Напишем для начала класс:
Приложение будет выглядеть так:
Обращаем внимание на функцию init() а именно строку
Она несет в себе информацию о том, что при изменении свойства «width» компонента button у нас будет вызываться функция returnInfo, которая сформирует данные о кнопке, эти данные будут автоматически переданы в функцию collector.collect() в которой они будут соответствующим образом обработаны.
Вот собственно и всё, что я хотел рассказать.
P.S. Топик специально писался для блога Adobe Flex.
Я думаю, он туда обязательно попадёт.
Связывание данных заключается в том, что мы можем с легкостью связывать два объекта (источника данных) между собой, что позволяет поддерживать их синхронизацию. Примером может служить связывание между со��ой элементов пользовательского интерфейса путем создания некоторых правил поведения, что способствует созданию более интерактивного пользовательского интерфейса.
Имея некоторый опыт в этой области, я решил разобрать все типы механизма связывания данных во 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.
Я думаю, он туда обязательно попадёт.
