9. února 2009

Jak v AS3 vytvorit tridu, ktera bude emitovat udalosti

Pokud používáte při programování v Action Scriptu instance vlastních tříd, budete určitě dříve nebo později potřebovat vytvořit třídu, která bude umět emitovat vlastní události. V následujícím článku popisuji jeden ze způsobů, jak na to v Action Scriptu 3.

Článek je určen především začátečníkům. Nechci zde ale zbytečně opakovat to, co si můžete sami přečíst v manuálu. Proto vám před dalším čtením doporučuji nastudovat si kapitolu: Zpracování událostí v dokumentaci.

Vypíchnu jen fakt, který je důležitý pro pochopení dalšího textu. Totiž, jakmile dojde za běhu Flash aplikace k nějaké události, Flash vytvoří speciální objekt události, který "pošle" do cíle události, a to buď přímo, nebo cestou od nejvyššího nadřazeného objektu, ve kterém je cíl události uložen (podle typu cíle). Cílem události může být například tlačítko na které jsme klikli myší a nebo přímo instance vaší vlastní třídy, která právě emitovala událost. V aplikaci pak můžete objekt události „odchytit“ pomocí tzv. posluchače události (listeneru) a příslušnou událost zpracovat pomocí funkce, kterou příslušnému listeneru přidáte (zaregistrujete).

Z tohoto faktu vyplývá, že budete-li si chtít vytvořit třídu, která bude emitovat vlastní události, musíte třídu "naučit" vytvářet a posílat objekty událostí svým instancím, přesněji řečeno do tzv. "toku událostí" v samotné aplikaci, kde již bude objekt události automaticky nasměrován k instanci vaší třídy.

Není to vůbec složité. Nejlépe bude, když to ukážu na nějakém praktickém příkladu.

Představte si, že chcete napsat třídu, která rozšíří možnosti třídy Timer. Chcete mít například k dispozici událost, která se emituje, kdykoli Timer spustíte ... No, při troše snahy by se snad dal najít případ, proč byste právě toto měli řešit pomocí mechanismu události a listeneru, když vlastně o okamžiku, kdy Timer spustíte rozhodujete přímo v kódu samotné aplikace. Ale budiž. Berte to prostě jako samoúčelnou ukázku "pro ukázku", jak to zapsat v kódu aplikace. Pro účely tohoto článku totiž potřebuji mít alespoň dvě rozšíření, dvě nové události.

Druhou událostí, kterou budete chtít mít v rozšiřující třídě Timer(u) k dispozici, je událost, která nastane, když váš upravený Timer "tikne" právě po n-té.

K oběma událostem budte chtít také přidat možnost poslat do aplikace nějaký textový řetězec (zprávu, serializovaná data), a to přímo prostřednictvím objektu události. To už by potřebu prvního typu události docela obhájilo, že?

Rozumí se samo sebou, že stávající události třídy Timer, TimerEvent.TIMER a TimerEvent.TIMER_COMPLETE, bude vaše třída muset umět emitovat také. Zbavovat se jich by v daném případě bylo zbytečné a "signalizovalo by to zřejmě nějakou poruchu".

Takže suma sumárum: potřebujete napsat třídu, která rozšíří možnosti Timeru o schopnost emitovat další dvě události vedle stávajích dvou, přičemž bude také umět poslat do aplikace textový řetězec přímo jako součást objektu událostí, které jste přidali. Až si projdete níže uvedený kód, jistě přijdete na to, jak vaši třídu naučit přidávat textový řetězec ke všem čtyřem typům (druhům) události, a to tím, že původní typy událostí třídy Timer nahradíte svými vlastními. Ale to již moc předbíhám.

Vaše třída se bude jmenovat např. MyTimer a její užití v aplikaci bude vypadat, dejme tomu, takto:


import MyTimerEvent;

var customTimer:MyTimer = new MyTimer(1000, 10, 5);

customTimer.addEventListener(TimerEvent.TIMER, customTimer_TIMER_handler);
customTimer.addEventListener(TimerEvent.TIMER_COMPLETE, customTimer_TIMER_COMPLETE_handler);
customTimer.addEventListener(MyTimerEvent.TIMER_STARTED,customTimer_TIMER_STARTED_handler);
customTimer.addEventListener(MyTimerEvent.TIMER_TICKED_N_TIMES,customTimer_TIMER_TICKED_N_TIMES_handler);

customTimer.start();

function customTimer_TIMER_handler(event:TimerEvent):void
{
   //kod pro zpracovani udalosti
}

function customTimer_TIMER_COMPLETE_handler(event:TimerEvent):void
{
   //kod pro zpracovani udalosti
}

function customTimer_TIMER_STARTED_handler(event:MyTimerEvent):void
{
   //kod pro zpracovani udalosti
}

function customTimer_TIMER_TICKED_N_TIMES_handler(event:MyTimerEvent):void
{
   //kod pro zpracovani udalosti
   //predany textovy retezec je dostupny v event.eventData
}


Všimněte si prosím, že právě ve dvou listenerech se vyskytují odkazy na "vaše" vlastní, přidané typy (objekty) událostí, jmenovitě typy MyTimerEvent.TIMER_STARTED a MyTimerEvent.TIMER_TICKED_N_TIMES. Stejně jako třída MyTimer, se zatím odkazují "do prázdna". Ale to hned napravíme.

Začneme samotnými objekty přidaných událostí.

Objekty přidaných událostí nejsou ničím jiným, než instancemi třídy, která musí být odvozená od třídy Event. V uvedeném případě jsou instancemi vaší třídy MyTimerEvent, vytvořené právě pro tento účel:


package
{
   import flash.events.Event;

   public class MyTimerEvent extends flash.events.Event
   {

      public static const TIMER_STARTED:String = "MyEvent_Timer_started";
      public static const TIMER_TICKED_N_TIMES:String = "MyEvent_Timer_ticked_n_times";

      public var eventType:String;
      public var eventData:String;

      public function MyTimerEvent(_type:String = MyTimerEvent.TIMER_STARTED, _data:String = "")
      {
         this.eventData = _data;
         this.eventType = _type;
         super(this.eventType);
      }

      public override function clone():Event
      {
         return new MyTimerEvent(eventType, eventData);
      }
      public override function toString():String
      {
         return formatToString("MyTimerEvent", "eventType", "bubbles", "cancelable", "eventPhase", "eventData");
      }
   }
}


Jak sami vidíte, ve třídě MyTimerEvent se neodehrává nic moc dramatického. Slouží přeci pouze ke generování samotných objektů událostí, a jejím primárním účelem je vlastně to, že jsou zde definovány statické konstanty, obsahující unikátní textové řetězce popisující konkrétní typ příslušné události. Pomocí těchto konstatnt spárujete události emitované třídou s příslušnými listenery v aplikaci, a to jednoznačně.

Třída MyTimerEvent obsahuje dále konstruktor, který umožňuje vytvořit správným způsobem objekt vlastní události příslušného typu (povšimněte si přikazu super) současně s možností přidat do objektu vlastní proměnnou ve formě textového řetězce. Toť vše. Tedy až na povinné překrytí metod clone() a toString() bázové třídy Event. V případě metody clone() jde o to, aby metoda vracela novou instanci MyTimerEvent se správně nastavenými hodnotami parametrů.

No, a nyní se již konečně dostávám k tomu, jak vlastně napsat třídu, která bude umět emitovat vlastní události. Opakuji, že v uvedeném příkladu to mohou být nejen události, které nabízí sám Action Script 3 ve třídě Timer, ale i dvě další, vaše vlastní události.

Než přidám ukázku kódu, nesmím opomenout preventivní vzkaz pro odborníky: nová třída proti očekávání nerozšiřuje samotnou třídu Timer, ale jako svoji bázovou třídu používá Sprite. Je to prostě příklad pro příklad. Sprite je mimo to dobrá univerzální bázová třída. Podstatné je, že Sprite je podtřídou třídy EventDispatcher (i když vzdálenou). Proč je to podstatné, to prozradím až si prohlédnete následující kód:


package
{
   import flash.display.Sprite;
   import flash.events.TimerEvent;
   import flash.utils.Timer;
   import MyTimerEvent;

   public class MyTimer extends Sprite
   {

      public var myTimer:Timer;
      private var pointer:int;
      private var n:int;
      
      public function MyTimer(delay:Number, repeatCount:int = 0, n:int = -1)
      {
         myTimer = new Timer(delay, repeatCount);
         this.n = n;
      }
      
      public function start():void
      {
         pointer = 0;
         myTimer.addEventListener(TimerEvent.TIMER, timer_handler);
         myTimer.addEventListener(TimerEvent.TIMER_COMPLETE, timer_handler);
         myTimer.start();
         this.dispatchEvent(new MyTimerEvent());
      }

      private function timer_handler(event:TimerEvent):void
      {
         this.dispatchEvent(event);
         if(pointer == n)
         {
            this.dispatchEvent(new MyTimerEvent(MyTimerEvent.TIMER_TICKED_N_TIMES, "textovy retezec, ktery predame aplikaci"));
         }
         pointer++;
      }
   }
}


Všimněte si, že schopnost emitovat události dodá vaší třídě metoda dispatchEvent(EventObject). Tuto metodu propůjčila vaší třídě třída EventDispatcher, od níž je řetězcem dědění odvozena právě vaše bázová třída Sprite. Ve Sprite získáte další a další šikovné metody, které v ukázkovém příkladě sice nevyužijete, ale v jiném případě nejspíše ano. Přitom jakkoli svou třídu odvozujete od Sprite a nikoli od Timer, o metody třídy Timer nepřijdete. Jsou totiž dostupné jako metody instance myTimer.

Parametrem metody dispatchEvent(EventObject) může být buď objekt události třídy Action Scriptu (v našem případě se jedná o objekty události třidy Timer typu TimerEvent.TIMER a TimerEvent.TIMER_COMPLETE), nebo, jak si můžete všimnout v kódu třídy MyTimer, může objektem události dispatchEvent být též instance nějaké vaší třídy odvozené od třídy Event (zde MyTimerEvent). Může se tedy jednat přímo o nově vytvořený objekt vlastní události s příslušnými parametry definujícími jednoznačně typ a v uvedeném případě i data události.

V případě přebírání událostí z tříd Action Scriptu jste odkázaní na jejich nativní mechanismus emitování, jako například zde v případě instance třídy Timer. V případě vlastních událostí je však můžete emitovat doslova kdy se vám zlíbí, a to prostým přidáním jediného řádku kódu do vaší vlastní třídy, např. this.dispatchEvent(new MyTimerEvent()).

No, vždyť jsem říkal, že je to jednoduché.

Na závěr nápověda, jak přidat možnost posílat textový řetězec (obecně jakokoli proměnnou či několik proměnných) i k nativním událostem instance třídy Timer.

Nejprve si do třídy MyTimerEvent přidejte další dvě konstanty s unikátní hodnotou, analogické konstantám TimerEvent.TIMER a TimerEvent.TIMER_COMPLETE. Nyní již ve třídě MyTimer v místech "přeposílání" nativního EventObjektu události třídy Timer použijte jako parametr metody dispatchEvent přímo konstruktor instance třídy MyTimerEvent, kde jako parametry konstruktoru instance použijte příslušnou konstantu třídy MyTimerEvent a samosebou i kýžený textový řetězec. Pak již stačí jen upravit listenery v samotné aplikaci "a je to".
 

3. prosince 2008

Flash (Flex) nebo Silverlight?

Jak asi dopadne souboj vzájemně si konkurujících technologií Adobe Flash a Microsoft Silverlight? Stane se časem jedna z nich dominantní? Má smysl se pro jistotu naučit obě, abychom byli připraveni, až jedna z nich náhle odejde ze scény?

Zachovejme paniku :o)

Jakmile se člověk oddá futuristickým úvahám, ocitne se vždy na tenkém ledě. Navíc, pokud svou věšteckou kouli máte také zahrabanou někde v koutě skříně, protože jste se nemohli při jejím používání zbavit dojmu, že:

a) se vás snaží odsunout do role pasivního diváka,
b) je asi nějak porouchaná, neb jste místy nevěděli, na co se vlastně díváte,

tak jste vlastně odkázaní jen na své vlastní znalosti a svou vlastní fantazii. Přiznejme si, že obojího není nikdy dost, tedy pokud se náhodou nejmenujete Leonardo nebo Jules.

Přesto mi to nedá a jisté futuristické úvahy se v otázce souboje Flashe a Silverlightu dopustím. I když pro mne osobně to bude vlastně vzpomínání, tak jakápak vlastně futuristika (myšleno turistika do budoucnosti), že?

Souboj vyvíjejících se technologií, které různým způsobem řeší v podstatě tytéž potřeby a problémy, jsem totiž už jednou prožil, a to od začátku až do konce. Bylo to v oblasti vývoje CAD systémů. "Kreslit" v CADu jsem začal už v roce 1989 a shodou okolností v systému AutoCAD. Prakticky už od začátku jsem AutoCAD používal nejen jako náhradu klasického rýsovacího prkna (2D), ale začal jsem v něm i modelovat ve 3D (verze 11 měla už primitivní objemový modelář, ale své úplně první 3D zakázky jsem byl s to řešit, světe div se, již ve verzi 10, která měla alespoň možnost zadat z-tovou souřadnici a uměla zobrazit perspektivu). Proč se o tom zmiňuji?

Musím totiž začít trochu zeširoka. Na počátku 90-tých let byla situace taková, že existovalo několik velmi drahých softwarových balíků pro návrh ve 3D. Ty byly pro mne nedostupné. Začínal jsem totiž zrovna na volné noze a chtěl jsem se vyhnout miliónovému úvěru (jo jo, tenkrát byl milión opravdu milión). Přitom 3D jsem pro sebe viděl jako možnost se úspěšně prosadit na trhu a hlavně "směr". Proto jsem se musel spokojit s relativně levným řešení ve "střední" třídě a naučit se využívat všechny možnosti na maximum. To, že jsem "se dal" zrovna na AutoCAD, byla více méně náhoda. Mohl jsem zrovna tak začít s CADKey či MicroStation.

Tyto tři "střední" CAD systémy si tehdy konkurovaly podobně jako si dnes konkuruje Flash se Silverlight. Stejně tak totiž mířily do oblasti, která byla zatím vyhrazená jen opravdu masivním a drahým řešením, anebo nebyla řešená vůbec. Prakticky celá 90-tá léta byla ve střední třídě CADů ve znamení objevování a přidávání dalších a dalších funkčností, až ...

... až se vývoj posunul k hranici, kdy první derivace notoricky známé S-křivky inovačního procesu začíná klesat k nule ... ehm ... kdy se tempo vývoje opět začíná zpomalovat na původní úroveň, protože "území" je dobyto a zmapováno, silnice postaveny, telefonní linky nataženy a už se jen sází kytky do zahrádek.

Zažil jsem v době bouřlivého rozvoje řadu vášnivých diskusí na téma, který CAD systém je lepší. Mně ty debaty ale vždy přišly trochu zbytečné. Jak už jsem říkal: musel jsem se naučit využívat AutoCAD na maximum a díky tomu jsem v něm pak dokázal dělat věci, které jiný nedokázal dělat ani s o třídu vyšším softwarem, anebo jsem byl schopný se mu alespoň vyrovnat v rychlosti i výsledku. AutoCADu jsem prostě dokonale rozuměl, znal jeho světlé i tmavé kouty, "cítil" ho a uměl (a stále umím) v něm myslet. Musel jsem se také pořád učit, protože AutoCAD přinášel s každou další verzi řadu nových možností a dílčích změn. Abych přešel na jiný CAD systém, musel by mi nabídnout něco opravdu trvale výjimečného, abych měl opravdu reálný důvod začít úplně od začátku, sžít se odlišnou filozofií a pak se dostat vlastně zase do stejné situace, kde jsem byl před tím s AutoCADem. Nic takového mi žádný systém nikdy nenabídl. A to vždy rozhodlo.

Je to myslím podstatné i v případě Flashe a Silverligtu. Oba systémy jsou už ve fázi velmi bouřlivého vývoje. Obě technologie cílí na řešení v podstatě stejného okruhu technických problémů a v pozadí obou stojí opravdu chytří lidé i silní producenti. A tak Flash i Silverlight "dělají" a budou "dělat" v podstatě totéž, jen "různým" způsobem a pomocí jinak postavených editačních nástrojů. Pokud se budou obě technologie vyvíjet dál (a to samotné se opravdu předpovědět nedá), obě se časem stanou velmi propracované a tedy náročné na zvládnutí. Vyžadují a budou vyžadovat nestálé učení se. A co je velmi důležité, obě technologie se budou muset naučit velmi dobře komunikovat se svým okolím a také se sebou navzájem - přestanou tedy být oddělenými světy.

Brzo se tedy dostaneme do situace, kdy jakékoli zadání budeme schopni řešit Flashem stejně dobře a efektivně jako Silverlightem, nebo i jejich libovolnou kombinací. Obě technologie na to potenciál mají, očividně tím směrem i míří, takže myslím jde jen o to, jestli to jejich producenti sami nezabalí nebo jinak nepokazí.

Marketing sem a mýty tam. Prostě problém není v tom, co která technologie bude umět a druhá ne. Spíš rozhodne, co v té či oné technologii budeme sami schopní udělat a do jaké hloubky jí vlastně budeme rozumět. Zákazník s příslušnými požadavky se pak vždy najde. A totéž platí i o spolupracovnících z druhé strany barikády.

Tak pravil ten, jenž bude muset jít za chvíli na oběd ... :o)