• Constantiner
  • Junik
  • Maar
  • Agahov
  • Graann
  • SlonVsapogah
  • Vertex

Fiat lux! Adobe Flex, ActionScript

  • В начало
  • Контакты
  • Войти

Adobe открывает RTMP протокол. Wow!

20-го января Adobe анонсировала планы по открытию своего проприетарного протокола RTMP (Real-Time Messaging Protocol) в рамках проекта Open Screen Project.
Спецификация ожидается в первой половине этого года и будет выложена на adobe.com/devnet/rtmp.

  • От Pavel Kozhin
  • Январь 22nd, 2009
  • Написан в News
  • 862 просмотров
  • 2 отзывов »
  Russian (RU)  
  Теги: adobe, news

Обратный инжениринг со Sparx EA за пару кликов

Баян. Ооочень древний.

Я почти уверен, ведь вы уже давно знаете, что Sparx Enterprise Architect поддерживает ActionScript 2/3. Костя писал про это еще в неблизком 2006 году. Кроме всего навороченного, он умеет генерировать код из UML-схем. Но субъективно гораздо более интересной была фича “Reverse Engineering and Synchronizing", которая позволяет генерировать UML-схемы на базе существующих исходников, с поддержкой различных языков: ActionScript, C, C#, C++, Delphi, Java, PHP, Python, Visual Basic, Visual Basic.NET. Также, например, можно преобразовать ActionScript код в любой из поддерживаемых языков и vice versa, импортировать и генерировать WSDL, импортировать модель базы данных через ODBC и много всего не менее прекрасного.

А сейчас, как можно проще и быстрее, сгенерируем UML-схему на базе ActionScript классов. Качаем триал (порядка 29MB ) и запускаем.

В картинках.

Создаем новый проект и выбираем модель “Class".

step 1.
Выбор модели

Можно, например, сразу создать новый package и импортировать туда папку с исходным кодом. Правый клик на package -> Code Engeneering -> Imoprt Source Directory, но сделаем же это пошагово. И не будем особо придираться к структуре папок по умолчанию, а оставим пока все как есть. Двойным щелчком открываем диаграмму System.

step 2.
Структура по умолчанию

Удаляем ненужный хлам и правым кликом выбираем импорт.

step 3.
Структура по умолчанию

После того как выбранные классы/интерфейсы импортировались и установлены зависимости, можно причесать схему автоматикой.

step 4.
Структура по умолчанию

При импорте папки с кодом целиком (Code Engeneering -> Imoprt Source Directory), возникает сложность с отображением всех схем на одной диаграмме, так как EA создает отдельную диаграмму со схемами для каждой вложенной папки (если в папке нет кода, то диаграмма будет пуста). Не искал как их объединить. Вдруг кто знает? Но можно напихать интересующие классы в один .as, импортировать и получить желаемый результат на одной диаграмме, ну или прочувствовать мощь step-а 3. :)

Стоит эта штука US $199 за одну Professional Edition.

  • От Pavel Kozhin
  • Январь 16th, 2009
  • Написан в Useful, Links
  • 437 просмотров
  • Оставить отзыв »
  Russian (RU)  
  Теги: actionscript, flex, uml

List растягивающийся под контент. AutoResizeableList.

Дело было ранней зимой и я как обычно засыпал на работе у батареи. Потом я проснулся, умылся, взбодрился и сел писать. К сожалению, я не сильно имею представление на сколько это актуально и свежо подходом к решению проблемы, но, по крайне мере, один человек очень получал от этого много удовольствия :)
Кстати, перед тем как начать читать, тем кто мало знаком lifecycle компонентов во Flex, я бы посоветовал познакомиться с этим, вот этим и с тем.

Начнем-с.

Необходимо было сделать компонент List само(ра)стягивающимся под данные по высоте, то есть визуализовать все данные находящиеся в дата провайдере. Если упростить понятие данных, то в этом случае это простой текст, который надо отобразить в полном объеме. Другими словами, в качестве рендерера должен выступать любой контейнер содержащий любой UIComponent, способный отображать не обрезанный многострочный текст.
Листинг 1. Например это такой рендерер:

  1. <mx:VBox xmlns:mx="http://www.adobe.com/2006/mxml"
  2.    mouseChildren="false">
  3.    <mx:Script>
  4.       <![CDATA[
  5.          ...
  6.       ]]>
  7.    </mx:Script>
  8.    <mx:Canvas width="100%">
  9.       <mx:Text id="tf" width="100%"/>
  10.    </mx:Canvas>
  11. </mx:VBox>

Если же рендерер не содержит в себе особых компонентов и его размеры можно корректно высчитать за один проход валидации, то вот самый примитивный способ. Выставляем List#variableRowHeight=true (в случае если высота варьируется) и снаружи его можно обсчитать с помощью, например, List#measureHeightOfItems() и делать это на событие FlexEvent.UPDATE_COMPLETE.
Листинг 2. Примерно так, без напильников и предохранителей:

  1. <mx:Script>
  2.    <![CDATA[
  3.       private function setMeasuredHeight(list:List):void
  4.       {
  5.          var h:Number = list.measureHeightOfItems();
  6.          list.height = h + list.getStyle("paddingTop") +
  7.             list.getStyle("paddingBottom");
  8.       }
  9.    ]]>
  10. </mx:Script>
  11. <mx:List id="simpleList"
  12.    width="400" variableRowHeight="true"
  13.    itemRenderer="SimpleRenderer"
  14.    dataProvider="{dp}"
  15.    updateComplete="setMeasuredHeight(simpleList)"/>

Особые компоненты.

Что же такое “особые” компоненты и их особенное поведение? Есть стандартные компоненты, способные отображать многострочный текст на базе flash.text.TextField, но из-за странностей в реализации текстовых полей во FlashPlayer, они становятся не обычными. Для них не достаточно одного прохода валидации. Им нужно два, иначе после первого прохода мы получаем некорректный TextField#textHeight. И то, как такие компоненты с этим живут, можно увидеть, например, в mx.controls.Text#updateDisplayList().
Листинг 3. mx.controls.Text#updateDisplayList() строка 326.

  1. if (isSpecialCase()){
  2.    var firstTime:Boolean = isNaN(lastUnscaledWidth) ||
  3.                    lastUnscaledWidth != unscaledWidth;
  4.    lastUnscaledWidth = unscaledWidth;
  5.    if (firstTime){
  6.       invalidateSize();
  7.       return;
  8.    }
  9. }

Вкратце, если updateDisplayList компонента произошел в первый раз после апдейта данных (присваивание текста в текстовое поле), пинаем еще один measure и выходим из метода. И только во время следующего measure будет вычислен корректный размер и запущен второй updateDisplayList для коррекции визуального представления. Это незаметно снаружи, в случаях когда мы присваиваем текстовому полю какой-нибудь текст и сразу вызываем validateNow(), то мы получаем правильные размеры, но если заглянуть внутрь, то мы увидим, что measure() и updatedDisplayList() выполняются два раза и происходит это в одном кадре не без помощи LayoutManager-а.
Под проходом валидации я понимаю процесс, который затрагивает по крайней мере один из защищенных методов: commitProperties, measure и updateDisplayList. В нашем случае, нас больше итересуют два последних.

Пара слов о компоненте List.

Если List-у не задана высота напрямую пользователем и не жестко диктуется родительским контейнером, то инициализируясь он всегда вычисляет List#measuredHeight руководствуясь List#rowCount и List#rowHeight. Если они не заданы вручную, то List#measuredHeight будет всегда равен 140 px, потому как высота строки по умолчанию 20 и количество строк по умолчанию 7. В ситуации когда высота List-а задана напрямую (e.g List#height = 320) фаза measure будет пропущена. Если же нет, то мы получим высоту в 140 px независимо от количества данных в провайдере и количества данных в рендерерах, которые поместились в этих 140 px.
Еще немного занудных подробностей о lifecycle компонента List. В первый проход updateDisplayList List создаст и отвалидирует рендереры, но сделает это не корректно. Если остановить валидацию на первом проходе мы увидим примерно неопрятную картину.
Картинка 1.
incorrect layout

В нормальных условиях особые рендереры, используя трюк с двойной валидацией, возбуждают List к повторному measure и updateDisplayList (см. Листинг 3). Ко второму updateDisplayList рендереры уже будут иметь корректные measured размеры и там List задаст им правильный лэйаут, то есть им будет выставлена правильная высота и тд.
Картинка 2.
correct layout

Отмечу также, что мы не выставляем никаких значений для List#rowCount и List#rowHeight, так как они попросту не нужны в случае когда List#variableRowHeight=true. Вообще, фаза measure с вариативным рендерером (для стандартного List-a) на мой вкус довольно бесполезна, потому как уже говорилось выше List#measuredHeight всегда будет высчитана по умолчанию в 140 px. Мы постараемся внести в нее немного больше смысла, заставив List растягиваться под контент по высоте.

Итак. Скрытый рендерер.

Необходимо высчитать размер всех рендереров на базе данных и учесть стили. Так как, например, в самом начале при инициализации в фазе measure List не имеет никаких созданных рендереров, то нужда заставляет нас использовать стандартный внутренний механизм скрытого рендерера. Cкрытый рендерер - это рендерер созданный внутри самим List-ом на базе List#itemRenderer с visible=false. С его помощью List производит необходимые вычисления для лэйаута, например, это касается вертикального скролбара или вычисления List#rowHeight.
Листинг 4. Создание скрытого рендерера внутри компонента List. mx.controls.List#getMeasuringRenderer() строка 1732.

  1. if (!item) {
  2.    item = createItemRenderer(data);
  3.    item.owner = this;
  4.    item.name = "hiddenItem";
  5.    item.visible = false;
  6.    item.styleName = listContent;
  7.    listContent.addChild(DisplayObject(item));
  8.    measuringObjects[factory] = item;
  9. }

Кульминация. Проблемные места.

Основная идея очевидна и проста. Необходимо пройти по всем данным пихая их в скрытый рендерер, валидируя и снимая с него measured размеры. Таким образом, складывая measured размеры одного и того же рендерера отвалидированного с разными данными в одну кучу, но с учетом паддингов и хрома, мы получаем общую measured высоту для List-a.
Листинг 5. com.riapriority.vertex.control.AutoResizableList#measure() строка 162.

  1. for (var i:int = 0; i < max; ++i) {
  2.    // Reusing standart mechanism to get hidden renderer.
  3.    renderer = getMeasuringRenderer(dp[i]);
  4.    rect = validateAndMeasure(dp[i], renderer);
  5.    h += rect.height + paddingTop + paddingBottom;
  6. }
  7. // We add top and bottom chrom + bottom offset
  8. measuredHeight = h + (viewMetrics.top + viewMetrics.bottom) + _bottomOffset;
  9. measuredMinHeight = measuredHeight;
  10. shouldBeUpdated = true;

На этом первый проход measure заканчивается и за ним следует updateDisplayList, в котором List по необходимости создает рендереры и выставляет им размеры и позиционирование. Но, как уже говорилось, если мы имеем дело с особыми компонентами, то в этот (первый) проход они отдадут неверные размеры, которые и будут им выставлены List-ом. Но эти рендереры сразу же попытаются возбудить List к повторному measure, который в свою очередь должен вызвать дополнительный updateDisplayList для корректировки лэйаута. В нашем случае с расширенным компонентом так и происходит, за исключением повторного вызова updateDisplayList и это не гут (на сколко это плохо мы наблюдали на картинке..). Почему? Все довольно просто, когда на List-е возбуждается вторая по счету всплывшая фаза measure (measure происходит снизу вверх) с подачи особых рендереров, то List снова обсчитывает все рендереры, снимая с них уже корректные размеры, делая это примерно также как я описал в предыдущем листинге и сравнивает результат со своими, а они были выставлены нами очень правильно в еще первый проход measure. В итоге, оба размера правильные и совпадают, а значит вызова updateDisplayList не произойдет вследствие отпимизационных введений. И это значит то, что в нашей ситуации ортодоксальное использование концепции жизненного цикла компонентов слегка хромает. Ибо основная концепция measure - вычисление предпочтительных размеров. Чтобы исправить положение мы воспользуемся ручным управлением.
Листинг 6.
com.riapriority.vertex.control.AutoResizableList#updateDisplayList() строка 204.

  1. if (shouldBeUpdated){
  2.    shouldBeUpdated = false;
  3.    callLater(function():void{
  4.       invalidateDisplayListFlag = true;
  5.       validateDisplayList();
  6.       });
  7. }

Если попытаться пнуть это стандартным образом, например, используя List#invalidateDisplayList(), то фаза updateDisplayList не будет запущена по причине того, что мы в ней находимся в данный момент. Можно запихать List#invalidateDisplayList() в callLater и это будет работать, но мы проиграем в производительности, потому что окажемся в этой фазе только через два кадра, а жуть как хотелось бы на кадр пораньше.

Добавка о производительности.

С таким подходом мы удваиваем все вызовы связанные с валидацией, потому как сначала мы прогоняем валидацию сами, используя скрытый рендерер, а затем List, создавая визуальные рендереры, количество которых также соответствует данным в провайдере (так как мы выставили достаточную measuredHeight), прогоняет валидацию для них. Вообще, в рамках идеи “fit to content” производительность уже страдает, так как создаются все визуальные рендереры соответственно данным и если мы имеем очень много данных, то это опасный подход. В случае очень большого количества данных лучше размазывать процесс во времени и скорее всего не использовать List, но здесь все строго индивидуально. В этом примере, при количестве данных в ~500 единиц, мы получаем довольно ощутимую паузу перед рендерингом контрола на сцену, хотя, с моей точки зрения, даже так вполне бодро.

Другой подход?

Можно было бы избежать удваивание вызовов валидации, используя updateDisplayList для выставления явной высоты List-у и не используя measure вообще, но в это приведет к совсем плачевным результатам. Казалось бы, раз в updateDisplayList происходит создание рендереров для List-а, то вместо скрытого рендерера, для вычисление measured размеров, там можно использовать уже созданные. Но не тут то было. И я думаю, многим известно почему. List - достаточно оптимизированный компонент чтобы создавать ровно столько рендереров, сколько необходимо. И так как в measure высота вычисляется в 140 px, то и рендереров будет создано лимитированное количество. А именно, не больше 15 штук. То есть, при дата провайдере в 16 итемов List создаст 7 рендереров для 7 строк по умолчанию. Затем вычислит количество рендереров для буферизации сверху и снизу (16/2=8), но буферизацию для верха создавать не имеет смысла, так как очевидно, что при создании List-а мы и так будем находиться в крайнем верхнем положении с List#verticalScrollPosition=0.
Листинг 7.
mx.controls.listClasses.ListBase#makeRowsAndColumnsWithExtraRows() строка 1344.

  1. var desiredExtraRowsTop:int = offscreenExtraRows / 2;
  2. var desiredExtraRowsBottom:int = offscreenExtraRows / 2;
  3. offscreenExtraRowsTop = Math.min(desiredExtraRowsTop, verticalScrollPosition);

В следствии чего получаем 7+8=15. Соответственно, для провайдера длинной в 58 итемов, при проходе всех данных и снятии разсера через getExplicitOrMeasuredHeight, мы получим ексепшен на 36 рендерере, так как его не будет существовать. А значит, что этот подход нас не удовлетворяет и даже с выставленным List#offscreenExtraRowsOrColumns = List#dataProvider.length.

Пара нюансов.

При тестировании возникла пара проблем с drag-and-drop. Нельзя было перетащить итем в самый низ List-а. В нашем компоненте мы имеем свойство AutoResizableList#bottomOffset, которое позволяет задавать пространство снизу. Оно необходимо для возможности сделать drag-and-drop вниз List-a, а также предотвращает ошибку в случае перетаскивания в пустой List. Вы может продемонстрировать это себе сами, просто перетащите все из левого List-а в правый, а затем попытайтесь перетащить один из итемов обратно (эксепшен тихо проглатывается при отсутствии дебаг версии FlashPlayer). Хотя, этого эксепшена можно избежать перекрыв метод List#showDropFeedback() и пристрочив заплатку.

Наконец-то его можно потрогать и все остальное уже не важно:

Странно, но внутри тоже получилось много комментов.
Скачать пример отсюда. view source.

И наконец.

Eсли вам нет нужды использовать пряники компонента List, например, API по работе с данными и визуалом, drag-and-drop и etc, то тогда не надо заниматься извращениями, а надо просто использовать стандартный mx.controls.VBox и побыстрей забыть это стремное многобуквие :)

Ну а теперь самое главное.

С наступающим Первым днем года!
Горячо жму вас!

  • От Pavel Kozhin
  • Декабрь 29th, 2008
  • Написан в ActionScript 3, Components, Flex
  • 1418 просмотров
  • 4 отзывов »
  Russian (RU)  
  Теги: actionscript 3, component, flex, list

Пейджинг на лету

На одном из проектов возникла производственная необходимость отобразить данные в виде таблицы. Казалось бы проще репы, но избыточность данных бесстыдно ставила контрол на локти. Стоит отметить, что он имел 46 колонок и 100 строк, количество же данных могло исчисляться десятками тысяч. Упомяну, что интеграция происходила на базе веб-сервисов, так что LCDS нам только снился.

Очевидно, что в нашем случае, чтобы поднять производительность необходимо было лимитировать количество данных в провайдере. Таким образом, накопав в доках пару красноречивых намёков на паджинацию тут и тут , мы начали погружение.
Сверхзадачей было добиться относительной нативности скроллинга без скачков и излишнего напряжения (чтобы недостающие данные, по пришествии, не вставали в нулевой индекс, а аккуратно подлипали в конец).

Прольем же немного света на базовые механизмы паджинации поддерживаемые стандартными компонентами, являющими собой производные List-а, а именно HorizontalList, TileList, DataGrid, Menu, Tree, ну и List конечно. Техника базируется на использовании класса mx.collections.errors.ItemPendingError. Выбросив такую ошибку из метода getItemAt() коллекции, которая является дата-провайдером для List-based копонент, можно заставить контрол впасть в ожидание pendent данных. Абстрактный например:

  1. public class PagingCollection implements IList
  2. {
  3.     override public function getItemAt(index:int, prefetch:int=0):Object
  4.     {
  5.         if (index < 0 || (_length != -1 && index >= _length))
  6.           throw new RangeError();
  7.         if(!cache.hasItem(index))
  8.           throw new ItemPendingError("");
  9.         return cache.getItemAt(index);
  10.   }
  11. }

При этом контрол поймав эту ошибку подписывается к ней с помощью респондеров и ждет их срабатывания. Код в листинге ниже взят из метода scrollHandler() класса mx.controls.List (line 1451).

  1. try
  2. {
  3.     if (!iteratorValid)
  4.         iterator.seek(CursorBookmark.FIRST, pos);
  5.     else
  6.         iterator.seek(CursorBookmark.CURRENT, delta);
  7.     if (!iteratorValid)
  8.     {
  9.         iteratorValid = true;
  10.         lastSeekPending = null;
  11.     }
  12. }
  13. catch(e:ItemPendingError)
  14. {
  15.     lastSeekPending = new ListBaseSeekPending(CursorBookmark.FIRST, pos);
  16.     e.addResponder(new ItemResponder( seekPendingResultHandler, seekPendingFailureHandler, lastSeekPending));
  17.     iteratorValid = false;
  18. }

Когда данные готовы, дергаем за респондеры из нутра кастомной коллекции и контрол, в свою очередь, начинает дергать getItemAt() используя индексы paged данных. Абстрактный код ниже (в контексте кастомной коллекции) иллюстрирует обход всех респондеров, что фактически приводит к вызову обработчиков, добавленных контролом в момента отлова ItemPendingError (e.g. seekPendingResultHandler из предыдущего листинга).

  1.  
  2. protected function onResult(val:PagingResult):void
  3. {
  4.     cache.addItems(val.result);
  5.     itemPendingError.responders.forEach(
  6.       function(responder:IResponder, index:int, arr:Array):void
  7.       {
  8.           responder.result(val.result);     
  9.       });
  10. }
  11.  

Интересно упомянуть, что начинается всё с обращения к геттеру length() коллекции. То есть при присвоения нового провайдера Листу, чтобы корректно отрисовать скролл, первым делом он пытается узнать его размер. Вначале, при отсутствии данных, возвращаем -1. Это важно чтобы отличить начальное состояние от возможно пустого массива в полученных данных в дальнейшем и делаем начальный запрос.

  1. override public function get length():int
  2. {
  3.     if (_length == -1 && !lengthPending)
  4.     {
  5.         lengthPending = true;
  6.         requestData();
  7.     }
  8.     return _length;
  9. }

Узнав общее колличество выставляем длину и пинаем контрол с помощью события CollectionEvent.COLLECTION_CHANGE.

  1. protected function onResult(val:PagingResult):void
  2. {
  3.     cache.addItems(val.result);
  4.     if(_length < 0)
  5.     {
  6.         _length = val.totalSize;
  7.         lengthPending = false;
  8.         var event:CollectionEvent = new CollectionEvent(CollectionEvent.COLLECTION_CHANGE);
  9.         event.kind = CollectionEventKind.RESET;
  10.         dispatchEvent(event);
  11.   }
  12. }

Весьма отмечу также, что интенсивное таскание тумбы в режиме liveScrolling представляло из себя совершенно удручающее зрелище, даже с задержкой данных в 1 секунду, контрол выбрасывал ItemPendingError. Отнаследовавшись и перекрыв scrollHandler() c hot-plug мы вернули его к жизни:

  1. public class ListExtended extends List
  2. {
  3.     public function ListExtended()
  4.     {
  5.         super();
  6.     }
  7.     override protected function scrollHandler(event:Event):void
  8.     {
  9.         /*
  10.         value can be null and it
  11.         results in throwing our custom
  12.         PagingCollectionItemPendingError.
  13.         In overridden method it is not
  14.         wrapped in try/catch block.
  15.         */
  16.         if (iterator.bookmark.value)
  17.             super.scrollHandler(event);
  18.     }
  19. }

Творение сие не есть алмаз, а лишь фантазия воображения, но мы счастливы и живы, и солнце, по обыкновению, снова греет наши затылки.

Довольно живописную картину можно получить ознакомившись с приведенными выше ссылками и исходниками ниже.
Качать пример отсюда (некоторые комментарии в коде на англицком). view source.

  • От Pavel Kozhin
  • Июнь 23rd, 2008
  • Написан в ActionScript 3
  • 3654 просмотров
  • 10 отзывов »
  Russian (RU)  
  Теги: actionscript 3, component, flex
<< 1 2
  • Author

    View Pavel Kozhin's profile on LinkedIn
  • Fiat lux! Adobe Flex, ActionScript

    • Последние
    • Архивы
    • Рубрики
    • Последние комментарии
  • Поиск




  • Рубрики

    • Все
    • ActionScript 3
    • Components
    • Flex
    • Fun
    • Links
    • News
    • Tricks
    • Uncategorized
    • Useful
    • Wow
  • XML ленты

    • RSS 2.0: Записи, Комментарии
    • Atom: Записи, Комментарии
    What is RSS?
  • Flex (re)sources

    Потрошитель
    White
    injun #576871
    Garbage Collector
    33 коровы
    RIA crumbs
    DL1t's blog
    Etcs.ru

    Doug McCune
    Jesse Warden
    Serge Jespers
    James Ward
    Mike Downey
    Andre Michelle
    Mike Chambers
    Ted Patrick
    InsideRIA
    Flex.org
    <mx:EverythingFlex/>
    Flex Examples
    Flash Magazine
    Quietly Scheming
    Senocular.com
    Sephiroth.it
  • Архивы

    • Июнь 2009 (1)
    • Февраль 2009 (2)
    • Январь 2009 (4)
    • Декабрь 2008 (1)
    • Июнь 2008 (1)
    • Больше...

powered by b2evolution free blog software


Контакты | Powered by b2evolution
Credits: Foppe Hemminga | blog software | web hosting | monetize