Во второй части Я показывал Вам, как сделать внешние itemRenderer'ы в MXML и в ActionScript. В примерах которые я использовал, есть Button, которая обрабатывает пользовательское событие (BuyBookEvent), таким образом, приложение может реагировать на неё. Эта часть касается взаимодействия itemRenderer'ов более подробно.
Есть правило, которому я твердо верю и которе никогда не должно нарушаться: Вы не должны овладеть экземпляром класса itemRenderer и изменить его (устанавливая public свойства) или вызвать его public методы. По причине, о которой я говорил в первой части, itemRenderer'ы являются интенсивными: itemRenderers являются циклическими. Захватывая, каждый ломает Flex фреймфорк.
С этим правилом в памяти, вот вещи, которые Вы можете делать с itemRenderer:
Вот MXML itemRenderer из предыдущей статьи, используемый для TileList. Я собираюсь сделать этот кусок более динамичным при наличии, что он реагирует на изменения из внешнего источника. (Я назвал этот файл BookItemRenderer.mxml):
<?xml version="1.0" encoding="utf-8"?> <mx:HBox xmlns:mx="http://www.adobe.com/2006/mxml" width="250" height="115" > <mx:Script> <![CDATA[ ]]> </mx:Script> <mx:Image id="bookImage" source="{data.image}" /> <mx:VBox height="115" verticalAlign="top" verticalGap="0"> <mx:Text text="{data.title}" fontWeight="bold" width="100%"/> <mx:Spacer height="20" /> <mx:Label text="{data.author}" /> <mx:Label text="Available {data.date}" /> <mx:Spacer height="100%" /> <mx:HBox width="100%" horizontalAlign="right"> <mx:Button label="Buy" fillColors="[0x99ff99,0x99ff99]"> <mx:click> <![CDATA[ var e:BuyBookEvent = new BuyBookEvent(); e.bookData = data; dispatchEvent(e); ]]> </mx:click> </mx:Button> </mx:HBox> </mx:VBox> </mx:HBox>
Предположим, что Вы показываете каталог элементов в TileList. У Вас также есть Slider (не часть itemRenderer), который позволяет пользователю давать диапазон цен; все элементы, которые выпадают за пределы диапазона, должны постепенно исчезнуть (альфа-значение itemRenderer'а должно изменяться). Вы должны сказать всем itemRenderer'ам, что criteria изменился так, чтобы они могли изменить свои альфа-значения.
Ваша перезагрузка set data могла бы выглядеть как-то так:
override public function set data(value:Object):void { super.data = value; if(data.price < criteria) alpha = 0.4; else alpha = 1; }
Вопрос: как изменить значение для criteria
?
"Передовой опыт" для itemRenderer'ов должен всегда делать так, чтобы они воздействовали на данные, которые им дают.
В этом случае, маловероятно и непрактично, чтобы иметь тестовый criteria, чтобы быть частью данных.
Так, что оставляет расположение за пределами данных.
У Вас есть два выбора:
Для меня, выбор первый: расширить класс и сделать criteria частью этого класса. В конце концов, класс используется, чтобы отобразить данные, criteria это часть того отображения. Для этого примера я бы расширил TileList и имел бы criteria как общий компонент данных.
package { import mx.controls.TileList; public class CatalogList extends TileList { public function CatalogList() { super(); } private var _criteria:Number = 10; public function get critera():Number { return _criteria; } public function set criteria(value:Number):void { _criteria = value; } } }
Идея состоит в том, что управление за пределами itemRenderer может изменить criteria, изменяя общее свойство в списковом контроле.
У itemRenderer'ов есть доступ к другой части данных: информация непосредственно о списке и какую строку и столбец (в столбец-ориентированном контроле) они рендерируют. Известное как listData, и могло бы использоваться в примере itemRenderer'а в BookItemRenderer.mxml:
override public function set data(value:Object):void { super.data = value; var criteria:Number = (listData.owner as MyTileList).criteria; if(data.price < criteria) alpha = 0.4; else alpha = 1; }
Поместите этот код в блок <mx:Script> в коде примера BooktItemRenderer.mxml, см. выше.
У свойства listData
itemRenderer'а есть поле owner
, которое является контролом, которому принадлежит itemRenderer.
В этом примере owner - контрол MyTileList
- моё расширение TileList.
Приведение поля owner к MyTileList
позволяет criteria быть выбранным.
IDropInListItemRenderer
Доступ к listData
возможен, когда класс itemRenderer имеет интерфейс IDropInListItemRenderer.
К сожалению, компоненты контейнера UI не имеют интерфейс, который предоставляет доступ к listData
.
Компоненты контролов, такие как Button и Label, делают, но для контейнеров Вы должны задать интерфейс самостоятельно.
Осуществление этого интерфейса является прямым, и найдено в документации по Flex. Вот то, что Вы должны сделать для класса BookItemRenderer:
<mx:HBox xmlns:mx="http://www.adobe.com/2006/mxml" ... implements="mx.controls.listClasses.IDropInListItemRenderer">
set
и get
в блок <mx:Script> в файле itemRenderer'а.
import mx.controls.listClasses.BaseListData; private var _listData:BaseListData; public function get listData():BaseListData { return _listData; } public function set listData(value:BaseListData):void { _listData = value; }
Когда списковый контрол видит, что itemRenderer осуществляет интерфейс IDropInListItemRenderer, он создаст элемент listData
, и назначит его на каждый экземпляр класса itemRenderer.
invalidateList()
Установка criteria в моём классе не такая же простая, как присвоение значения.
Выполнение, которое не будет говорить фреймворку Flex, что данные были изменены.
Изменение criteria должно вызвать событие.
Вот изменение к функции set criteria()
:
public function set criteria( value:Number ) : void { _criteria = value; invalidateList(); }
Обратите внимание, что, как только значение _criteria
было установлено, оно вызывает invalidateList()
.
Это заставляет все itemRenderers быть сброшенными со значениями из dataProvider, и вызывать их функции set_data.
Процесс тогда выглядит следующим образом:
В предыдущих частях я показывал, как использовать "всплывающее" событие, чтобы позволить itemRenderer'у общаться с остальной частью приложения. Я думаю, что это конечно быстро. Но я также думаю, что есть лучший путь, тот, который соответствует предположению, что задание itemRenderer'а должно представить данные, и задание контрола должно обработать данные.
Идея контрола MyTileList
состоит в том, что он - видимое представление каталога книг для продажи.
Когда пользователь выбирает книгу и хочет купить её, она должна иметь соответствие в списковом контроле, и сообщить эту информацию приложению.
Другими словами:
<CatalogList bookBuy="addToCart(event)"/>
Путём прямой установки событие "выталкивает" и обходит MyTileList
.
"Всплывающий" подход не ассоциирует событие (bookBuy) с списковым контролом (MyTileList), разрешая Вам переместить контрол в другие части Вашего приложения.
Например, если Вы закодируете слушателя события для bookBuy в главном Приложении, то Вы не сможете переместить списковый контрол в другую часть приложения.
Вы должны будете переместить этот обработчик событий, также.
Если, с другой стороны, Вы связали событие с контролом, Вы всего лишь перемещаете контрол.
Рассмотрим этот путь: предположим, что событие click на Button не было фактически событием, посланным Button, но "вытолкнуто" из чего-то внутри кнопки.
Вы никогда не смогли бы сделать: <mx:Button click="doLogin()" label="Log in"/>;, Вы ещё должны были бы поместить функцию doLogin()
где-нибудь, и это сделает событие приложения ещё тяжелее для использования.
Я надеюсь, что я убедил Вас, таким образом чтобы изменить пример от "всплывания" на выполнение из спискового контрола.
import events.BuyBookEvent; import mx.controls.TileList; [Event(name="buyBook",type="events.BuyBookEvent")] public class CatalogList extends TileList {
public function dispatchBuyEvent(item:Object):void { var event:BuyBookEvent = new BuyBookEvent(); event.bookData = item; dispatchEvent( event ); } }
<mx:Button label="Buy" fillColors="[0x99ff99,0x99ff99]"> <mx:click> <![CDATA[ (listData.owner as CatalogList).dispatchBuyEvent(data); ]]> </mx:click> </mx:Button>
Теперь Button в itemRenderer'е может просто вызвать функцию в списковом контроле вместе с данными для записи (или что-нибудь ещё, что является соответствующим для действия), и передать ответственность связи с помощью интерфейса с остальной частью приложения на списковый контрол.
Списковый контрол в этом примере выполняет событие с данными.
Приложение может добавить слушателей для этого события или используя ActionScript, или [Event]
-метаданные в файле CatalogList.as, MXML; использование [Event]
-метаданных облегчает разработчикам использовать Ваш код.
itemRenderers должен взаимодействовать любыми действиями, используя события. Пользовательские события позволяют Вам передавать информацию вместе с событием, таким образом пользователь события не должен обращаться к itemRenderer'у для любых данных.
itemRenderers должен "реагировать" на изменения в данных, перегружая их функции set data. Внутри функции они могут обратиться к значениям в своем listData.owner. Они могут также обратиться к данным, хранившимся в статическом классе или в основном приложении через Application.application.