Март 22 2011
Биндинг в ActionScript проектах. Часть 2
Рамки использования метатега [Bindable] значительно шире указнных в статье Биндинг в ActionScript проектах. Часть 1. В данной статье расскажу о том куда можно применить данный метатег и о возможностях биндинга в случае использования собственных событий.
Куда можно воткнуть метатег [Bindable]?
Метатег [Bindable] можно использовать в следующих местах:
1. Перед свойством.
Свойство может быть как public, так и protected или private.
Например:
1 2 | [Bindable] public var text:String; |
О биндинге применительно к свойствам уже говорилось в первой части. Компилятор автоматически преобразует свойство, помеченное тегом [Bindable] к геттеру/сеттеру с отсылкой события.
2. Перед определением класса
1 2 | [Bindable] public class SomeClass extends EventDispatcher |
Это равносильно тому, что мы бы проставили метатег [Bindable] для каждого из public свойств этого класса. Private и protected или свойства из другого пространства имен автоматически не будут помечены как [Bindable]. Если нужно забиндить не public свойства к ним нужно дописывать метатег [Bindable] самостоятельно.
Я бы не рекомендовал использовать эту возможность в ваших проектах, проставьте метатег биндинга вручную там где это нужно. Лучше держать все под контролем и видеть те свойства для которых будет срабатывать биндинг.
3. Перед get или set методом
Метатег [Bindable] так же можно использовать если свойства у класса оформленны в виде get/set методов:
1 2 3 4 5 6 7 8 | private var _text:String; [Bindable] public function get text():String{ return _text; } public function set text(value:String):void{ _text = value; } |
Компилятор позволит вам поставить его как возле геттера, так и возле сеттера. Но! Как изветсно если мы для свойства напишем только сеттер то оно будет доступно только для записи (write only), если же напишем только геттер то только для чтения (read only). С помощью BindingUtils “забиндиться” на свойство доступное только для записи невозможно, но сделать это для свойства, доступного только для чтения – вполне. Я настоятельно рекомендую всегда писать тег [Bindable] только рядом с геттером, это позволит видеть событие (см. ниже) и избежать путаницы и ошибок.
Если указан только геттер и свойство доступно только для чтения, целесообразно использовать биндинг только совместно с событиями (см. раздел “Кастомные события”), иначе биндинг сработает только во время инициализации, а среда разработки будет выдавать предупреждения.
4. Перед методом
1 2 3 4 5 | [Bindable(event="bindFunctionChange")] public function bindFunction(prop:int):String{ ... return someString; } |
Так же как и в случае со свойством доступным только для чтения, метатег для метода имеет смысл указывать только вместе с событием. Ниже можно посмотреть пример использования.
Кастомные события
Кастомные события можно использовать для геттеров и методов. К моему великому недоумению кастомные события для биндинга в привязке к свойствам не работают, хотя в официальной документации сказано что их использование вполне возможно.
Итак о событиях. Рассмотрим их при использовании с геттером. Представим что нам необходимо задать в каких конкретно случаях будет срабатывать биндинг, или чтобы он сработал после того как мы выполним все необходимые оперции в сеттере. Для этого в нашем метатеге мы можем указать событие в результате которого сработает биндинг:
1 | [Bindable(event="textChanged")] |
Для того чтобы биндинг сработал и все слушатели, подписавшиеся посредством BindingUtils, получили уведомление об изменении свойства, в классе со свойством помеченным этим метатегом нужно послать событие:
1 | dispatchEvent(new Event("textChanged")); |
Пример:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | private var _text:String; [Bindable(event="textChanged")] public function get text():String { return _text; } public function set text(value:String):void { if (value == _text) { return; } if (value == "дурак"){ trace("Сам дурак!"); } _text = value; dispatchEvent(new Event("textChanged")); } |
В данном примере как только изменится свойство text (именно изменится) в консоль при необходимости выведется соответствующее сообщение и посредством отсылки события слушатели будут удовемлены об ее изменении.
Как только мы указали свое событие для биндинга он перестает стандартно срабатывать при изменении свойства для которого указан метатег. Биндинг сработает только в случае когда будет сгенерировано указннное в метатеге событие.
Очень важный нюанс при использовании биндинга с самостоятельным указанием имени события – это то, что очень желательно проверять старое и новое значение. В случае если оно не изменилось – слушателей, посредством отсылки событий, уведомлять не нужно. Когда событие в метатеге не указывается, код проверяющий старое и новое значение генерируется автоматически (см. Часть 1, код в разделе “Как это работает”) и биндинг без изменения значения свойства срабатывать не будет.
Итак, при использовании своих событий следите за тем чтобы биндинг лишний раз не срабатывал.
Естественно событие можно послать не только из сеттера, а из любого места в классе. Это может быть полезно, например, при использовании массивов в качестве свойств. Если будет указано:
1 2 | [Bindable] public var items:Array; |
биндинг сработает только в случае
1 | items = new Array(); |
Если просто добавлять элементы в массив
1 | items.push(new Item()); |
биндинг не сработает, так как сам экземпляр класса Array в свойстве items остается неизменным. В этом случае мы можем указать метатегу событие
1 2 | [Bindable(event="itemsChanged")] public var items:Array; |
и отсылать событие
1 | dispatchEvent(new Event("itemsChanged")); |
при изменении набора элементов в массиве. Тогда элемент отображения, подписавшийся посредством BindingUtils на свойство items, будет получать уведомления не только при изменении экземпляра в свойстве items, но и в тех случаях когда меняется элементы в массиве.
Рассмотрим биндинг для методов. Биндинг для методов целесообразен только вкупе с кастомным событием. Он может быть очень полезен и удобен в случаях когда нам не просто нужно получить значение свойства, на которое мы забиндились, а получить некоторое значение в зависимости от некоторых параметров, которые мы можем передать на вход методу. Например, для изменения локализации приложения “на лету”.
Пусть у нас есть класс LocaleManager который при смене локали должен оповестить всех заинтересованных слушателей о том что локаль изменилась и дать возможность взять новое значение нужной строки для текущей локали currentLocale. Все строки у нас хранятся в некотором хранилище localeRepository из которого мы можем взять нужную нам строку по ID в соответствии с текущей локалью.
В классе LocaleManager мы заводим метод:
1 2 3 4 | [Bindable(event="localeChange")] public function getLocaleString(stringID:int):String{ return localeRepository.getString(currentLocale, stringID); } |
При изменении локали нужно сгенерировать событие
1 | dispatchEvent(new Event("localeChange")); |
Все слушатели заинтересованные в получении актуальных значений строк для текущей локали должны подписаться на изменения getLocaleString посредством BindingUtils.
Например у нас есть класс, отображающий некоторые текстовые поля myTextField1 и myTextField2, значение которых должно меняться в зависимости от выбранного языка.
Подписываемся на изменения локали в localeManager:
1 | BindingUtils.bindSetter(onLocaleChange, localeManager, "getLocaleString"); |
В случае смены локали будет вызван метод onLocaleChange, параметром к которому будет передан экземпляр класса Function. Далее мы можем вызвать полученный экземпляр, указывая ему на вход необходимый ID нужной строки.
1 2 3 4 | private function onLocaleChange(func:Function):void{ myTextField1.text = func.call(this, 111); myTextField2.text = func.call(this, 222); } |
В результате при смене локали автоматически поменяется текст в нужных полях элементов отображения.
При использовании кастомных событий возможно указание нескольких метатегов [Bindable (event="")] перед изменяемым обектом:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 | [Bindable(event="xChanged")] [Bindable(event="yChanged")] [Bindable(event="widthChanged")] [Bindable(event="heightChanged")] public function get viewRect():Rectangle{ return _viewRect; } public function set x(value:Number):void{ if (_viewRect.x != value){ _viewRect.x = value; dispatchEvent(new Event("xChanged")); } } public function set y(value:Number):void{ if (_viewRect.y != value){ _viewRect.y = value; dispatchEvent(new Event("yChanged")); } } public function set width(value:Number):void{ if (_viewRect.width != value){ _viewRect.width = value; dispatchEvent(new Event("widthChanged")); } } public function set height(value:Number):void{ if (_viewRect.height != value){ _viewRect.height = value; dispatchEvent(new Event("heightChanged")); } } |
В этом примере биндинг для свойства viewRect сработает в случае изменения любого из свойств x, y, width или height.
На этом пока все, продолжение следует.
