Issue time13:35:41, от Junik Email 2120 просмотров
Рубрики: Flex

Так случилось, что фильтрация коллекций ни разу не беспокоила меня. А тут коллега задал вопрос, который послужил толчком к тому, что вы сейчас прочитаете. =)

Коллеге необходимо показать во view отфильтрованную ArrayCollection. Сама ArrayCollection находится в модели. Когда во view применяется фильтр, то данные модели очень сильно выглядят измененными. Что делать? Как быть?

Конечно, мы с вами знаем, что данные на самом деле не меняются. source коллекции так и остается исходным. Но не работать же в модели с source?

Конечно, не работать! И уж конечно, не делать копии коллекций! Тут спасибо Константинеру за наводку. =)

Можно использовать ListCollectionView в качестве обертки нашей исходной коллекции уже на уровне view. Тогда довольная модель работает с ArrayCollection, как и прежде, а view показывает, что захочет.

В качестве примера использую пример из доков Adobe для фильтрации коллекций.
Пример такой:

XML:

<?xml version="1.0"?>
<!−− dpcontrols\SortFilterArrayCollection.mxml −−>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" width="600">
    <mx:Script>
        <![CDATA[
            import mx.collections.*;
    
            /* Function to sort the ICollectionView
               in ascending order. */
            public function sortAC():void {
                var sortA:Sort = new Sort();
                sortA.fields=[new SortField("label")];
                myAC.sort=sortA;
                //Refresh the collection view to show the sort.
                myAC.refresh();
            }
 
            /* Function to filter out all items with labels
               that are not in the range of M-N. */
            public function stateFilterFunc(item:Object):Boolean {
                return item.label >= "M" && item.label < "O";
            }
            
            /* Function to apply the filter function the ICollectionView. */
            public function filterAC():void {
                myAC.filterFunction=stateFilterFunc;
                /* Refresh the collection view to apply the filter. */
                myAC.refresh();
            }
 
            /* Function to Reset the view to its original state. */
            public function resetAC():void {
                myAC.filterFunction=null;
                myAC.sort=null;
                //Refresh the collection view.
                myAC.refresh();
            }
 
        ]]>
    </mx:Script>
 
    <!−− An ArrayCollection with an array of objects. −−>
    <mx:ArrayCollection id="myAC">
        <mx:Array id="myArray">
            <mx:Object label="LA" data="Baton Rouge"/>
            <mx:Object label="NH" data="Concord"/>
            <mx:Object label="TX" data="Austin"/>
            <mx:Object label="MA" data="Boston"/>
            <mx:Object label="AZ" data="Phoenix"/>
            <mx:Object label="OR" data="Salem"/>
            <mx:Object label="FL" data="Tallahassee"/>
            <mx:Object label="MN" data="Saint Paul"/>
            <mx:Object label="NY" data="Albany"/>
        </mx:Array>
    </mx:ArrayCollection>
 
    <!-- Buttons to filter, sort, or reset the view in the second ComboBox
            control. -->
    <mx:HBox width="100%">
        <mx:Button id="sortButton" label="Sort" click="sortAC();"/>
        <mx:Button id="filterButton" label="Filter" click="filterAC();"/>
        <mx:Button id="resetButton" label="Reset" click="resetAC();"/>
    </mx:HBox>
    <mx:VBox width="550" height="143" borderStyle="solid" paddingTop="10" paddingLeft="10">
        <mx:Label text="This box retains original order and contents of the Array:"/>
        <!-- A ComboBox populated by the underlying Array object.
            This control shows that Array retains its original order. -->
        <mx:ComboBox id="cb2" rowCount="10" dataProvider="{myArray}"/>
        <mx:HRule/>
        <mx:Label text="This box reflects the changes to the Array:"/>
        <!−− A ComboBox populated by the collection view of the Array. −−>
        <mx:ComboBox id="cb1" rowCount="10" dataProvider="{myAC}"/>
    </mx:VBox>
</mx:Application>

Тут у нас два выпадающих списка: один с source коллекции, а второй с самой коллекцией.
Можно отсортировать, отфильтровать коллекцию и посмотреть, что будет.
После выполнения метода filterAC наша коллекция уже сама не своя, ее прямо не узнать.

Что же мы делаем? Тут и далее использован беглый стиль программирования, потому что все-таки пример. =)
Добавляем обертку для коллекции:

Code:

[Bindable]
private var wrapper:ListCollectionView;

После создания приложения создаем эту нашу обертку:

Code:

creationComplete="wrapper=new ListCollectionView(myAC)"

Фильтруем уже эту обертку, ее же показываем во втором выпадающем списке.
Добавляем новый выпадающий список для нашей родной коллекции.

Вот как выглядит код после изменений:

XML:

<?xml version="1.0"?>
<!−− dpcontrols\SortFilterArrayCollection.mxml −−>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" width="600"
  creationComplete="wrapper=new ListCollectionView(myAC)">
    <mx:Script>
        <![CDATA[
 
            import mx.collections.*;
            
            [Bindable]
            private var wrapper:ListCollectionView;
    
            /* Function to sort the ICollectionView
               in ascending order. */
            public function sortAC():void {
                var sortA:Sort = new Sort();
                sortA.fields=[new SortField("label")];
                wrapper.sort=sortA;
                //Refresh the collection view to show the sort.
                wrapper.refresh();
            }
 
            /* Function to filter out all items with labels
               that are not in the range of M-N. */
            public function stateFilterFunc(item:Object):Boolean {
                return item.label >= "M" && item.label < "O";
            }
            
            /* Function to apply the filter function the ICollectionView. */
            public function filterAC():void {
                wrapper.filterFunction=stateFilterFunc;
                /* Refresh the collection view to apply the filter. */
                wrapper.refresh();
            }
 
            /* Function to Reset the view to its original state. */
            public function resetAC():void {
                wrapper.filterFunction=null;
                wrapper.sort=null;
                //Refresh the collection view.
                wrapper.refresh();
            }
 
        ]]>
    </mx:Script>
 
    <!−− An ArrayCollection with an array of objects. −−>
    <mx:ArrayCollection id="myAC">
        <mx:Array id="myArray">
            <mx:Object label="LA" data="Baton Rouge"/>
            <mx:Object label="NH" data="Concord"/>
            <mx:Object label="TX" data="Austin"/>
            <mx:Object label="MA" data="Boston"/>
            <mx:Object label="AZ" data="Phoenix"/>
            <mx:Object label="OR" data="Salem"/>
            <mx:Object label="FL" data="Tallahassee"/>
            <mx:Object label="MN" data="Saint Paul"/>
            <mx:Object label="NY" data="Albany"/>
        </mx:Array>
    </mx:ArrayCollection>
 
    <!-- Buttons to filter, sort, or reset the view in the second ComboBox
            control. -->
    <mx:HBox width="100%">
        <mx:Button id="sortButton" label="Sort" click="sortAC();"/>
        <mx:Button id="filterButton" label="Filter" click="filterAC();"/>
        <mx:Button id="resetButton" label="Reset" click="resetAC();"/>
    </mx:HBox>
    <mx:VBox width="550" borderStyle="solid" paddingTop="10" paddingLeft="10">
        <mx:Label text="This box retains original order and contents of the Array:"/>
        <!-- A ComboBox populated by the underlying Array object.
            This control shows that Array retains its original order. -->
        <mx:ComboBox id="cb2" rowCount="10" dataProvider="{myArray}"/>
        <mx:HRule/>
        <mx:Label text="This box reflects the changes to the Wrapper:"/>
        <!−− A ComboBox populated by the collection view of the Array. −−>
        <mx:ComboBox id="cb1" rowCount="10" dataProvider="{wrapper}"/>
        <mx:HRule/>
        <mx:Label text="This box reflects the changes to the ArrayCollection:"/>
        <!−− A ComboBox populated by the collection view of the Array. −−>
        <mx:ComboBox rowCount="10" dataProvider="{myAC}"/>
    </mx:VBox>
</mx:Application>

В результате получаем фильтрацию исключительно на уровне view. А модель живет своей счастливой и долгой жизнью. =)

Этот же подход можно использовать и при необходимости показывать одну и ту же коллекцию в разных контролах с разными фильтрами.

Issue time21:12:32, от Junik Email 350 просмотров
Рубрики: о разработке

Мне кажется, что есть три стадии принятия программистами паттерна Singleton.

  1. Эйфория – Как удобно иметь такую клевую глобальную штуку.
    Стадия быстро проходит.
  2. Страх – Как страшно иметь такую глобальную штуку.
    На этой стадии программисты сильно боятся Singleton-а и всячески стараются не использовать. Отговаривают окружающих пользоваться этим паттерном, применяя тактику запугивания.
  3. Понимание.
    На этой стадии программисты понимают, что бояться нечего, если использовать паттерн с умом.

Жалко, что не все доходят до третьей стадии. =)

Issue time14:33:14, от Junik Email 2387 просмотров
Рубрики: Flex

Часто flex-разработчики работают в небольших командах, либо (о, ужас) в одиночку. А это может привести к тому, что утрачивается критическое отношение к своему коду. Но мы же не хотим деградации! =)

Некоторое время назад Adode выпустили инструмент, который покритикует, укажет на недоработки, ошибки и bad practices в коде – это FlexPMD.
Расшифровка PMD точно неизвестна, но мне нравится – Programming Mistake Detector. Эту технологию уже давно используют Java-разработчики.

Даже если вы пишете идеальный код (чего, конечно, не бывает), то будет полезно узнать про пару-тройку неиспользуемых методов или наличие пустых используемых методов. Или может в каком-то методе затесался неиспользуемый аргумент? =)

На пути к идеальному коду у вас три этапа:

  1. определение правил, на соответствие которым будет проверен код (используем FlexPMD Ruleset Creator)
  2. запуск FlexPMD и получение в результате файла с расширением pmd.xml
  3. просмотр pmd-файла, например, с помощью PMD Violations Viewer

Набор правил

Прежде всего необходимо определиться с набором правил, по которым будет проверяться код. Адобе предоставил FlexPMD Ruleset Creator, который по умолчанию предлагает набор из 84 правил разного приоритета: Error, Warning, Info. Этот набор можно редактировать и сгенерить файл pmd.xml, который будет в дальнейшем использоваться при проверке кода.

Расскажу кратко про некоторые правила.

Error priority

Ошибками считается:

  • использование BindingUtils и ChangeWatcher. Да и как тут не согласиться? Ведь на этапе компиляции вы не узнаете о том, что все плохо.
  • использование callLater. А кто бы сомневался?
  • использование dynamic классов, Dictionary, Object и *.
  • создание или удаление чайлдов внутри updateDisplayList и вообще не внутри createChildren
  • dispatch события внутри конструктора (а кому нужны такие события? =))
  • некорректные метаданные [Event]. Вот это удобно.
  • использование не статических констант. Константы обязаны быть статическими.
  • наличие неиспользуемых, методов, параметров и тд. Это полезно. Часто в больших проектах повисают неиспользуемые методы, классы и даже пакеты. =)

Warning priority

  • Предупреждают, что не надо инстанциировать переменную в цикле.
  • Не рекомендуют использовать статические переменные. И правильно.
  • Также не рекомендуют делать весь класс [Bindable]. Тоже правильно. Что тут скажешь?
  • Находит слишком сложные методы и указывает на это. Если программе метод кажется слишком сложным, то что же делать вашим коллегам?
  • Ну и конечно вложенные if else никуда не годятся.
  • Много импортов наводят программку на размышления о том, что этот объект связан слишком со многими. А это не есть гуд.
  • Слишком длинные методы, конструкторы и тп не нравятся никому.
  • Кстати, на нравятся заглавные буквы в названии пакетов. Что ж. Может в этом что-то есть.
  • Очень приятно, что даются рекомендации понижать уровень видимости методов. Ура-ура! =)
  • Слишком много параметров у метода? Да я сама это терпеть не могу.
  • Наблюдая переменные со странными именами (наличие цифр, tmp и тд), программа тоже дает об этом знать.

Info priority

  • Слишком короткое имя переменной.
  • Слишком длинная строка кода.
  • Конструктор какого-то фига имеет возвращаемое значение.
  • Отсутствует copyright header.

В общем, лично я считаю эту программу крайне полезной. Если вы практикуете в команде ревью кода, то это может помочь на начальном этапе. Если команды нет, то полезно для самоорганизации.

Шаги к идеальному коду с помощью Ant

Приведу шаги, в результате которых, вы сможете узнать всю правду про свой код. =)
Это вариант с использованием Ant.

  1. Скачиваем нужную версию http://opensource.adobe.com/wiki/display/flexpmd/Downloads из колонки “Ant Task”
  2. Распаковываем архив в любое удобное место, например в C:\Program Files\flex-pmd
  3. С помощью FlexPMD Ruleset Creator создаем файл pmd.xml и кладем в любое удобное место, например тоже в C:\Program Files\flex-pmd. Для начала можно правила не редактировать, а просто кликнуть “Export” и сохранить файл.
  4. В файл свойств для build.xml добавляем переменные
    FLEXPMD=c:/Program Files/flex-pmd
    FLEXPMD_VERSION=1.0.RC4
    Пример файла local.properties:
    FLEXPMD=c:/Program Files/flex-pmd
    FLEXPMD_VERSION=1.0.RC4
    SRC_DIR =${basedir}/src
    DEPLOY_DIR = ${basedir}/DEPLOY
  5. Пример build.xml:

    XML:

    <project name="" default="pmd">
      <!−− load previously defined configuration properties file −−>
      <property file="local.properties" />    
        <!−− delete and create the DEPLOY dir again −−>
      <target name="init">
        <delete dir="${DEPLOY_DIR}" />
        <mkdir dir="${DEPLOY_DIR}" />      
      </target>
      <taskdef name="flexPmd"
        classname="com.adobe.ac.pmd.ant.FlexPmdAntTask"
        classpath="${FLEXPMD}/flex-pmd-ant-task-${FLEXPMD_VERSION}.jar">
            <classpath>
                <pathelement location="${FLEXPMD}/flex-pmd-ruleset-api-${FLEXPMD_VERSION}.jar"/>
                <pathelement location="${FLEXPMD}/flex-pmd-ruleset-${FLEXPMD_VERSION}.jar"/>
                <pathelement location="${FLEXPMD}/flex-pmd-core-${FLEXPMD_VERSION}.jar"/>
                <pathelement location="${FLEXPMD}/as3-plugin-utils-${FLEXPMD_VERSION}.jar"/>
                <pathelement location="${FLEXPMD}/as3-parser-${FLEXPMD_VERSION}.jar"/>
                <pathelement location="${FLEXPMD}/pmd-4.2.2.jar"/>
                <pathelement location="${FLEXPMD}/commons-lang-2.4.jar"/>
                <pathelement location="${FLEXPMD}/flex-pmd-files-${FLEXPMD_VERSION}.jar"/>
                <pathelement location="${FLEXPMD}/as3-parser-api-${FLEXPMD_VERSION}.jar"/>
                <pathelement location="${FLEXPMD}/plexus-utils-1.0.2.jar"/>
            </classpath>
        </taskdef>
     
        <target name="pmd" depends="init">
            <flexPmd
                sourceDirectory="${SRC_DIR}"
                outputDirectory="${DEPLOY_DIR}"
                ruleSet="${FLEXPMD}/pmd.xml"/>
        </target>
    </project>
  6. Полученный pmd.xml анализируем с помощью PMD Violations Viewer
Теги: ant, flex, flexpmd
Issue time12:13:51, от Junik Email 512 просмотров
Рубрики: Flex

Когда давно я писала о вариантах передачи версии в приложение. Там упоминалось возможное использования для этого сonditional compilation, но тогда еще Flex 3 не вышел.

Сейчас уже давным давно вышел Flex 3 и даже 4 не за горами.

Сейчас расскажу про то, как легко и непринужденно передать версию вашего приложения при компиляции, а потом и запросто использовать ее в приложении.

Передать константы в приложение можно, используя опцию компилятора compiler.define. Константы могут быть типов Boolean, String, Number или выражения, которые могут быть преобразованы в ActionScript в процессе компиляции. Далее эти константы доступны в приложении как глобальные.

Используя опцию define, необходимо для константы определить: namespace, имя и значение:

-define=namespace::variable_name,value

Пример:

-define=VERSION::description,'3.15.6'

Доступ из приложения доступен пока только из as-классов. В скором будущем, возможно будет работать и доступ из mxml-классов. Говорят, что в mxml-классах можно использовать только Boolean. Но зачем нам? Мы же код в mxml не пишем. ;)
Пример доступа:

public static const VERSION_DESCR:String = VERSION::description;

Подробнее про использование conditional compilation в доках.

Issue time15:50:22, от Junik Email 1323 просмотров
Рубрики: Flex

ArrayCollection является источником множества событий

Случается, что разработчики сильно расстроены разговорчивостью коллекций вплоть до того, что используют вместо них просто Array.

Событие CollectionChange рассылается при каждом изменении коллекции. Например, при удалении двух элементов и добавлении трех, разошлется как минимум 5 событий. А тогда, например, визуальные компоненты, напрямую реагирующие на изменения коллекций (они подписаны на CollectionChange), будут производить слишком много промежуточных действий, хотя их волнует только финальное состояние коллекции. Да и некоторых разработчиков откровенно пугает, что столько неприкаянных ненужных событий бродит по просторам родного и горячо любимого приложения.

А оно тебе надо?

Но перед тем, как с чем-то бороться, призываю все-таки задуматься. Нужно ли с этим бороться? Чем лично тебе мешает частая рассылка событий коллекции? Ведь если мешает чисто “просто так", то паранойя - это, вообще говоря, серьезное заболевание, которое можно лечить.

Мне в голову приходит не так много ситуаций, когда эта проблема должна беспокоить. При адекватной разработке компоненты не реагируют на изменения коллекции молниеносно, обработка происходит в отложенном режиме. Сколько бы раз ни пришло событие, обработка произойдет только в commitProperties, например. Поэтому на работе стандартного RIA это особо не скажется.

Однако бывают все-таки неприятные вещи. Например, происходит какая-то долгая обработка коллекции, которая занимает времени больше, чем кадр или несколько кадров, то приложение входит в фазу полного зависания. Изменения коллекции еще не завершились, а отрисовка уже запустилась и т.п.

Если все-таки надо.

Когда посещают мысли что-то с этим делать, то конечно, немедленно хочется воспользоваться методом disableAutoUpdate, который позволяет отменить рассылку событий. Вызвав этот метод, вы можете быть уверены, что никто не узнает об изменениях коллекции.

Метод enableAutoUpdate включит рассылку событий обратно. Обратите внимание, что это приведет к рассылке всех событий, накопленных за время “молчания” коллекции.

Казалось бы все просто. Но тут есть интересная особенность. В зависимости от того сколько раз вы вызвали disableAutoUpdate, столько раз и придется вызвать enableAutoUpdate, чтобы кто-нибудь все-таки узнал об изменениях. Это может быть неудобно, особенно, если учесть, что, например, DataGrid использует эти механизмы сам.

Если вы хотите сами решать, когда сообщать об изменении коллекции, то можно унаследоваться от класса коллекции и переопределить метод enableAutoUpdate таким образом, чтобы рассылка никогда не возобновлялась. Однако это приведет к тому, что коллекция все равно будет сохранять данные о своих изменениях.

Тут есть еще интересная особенность. Вызов метода refresh приводит к рассылке события CollectionChange вне зависимости от того, отключен автоапдейт или нет. Не знаю, баг это или фича, но этот же метод еще и обнуляет массив накопленных за “время молчания” изменений.

Таким образом, переопределив метод enableAutoUpdate и вызывая refresh только тогда, когда сочтете нужным, вы решите проблему рассылки лишних событий и их накопления в коллекции.

Issue time20:57:22, от Junik Email 2723 просмотров
Рубрики: Flex

Если вы мечтаете стать сертифицированным flex-разработчиком, то безусловно порадуетесь тому, что программа для подготовки к тесту Attest стала бесплатной. Приятно, что это произошло несмотря на мировой финансовый кризис.
Кстати, это теперь AIR-приложение.

Будущие Adobe Flex 3 with AIR Certified Developer-ы дерзайте! :)

PS: А есть желающие стать сертифицированными?

Issue time23:29:09, от Junik Email 2013 просмотров
Рубрики: Flex

Недавно в посте Многопоточность и flash платформа вместе я уже предлагала обсудить многопоточность.

И вот сегодня читаю пост Threading in Flex про библиотеку AsyncThreading. Можно сказать, что библиотека эта для Flex, потому что в ней используются флексовые классы (я заметила один). И вот это уже действительно похоже на библиотеку, которую можно брать и использовать.

Удачи тем, кому многопоточность необходима. :) Судя по комментариям к предыдущему посту, такие есть.

Issue time14:32:04, от Junik Email 1570 просмотров
Рубрики: Flex, Flash, flash player

Существуют разработчики, которым сильно не хватает многопоточности во flash и flex. Понятное дело, что они уже давно сделали свои эмуляции и живут прекрасно.
Хотя, наверное, существуют и такие разработчики, которые просто страдают от того, что не было, нет и не будет потоков. :)

Вот и еще один пост об этом же: Ender Lib - Threads for Flex.

Библиотека Ender Lib, насколько я успела посмотреть, основана на использовании события ENTER_FRAME, но судя по всему вы можете использовать и таймер, так как нужные методы protected.

А расскажите про свои реализации или чьи-нибудь уже известные и опробованные.
И вообще, часто вам так уж необходима многопоточность?

Issue time23:49:26, от Junik Email 3870 просмотров
Рубрики: события, разное

constantiner

В эту субботу у Константина Ковалева aka Constantiner был день рождения!
Костя, желаем тебе творческих успехов, новых открытий и интересным тем для исследования. :)

Теги: birthday
Issue time22:18:39, от Junik Email 3361 просмотров
Рубрики: Flex, события, Flex 3, frameworks

В воскресенье мы посетили августовскую встречу RAFPUG почти полным составом riapriority. Причем доклады про flex-фреймворки читали опять же представители riapriority: Константин Ковалев aka Constantiner рассказывал про Mate и Павел Кожин aka Vertex про Cairngorm.
Еще один доклад был посвящен следующей (а значит четвертой) версии Flex - Gumbo, и его представлял Артемий Малков.

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

First of all шок дня - Иван Дембицкий признал существование Flex. Ура, товарищи! :)

Первый доклад про Gumbo порадовал богатыми перспективами Flex. Об этом уже многие писали, в том числе и Constantiner в посте Flex 4 “Gumbo” увидеть не хотите ли? И Gumbo, действительно, прекрасен.
Самые впечетляющие меня меня фичи:

  • разделение в компонентах view и модели, до сих пор не верится, что мы этого дождемся :)
  • новый подход к layout-ам
  • то, что уж совсем похоже на сон или на сказку - новые Gumbo компоненты будут полностью совместимы со старыми Halo компонентами
  • мы сможем более удобно работать с графическими примитивами посредством FXG

В качестве переходного этапа к рассказам о Cairngorm и Mate был мини-доклад про то, зачем нужны фреймворки. Constantiner вкратце рассказал о проблематике командной разработки, о спагетти-коде, и о том, как перестать думать мучиться и начать программировать. :)

Кстати Constantiner придумал очень интересный проект - написать одно и тоже приложение с использованием разных фреймворков. Приложение не очень сложное - это поисковик книжек в Ozon, из функционала - собственно сам поиск и сохранение избранного в shared objects.
Код проекта хранится тут:
http://code.google.com/p/ozon-books-finder/.
А если у вас есть желание поучаствовать, то обязательно присоединяйтесь!

Далее Vertex очень подробно рассказал про Cairngorm. В результате сложилось впечатление, что полезно знать этот фреймворк, так как он очень распространен сейчас. С другой стороны кажется, что тяжеловесность и неповоротливость Cairngorm-а, может склонить разработчика к принятию решения о выборе другого фреймворка.

А какого другого фреймворка спросите вы? При принятии этого решения может оказать незаменимую помощь доклад Кости про Mate.
Сразу скажу, что если вы не видели и не слышали этот доклад, то пора в отчаянии убить себя апсену очень-очень-очень много потеряли.
Я еще ни разу не наблюдала в докладах Кости столько воодушевления и экспрессии. Трудно было не сорваться и не убежать изучать Mate прямо после встречи. :)
Constantiner поделился тем, что давно мечтал о таком фреймворке и даже втайне хотел написать нечто подобное.

Неформальная часть тоже порадовала темами для обсуждения и приятным общением. Сообщество интересуется и разработкой серверной части, и работой с системами контроля версий, и общей организацией разработки, и многим другим.
Приятно, что московские встречи посещают разработчики не только из Москвы и Питера. Кстати, пользуясь случаем, передаю привет гостям из Самары. :)

А если вы еще ни разу не посетили встречу, то обязательно это сделайте в ближайшее время. Все-таки не всем разработчикам на flash-платформе повезло работать в команде, а тут такая возможность обменяться опытом. :)

Блог посвященный Flash-платформе, Flex, программированию и разработке ПО.

Поиск

Put your credits or banners here.

You can change or delete this text in /_sidebar_credits.inc.php
Powered by b2evolution