Flex. Понимание itemRenderer'ов. Часть 3. Взаимодействие

Часть 3. Взаимодействие

Во второй части Я показывал Вам, как сделать внешние itemRenderer'ы в MXML и в ActionScript. В примерах которые я использовал, есть Button, которая обрабатывает пользовательское событие (BuyBookEvent), таким образом, приложение может реагировать на неё. Эта часть касается взаимодействия itemRenderer'ов более подробно.

Есть правило, которому я твердо верю и которе никогда не должно нарушаться: Вы не должны овладеть экземпляром класса itemRenderer и изменить его (устанавливая public свойства) или вызвать его public методы. По причине, о которой я говорил в первой части, itemRenderer'ы являются интенсивными: itemRenderers являются циклическими. Захватывая, каждый ломает Flex фреймфорк.

С этим правилом в памяти, вот вещи, которые Вы можете делать с itemRenderer:

Динамически изменяемый 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, изменяя общее свойство в списковом контроле.

listData

У 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:

  1. Иметь класс интерфейса.
    <mx:HBox xmlns:mx="http://www.adobe.com/2006/mxml" ...
    implements="mx.controls.listClasses.IDropInListItemRenderer">
    
  2. Добавить функции 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.

    Процесс тогда выглядит следующим образом:

    1. itemRenderer рассматривает своего владельца списка для criteria, чтобы использовать помощь для решения как рендерить данные.
    2. Класс владельца списка, расширение одного из классов списка во Flex, содержит общие свойства, читаемые itemRenderer'ом (или itemRenderer'ми), и установлен внешним кодом - другой контрол или код ActionScript (возможно, как результат получения данных из удалённого вызова).
    3. Когда свойство списка установлено, оно вызывает метод списка invalidateList(). Это вызывает обновление itemRenderer"ов, заставляя их сбрасывать данные (и затем назад к шагу 1).

    События

    В предыдущих частях я показывал, как использовать "всплывающее" событие, чтобы позволить itemRenderer'у общаться с остальной частью приложения. Я думаю, что это конечно быстро. Но я также думаю, что есть лучший путь, тот, который соответствует предположению, что задание itemRenderer'а должно представить данные, и задание контрола должно обработать данные.

    Идея контрола MyTileList состоит в том, что он - видимое представление каталога книг для продажи. Когда пользователь выбирает книгу и хочет купить её, она должна иметь соответствие в списковом контроле, и сообщить эту информацию приложению. Другими словами:

    <CatalogList bookBuy="addToCart(event)"/>
    

    Путём прямой установки событие "выталкивает" и обходит MyTileList. "Всплывающий" подход не ассоциирует событие (bookBuy) с списковым контролом (MyTileList), разрешая Вам переместить контрол в другие части Вашего приложения. Например, если Вы закодируете слушателя события для bookBuy в главном Приложении, то Вы не сможете переместить списковый контрол в другую часть приложения. Вы должны будете переместить этот обработчик событий, также. Если, с другой стороны, Вы связали событие с контролом, Вы всего лишь перемещаете контрол.

    Рассмотрим этот путь: предположим, что событие click на Button не было фактически событием, посланным Button, но "вытолкнуто" из чего-то внутри кнопки. Вы никогда не смогли бы сделать: <mx:Button click="doLogin()" label="Log in"/>;, Вы ещё должны были бы поместить функцию doLogin() где-нибудь, и это сделает событие приложения ещё тяжелее для использования.

    Я надеюсь, что я убедил Вас, таким образом чтобы изменить пример от "всплывания" на выполнение из спискового контрола.

    1. Добавьте метаданные к контролу CatalogList, чтобы позволить компилятору знать, что контрол выполняет событие:
          import events.BuyBookEvent;
          import mx.controls.TileList;
      
          [Event(name="buyBook",type="events.BuyBookEvent")]
      
          public class CatalogList extends TileList
          {
      
    2. Добавьте функцию к CatalogList, чтобы выполнить событие. Эта функция будет вызвана экземплярами класса itemRenderer:
              public function dispatchBuyEvent(item:Object):void
              {
                  var event:BuyBookEvent = new BuyBookEvent();
                  event.bookData = item;
                  dispatchEvent( event );
              }
          }
      
    3. Измените код кнопки Buy в itemRenderer'е, чтобы вызвать функцию:
      <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.

    Источник


    <-Назад Вперёд->