Руководство по actionscript. часть 2, стр. 067

Недостаток проверки типов

в событийной модели языка ActionScript

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

? Должен существовать тип события, для которого регистрируется приемник.

? Должен существовать сам приемник.

? Приемник должен знать, как обрабатывать событийный объект, передаваемый в процессе диспетчеризации возникшего события.

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

Когда приемник регистрируется в объекте для получения события, он вступает в соглашение, основанное на типах данных, которое гарантирует выполнение первых трех условий. Если это соглашение не выполняется, компилятор генерирует ошибку типа. Например, рассмотрим следующий код, описывающий и регистрирующий приемник, в котором умышленно допущены три нарушения (выделенные полужирным шрифтом) контракта приемника события:

urlLoader. addEventLi stener(Event. COMPLTE, completeLi stenr);

private function completeListener (e:MouseEvent):void { traceCLoad complete»);

}

В приведенном коде нарушения контракта приемника события заключаются в следующем.

? Константа Event. COMPLTE записана с ошибкой: пропущена буква Е. Компилятор сгенерирует ошибку, которая предупредит программиста о том, что тип события, для получения которого пытается зарегистрироваться приемник, не существует.

Руководство по actionscript. часть 2, стр. 068

? Название приемника события записано с ошибкой: пропущена еще одна буква е. Компилятор сгенерирует ошибку, которая предупредит программиста о том, что регистрируемый приемник не существует.

? Типом данных первого параметра, передаваемого в метод completeListener ( ), является Мои s еЕ ve n t, который не соответствует типу данных событийного объекта для события Event. COMPLETE. На этапе диспетчеризации события среда выполнения Flash сгенерирует ошибку, которая предупредит программиста, что приемник не может обработать переданный в него событийный объект.

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

^ I Соглашение между приемником события и объектом, который регистрирует этот при-d, m емник, основанное на типах данных, позволяет гарантировать корректное выполнение нашего кода, обрабатывающего события.

Тем не менее соглашение между приемником и объектом, регистрирующим этот приемник, имеет один недостаток: оно не гарантирует, что объект поддерживает указанный тип события. Например, рассмотрим следующим код, который регистрирует приемник в объекте urlLoader для событий TextEvent. TEXT_INPUT:

url Loader. addEventLi stener (TextEvent. TEXTJNPUT. text InputLi stener);

Хотя с практической точки зрения у нас есть все основания полагать, что объект URLLoader никогда не будет получателем события TextEvent. TEXT_INPUT, приведенный код не вызовет ошибки. В языке ActionScript регистрация приемников для событий осуществляется по любому имени. Например, следующий бессмысленный код также является допустимым:

urlLoader. addEventLi stener(«dlrognw», dlrognwLi stener);

Каким бы очевидным ни казалось то, что объект urlLoader никогда не будет получателем события с именем 11 dl rognw «, программа на самом деле может выполнить диспетчеризацию такого события. Это демонстрирует следующий код:

urlLoader. dispatchEvent(new Event(«dlrognw»));

Учитывая, что программа имеет возможность назначать любой объект в качестве получателя любого события, язык ActionScript намеренно не использует концепцию «поддерживаемых событий». Подобная гибкость является предметом споров, поскольку она может привести к трудновыявляемым ошибкам в коде. Например, предположим, что мы используем класс Loader для загрузки внешнего изображения, как показано в следующем коде:

var loader:Loader = new Loader( );

1oader.1oad(new URLRequest(«i mage. jpg»)):

Предположим также, что объект loader будет являться получателем событий, информирующих о завершении загрузки изображения (во многом аналогично тому, как объект URLLoader является получателем событий о завершении загрузки элемента). Таким образом, мы пытаемся обработать событие Event. COMPLETE для нашего загружаемого элемента, зарегистрировав приемник непосредственно в объекте Loader, как показано в следующем коде:

1oader. addEventLi stener(Event. COMPLETE. completeLi stener);

Запустив код, мы будем удивлены, поскольку, даже несмотря на отсутствие ошибок, метод completeListener ( ) не был вызван ни разу. Так как никаких ошибок не возникло, мы не сможем сразу же определить источник проблемы в нашем коде. Последующий анализ и отладка кода будут стоить нам времени и, по всей вероятности, значительного неудовлетворения. Только прочитав соответствующий раздел документации корпорации Adobe, мы определим проблему: объекты Loader на самом деле не являются получателями событий о завершении загрузки; вместо этого события о завершении загрузки должны обрабатываться

экземпляром класса Loaderlnf о каждого объекта Loader, как показано в следующем коде:

1oader. contentLoaderInfо. addEventLi stener(Event. COMPLETE. completeLi stener);

В будущем язык ActionScript, возможно, позволит классам перечислять события, которые они поддерживают, и генерировать соответствующие предупреждения компилятора на попытки зарегистрировать приемник на события, которые не поддерживаются данным классом. Пока же классы, которые реализуют пользовательские события, путем переопределения метода addEventListener ( ) могут самостоятельно генерировать пользовательские ошибки в тех случаях, когда программа пытается зарегистрировать приемник для неподдерживаемых событий, как показано в следующем коде:

public override function addEventListener(eventType:String.

Руководство по actionscript. часть 2, стр. 069

Handler:Function, capture:Boolean = false, priority:int = 0. weakRef:Boolean = false):void { // Метод canDispatchEv. entC ) (не показан) проверяет наличие // указанного типа eventType в списке поддерживаемых // данным классом событий и возвращает значение типа Boolean. // которое говорит о том. является ли указанный тип eventType // поддерживаемым типом событий if(canDispatchEvent(eventType)) { // Событие поддерживается, поэтому приступаем к регистрации super. addEventListener(eventType. handler, capture, priority. weakRef); } else {

// Событие не поддерживается, поэтому генерируем ошибку throw new ErrorCthis + » does not support events of type ‘» + eventType + );

}

}

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

Теперь рассмотрим последний вопрос, касающийся событийной модели: обработку событий в приложениях, состоящих из нескольких SWF-файлов, которые размещены в различных интернет-доменах. Для изучения следующего раздела необходимс иметь общее представление о методах загрузки SWF-файлов, которые рассматриваются в гл. 28.

Обработка событий между границами зон безопасности

В гл. 19 будет рассмотрено несколько сценариев, в которых ограничения безопас ности не позволяют одному SWF-файлу осуществлять кросс-скриптинг (управ лять программным путем) над другим файлом. Когда два SWF-файла не могу]

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

? Приемники событий из одного SWF-файла не могут регистрироваться для событий в объектах другого SWF-файла.

? Если получателем события является объект в иерархии отображения, любые объекты, недоступные в SWF-файле объекта получателя, не включаются в цепочку диспетчеризации событий.

К счастью, описанные ограничения можно полностью обойти с помощью статического метода allowDomain ( ) класса flash. system. Security. Рассмотрим два примера, которые демонстрируют применение метода allowDomain ( ) для обхода каждого из этих ограничений.

*«, _

*« Информацию по загрузке SWF-файлов можно найти в гл. 28.

Руководство по actionscript. часть 2, стр. 070

Приемник из файла Module. swf регистрируется в объекте файла Main. swf

Предположим, что SWF-файл, находящийся на одном сайте (site-a. com/Main. swf), загружает SWF-файл, расположенный на другом сайте (site-b. com/Module. swf). Предположим также, что в файле Module. swf определен приемник, который желает зарегистрироваться в объекте, созданном в файле Main. swf. Чтобы разрешить данную регистрацию, перед регистрацией приемника из файла Modul е. swf в файле Main. swf должна быть выполнена следующая строка кода:

Security. а11owDoma i n(«s i tе-b. com»);

Эта строка позволяет всем SWF-файлам, находящимся на сайте site-b. com (включая файл Module. swf), регистрировать приемники в любом объекте, созданном в файле Main. swf.

Приемник из файла Main. swf получает уведомление

о событии, получателем которого является отображаемый

объект в файле Module. swf

Продолжая рассматривать сценарий, в котором файл Main. swf загружает файл Module. swf из предыдущего раздела, предположим, что экземпляр основного класса файла Main. swf добавляет объект класса Loader, содержащий файл Module. swf, в свою иерархию отображения, как показано в следующем коде:

package { import flash. display.*; import flash. net.*; import flash. events.*;

public class Main extends Sprite { private var loader:Loader;

public function Main( ) { loader = new Loader( );

1oader.1oad(new URLRequest(«http://site-b. com/Module. swf»));

// Добавляем объект Loader, содержащий файл Module. swf, в иерархию

// отображения данного объекта

addChild(loader);

• } .} }.

Руководство по actionscript. часть 2, стр. 071

Предположим также, что экземпляр основного класса файла Main. swf желает получать уведомления всякий раз, когда пользователь щелкает кнопкой мыши на объекте из файла Module. swf. Следовательно, экземпляр основного класса файла Main, swf регистрирует приемник в объекте loader для событий MouseEvent. CLICK, как показано в следующем коде:

package { import flash. display.*: import flash. net.*; import flash. events.*;

public class Main extends Sprite { private var loader:Loader;

public function Main( ) { loader = new Loader( );

1 oader.-load(new URLRequest(«http://site-b. com/Module. swf»)); addChild(loader);

1oader. addEventLi stener(MouseEvent. CLICK. clickListener); } •

private function clickListener (e:MouseEvent):void { trace(«Module. swf was clicked»);

}

}

}

Тем не менее, поскольку файлы Ma in. swf и Module, swf размещены в различных интернет-доменах, ограничения безопасности запрещают вызывать метод clickListener ( ) для возникающих событий MouseEvent. CLICK, получателями которых являются отображаемые потомки объекта loader (то есть отображаемые объекты из файла Module. swf).

Для того чтобы обойти данное ограничение, конструктор основного класса файла Module. swf содержит следующую строку кода:

Security. al1owDoma i n(«s i te-a. com»);

После выполнения этой строки файл Module. swf начнет доверять файлу Main. swf (и всем SWF-файлам с сайта site-a. com), благодаря чему экземпляр основного класса файла Ma in. swf будет включен клиентской средой выполнения Flash в цепочку диспетчеризации события MouseEvent. CLICK, получателем которого являются объекты из файла Module. swf. В результате метод clickListener ( )

будет вызываться всякий раз при щелчке кнопкой мыши на объекте из файла Module. swf.

Руководство по actionscript. часть 2, стр. 072

Подробную информацию о. методе allowDomain() и безопасности приложения Flash Player м$ j * можно найти в разд. «Разрешения создателя (allowDomain())» гл. 19.

Стоит отметить, что вызов метода а 11 о wDoma i n ( ) позволяет не только обрабатывать события между границами зон безопасности: все SWF-файлы из разрешенного домена получают возможность осуществлять кросс-скриптинг над SWF-файлом, в котором был вызван метод allowDomain ( ). Однако существует альтернатива всеобъемлющим разрешениям, выдаваемым методом allowDomain ( ).

Альтернатива методу allowDomain( ): разделяемые события

В некоторых случаях SWF-файлы из различных доменов могут пожелать совместно использовать события, не предоставляя при этом всех полномочий для кросс-скриптинга. Для решения подобных проблем приложение Flash Player предоставляет переменную экземпляра sharedEvents класса Loaderlnf о. Переменная sharedEvents — это простой нейтральный объект, через который два SWF-файла могут отправлять события друг другу, независимо от ограничений, обусловленных требованиями безопасности. Этот подход позволяет осуществлять взаимодействие между SWF-файлами, основанное на событиях, не отменяя требований безопасности, но для его реализации требуется написание большего объема кода, чем при использовании альтернативного подхода с методом allowDomain ( ).

Рассмотрим применение переменной sharedEvents на примере. Предположим, что Томми основал компанию по производству фейерверков и создал рекламный сайт www. blast. ca с использованием технологии Flash. Томми нанял подрядчика Дерека для создания отдельного элемента, реализующего эффект, который заключается в хаотичной генерации анимированных взрывов фейерверков под указателем мыши. Дерек создает SWF-файл MouseEf fect. swf, в котором реализован этот эффект, и размещает его по адресу www. dereksflasheffects. com/MouseEffect. swf. Дерек говорит Гомми загрузить файл MouseEf f ect. swf в его приложение www. blast. ca/BlastSite. swf. Церек и Томми согласились, что файл MouseEf feet. swf должен размещаться на :ервере www. derekflasheffects. com, что в дальнейшем позволит Дереку легко обновлять данный файл, не внося при этом никаких изменений в сайт Томми.

Гомми просит Дерека изменить файл MouseEf feet. swf таким образом, чтобы генерация взрывов прекращалась в тот момент, когда указатель мыши покидает об-1асть отображения приложения Flash Player. Дерек считает эту идею целесообраз-¦юй и приступает к написанию соответствующего кода. В обычной ситуации, чтобы шределить выход указателя мыши за пределы области отображения приложения Flash Player, код в файле MouseEf feet. swf должен зарегистрировать приемник * экземпляре Stage для событий Event. MOUSE_LEAVE. Однако, поскольку фай-ты MouseEf feet. swf и BlastSite. swf размещены в разных доменах, файл MouseEf feet. swf не имеет доступа к экземпляру Stage. Томми решает, что

вместо того, чтобы предоставлять файлу MouseEf feet. swf полный доступ к файлу BlastSite. swf, он просто переадресует все события Event. MOUSE_LEAVE файлу MouseEf feet. swf через переменную sharedEvents.

Листинг 12.6 демонстрирует код файла BlastSite. swf, относящийся к переадресации событий.

Листинг 12.6. Переадресация события через переменную sharedEvents

package { import flash. display.*; import flash. net.*; import flash. events.*; import flash. system.*;

public class BlastSite extends Sprite { private var loader:Loader;

public function BlastSite ( ) { // Загружаем файл MouseEffeet. swf loader = new Loader( ); loader. load(

new URLRequest(«http://www. dereksf1asheffects. com/MouseEffect. swf»)); addChild(loader);

// Регистрируем приемник для событий Event. MOUSE_LEAVE

stage. addEventLi stener(Event. MOUSE_LEAVE. mouseLeaveLi stener);

}

// Когда возникает событие Event. MOUSELEAVE…. private function mouseLeaveListener (e:Event):void {

// …переадресуем его файлу MouseEffeet. swf

1oader. contentLoaderlnfо. sharedEvents. di spatchEvent(e);

}

}

}

Листинг 12.7 демонстрирует код файла MouseEf feet. swf, относящийся к обра ботке событий.

Руководство по actionscript. часть 2, стр. 073

Листинг 12.7. Обработка события, полученного через переменную sharedEvents

package { import flash. display. Sprite; import flash. events.*;

public class MouseEffeet extends Sprite { public function MouseEffeet ( ) { // Регистрируем приемник для событий Event. MOUSELEAVE. получаемых // через переменную sharedEvents

1 oaderInfo. sharedEvents. addEventLi stener(Event. MOUSE LEAVE.

mouseLeaveListener);

// Обрабатываем события Event. MOUSE_LEAVE. полученные через переменную // sharedEvents

private function mouseLeaveListener (e:Event):void { traceCMouseEffect. mouseLeaveLi stener( ) was invoked…»); // Здесь прекращаем генерацию взрывов…

}

}

}

Дерек получил деньги за свою работу и отложил их на предстоящую поездку в Японию. Томми доволен эффектом взрывов, хотя и не уверен, что он способствовал увеличению продаж.

Что дальше?

Мы достигли больших успехов в изучении основ языка ActionScript. Если вы поняли концепцию прочитанных 12 глав, то теперь обладаете достаточными знаниями языка ActionScript, чтобы приступить к рассмотрению большей части API клиентской среды выполнения Flash. Таким образом, пришло время сделать собственный выбор. Если вы желаете продолжить знакомство с базовыми возможностями ActionScript, переходите к чтению гл. 13, в которой будет рассказано, как создавать код, позволяющий выходить из сбойных ситуаций на этапе выполнения программы. Если, с другой стороны, вы предпочитаете узнать, как использовать язык ActionScript для отображения содержимого на экране, сразу переходите к части И.

Руководство по actionscript. часть 2, стр. 074

Пиши

Обработка исключений

и ошибок

В этой главе мы познакомимся с системой языка ActionScript, предназначенной для генерации и реагирования на ошибки этапа выполнения программы, которые называются исключениями. В языке ActionScript ошибки могут генерироваться как средой Flash, так и выполняемой программой. Ошибки, генерируемые средой Flash, называются предопределенными; ошибки, генерируемые программой, называются пользовательскими. В программе можно реагировать на любые ошибки (как предопределенные, так и пользовательские), или, иначе говоря, обрабатывать их с помощью инструкции try/catch/finally. Генерация ошибок осуществляется с помощью инструкции throw.

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

I Изменения, которые будут внесены в программу «Зоопарк» на протяжении этой главы, м?; j „ являются последними для этой программы до конца книги. Чтобы завершить программу по созданию виртуального зоопарка, мы должны рассмотреть вопросы, связанные с экранным программированием и работой с мышью, которые освещены в части П. Прочитав часть II, обратитесь к приложению, чтобы узнать, как добавить графику и интерактивность в программу «Зоопарк».

Механизм обработки исключений

Если помните, класс VirtualPet определяет метод setName ( ), который присваивает значение переменной petName экземпляров класса VirtualPet. Чтобы освежить вашу память, ниже представлен соответствующий код класса VirtualPet (те части класса, которые не имеют отношения к присваиванию значения переменной petName, не приводятся):

public class VirtualPet { private var petName:String;

public function setName (newName:String):void { // Если длина заданного нового имени больше maxNameLength символов… if (newName. length > Virtual Pet. maxNameLength) { // …обрезать имя

newName = newName. substr(0, VirtualPet. maxNameLength); } else if (newName == «») { // …в противном случае, если заданное новое имя является // пустой строкой, завершить выполнение метода, не изменяя // значения переменной petName

return;

}

// Присвоить новое проверенное имя переменной petName petName = newName;

}

}

Метод setName ( ) перед тем, как изменить значение переменной petName, проверяет, является ли допустимым количество символов в новом имени животного. Если новое имя животного не является допустимым, значение переменной не изменяется; в противном случае изменение значения допускается.

Руководство по actionscript. часть 2, стр. 075

Изменим метод setName ( ) таким образом, чтобы он генерировал исключение (оповещал об ошибке) в тех случаях, когда передаваемое значение параметра newName содержит недопустимое количество символов. Позднее мы добавим код, восстанавливающий работоспособность программы после ошибки, для обработки нового исключения, генерируемого в методе setName ( ).

Для генерации исключения в нашем коде мы используем оператор throw, который имеет следующий вид:

throw выражение

В предыдущем коде выражение — это значение данных, описывающее некоторую необычную или проблематичную ситуацию. Использование оператора throw для оповещения об ошибке иногда называется «генерацией исключения». Язык ActionScript позволяет использовать любое значение в качестве выражения выражение в операторе throw. Например, значением выражения выражение может быть строковый литерал «Something went wrong!11 (Что-то не получилось!) или числовой код ошибки. Однако корпорация Adobe рекомендует использовать в качестве значения выражения выражение экземпляр предопределенного класса Error (или одного из его подклассов), считая этот подход хорошей практикой. Класс Error является стандартным классом, представляющим исключительные ситуации в программе. Его переменная экземпляра message используется для описания ошибки.

Оператор throw останавливает выполнение кода и передает значение выражения выражение в специальный блок кода, называемый блоком catch, который будет реагировать на возникшую проблему, или обрабатывать ее. Прежде чем рассмотреть, как работает блок catch, изменим метод setName ( ) таким образом, чтобы он генерировал исключение с помощью оператора throw, когда получено недопустимое значение параметра petName:

public function setName (newName:String):void { // Если длина заданного нового имени больше maxNameLength символов… if (newName. length > Virtual Pet. maxNameLength || newName = «») { // …генерируем ошибку

throw new Error(«Invalid pet name specified.»);

}

// Присвоить новое допустимое имя переменной petName petName = newName;

В нашей новой версии метода setName ( ), если значение параметра newName является недопустимым, мы используем оператор throw для прекращения выполнения метода вместо того, чтобы просто обрезать указанное имя, как мы делали раньше. Кроме того, мы указываем описание проблемы — «Invalid pet name specified» (Указано недопустимое имя животного) — в качестве аргумента конструктора Error. Это описание определяет ситуацию, из-за которой возникла ошибка. Конструктор Error присваивает это описание переменной message созданного объекта Error.

Если метод setName ( ) не обнаружит никаких проблем со значением параметра newName, то он завершится нормально, и код, вызвавший его, может быть уверен, что работа, возложенная на этот метод, выполнена успешно. В противном случае блок catch должен обработать возникшую проблему. Блок catch является частью большой инструкции, называемой инструкцией try/catch/finally. Инструкция try/catch/ fin ally предусматривает план восстановления для кода, который может сгенерировать исключение. Вот общая структура типовой инструкции try/ catch/finally:

try {

// Код в этом блоке может генерировать исключения } catch (е:тип) {

II Код в этом блоке обрабатывает возникшую проблему } finally {

// Код в этом блоке выполняется всегда, независимо от того, // сгенерировал блок try исключение или нет

}

В приведенном коде ключевое слово try сообщает среде Flash, что мы собираемся выполнить код, который может сгенерировать исключение. Блок catch обрабатывает исключения, генерируемые блоком try. Код в блоке catch выполняется в том, и только в том случае, когда код в блоке try сгенерировал исключение. Код в блоке final 1 у выполняется всегда после завершения выполнения блока try или catch. Блок final 1 у инструкции try/ catch/finally обычно содержит очищающий код, который должен выполняться независимо от того, было сгенерировано исключение в соответствующем блоке try или нет.

Руководство по actionscript. часть 2, стр. 076

Обратите внимание на типовую структуру.

1. Блок try выполняет код, который может сгенерировать исключение.

2. Код в блоке try использует оператор throw для оповещения о любых ошибках.

3. Если в блоке try не возникло никаких ошибок, то он выполняется полностью и программа пропускает блок catch.

4. Если в блоке try была сгенерирована ошибка, то его выполнение прекращается и начинается выполнение блока catch. Блок catch способен обрабатывать любые ошибки, возникающие в блоке try.

5. Выполняется блок final 1 у.

В большинстве случаев блок finally не требуется и, следовательно, опускается. В последующих примерах мы будем опускать блок finally. Далее, в разд. «Блок finally», мы рассмотрим пример использования этого блока.

Когда выполняется блок catch, он получает значение выражения выражение оператора throw в качестве параметра. В блоке catch это значение может помочь выявить ошибку, сгенерированную в блоке try. Образно говоря, код, в котором возникла проблема, бросает (throw) исключение (передает объект Error) в блок catch, который получает этот объект в качестве параметра (ловит (catch) его).

I Далее в разд. «Передача исключений вверх по иерархии объектов» мы выясним, что м$ 4 щ произойдет в том случае, если возникшая ошибка не будет обработана. —За*-

Рассмотрим пример инструкции try/catch/finally: try {

somePet. setName(«James»);

// Если мы находимся здесь, значит, исключение не возникло; // продолжаем выполнение, как планировалось ранее. traceCPet name set successfully.»); } catch (e:Error) { // ОШИБКА! Недопустимые данные. Выводим предупреждение. traceC’An error occurred: » + e. message);

}

Если при вызове метода pet. setName ( ) внутри предыдущего блока try оператор throw метода setName ( ) не будет выполнен (если не произойдет никакой ошибки), то все последующие инструкции в блоке try будут выполнены успешно и программа полностью пропустит блок catch. Однако если метод setName ( ) сгенерирует исключение, программа немедленно прекратит выполнение инструкций в блоке try и перейдет к выполнению блока catch. В блоке catch значением параметра е является объект класса Error, переданный в оператор throw внутри метода setName ( ).



Полезные ссылки
Случайные записи
  • 04.03.2011">Руководство по actionscript. часть 5, стр. 048
  • 20.03.2011">Руководство по actionscript. часть 2, стр. 074
  • 15.06.2010">Самоучитель по креативному веб-дизайну. Книга 4, стр.55
  • 17.06.2012">«Лаборатория Касперского» отметила сокращение количества спама
  • 05.03.2011">Руководство по actionscript. часть 5, стр. 018
  • 02.03.2011">Руководство по actionscript. часть 5, стр. 107
  • 12.03.2011">Руководство по actionscript. часть 3, стр. 122
  • 02.03.2011">Руководство по actionscript. часть 5, стр. 081
  • 22.07.2011">Интернет-магазины решат проблему с отсутствием качественной одежды
  • 10.05.2010">Самоучитель по креативному веб-дизайну. Книга 1, стр.123
  • 07.03.2011">Руководство по actionscript. часть 4, стр. 103
  • 18.05.2010">Самоучитель по креативному веб-дизайну. Книга 2, стр.41
  • 20.05.2012">Владельцы iPhone5.com отказались от доменного имени из-за жалобы Apple
  • 18.05.2010">Самоучитель по креативному веб-дизайну. Книга 2, стр.58
  • 04.03.2011">Руководство по actionscript. часть 5, стр. 052
Опрос

Какие цвета вы предпочитаете?

View Results

Loading ... Loading ...