3. dubna 2009

Distribuce třídy jako Flash SWC komponenty v ActionScriptu 3

Občas se stane, že klient, pro kterého vyvíjíte Flash aplikaci, požaduje i zdrojové FLA a AS soubory. Chcete-li klientovi vyhovět, narazíte na vážný problém. Aby klient mohl aplikaci ze zdrojových souborů znovu zkompilovat, musí mít, vedle kódu, který jste napsali speciálně pro něj, k dispozici i vaše soukromé knihovny tříd. Jejich vývoj si klient ovšem nezaplatil. Jste tedy nuceni nedobrovolně odtajnit své vlastní know-how, nemluvě o nutnosti sestavit zvláštní distribuci vašich tříd, čímž si logicky jen „zaděláte“ na další a další problémy.

Situace si přímo žádá řešení, které by umožnilo dodat klientovi zdrojové soubory jeho Flash aplikace v takové sestavě, aby neměl přístup k vašemu „frameworku“ a přitom mohl zdrojový FLA soubor aplikace bez problémů zkompilovat do SWF, jako by zdrojáky vašeho frameworku měl.

Takové řešení naštěstí existuje a je ke všemu i velmi jednoduché a elegantní !!!

Třídu napsanou v ActionScriptu a uloženou v AS souboru lze totiž zkompilovat do SWC souboru, který pak v libovolné aplikaci můžete použít jako standardní Flashovou komponentu. Komponenta obsahující zkompilovanou třídu bude přitom uložená přímo v knihovně FLA souboru aplikace. Jako součást FLA souboru bude tedy distribuovaná ke klientovi zcela automaticky bez nutnosti přikládat k distribuci původní AS zdrojový soubor definice třídy.

Z pohledu ActionScriptu samotné klientovy aplikace přitom vše zůstane při starém. Instanci třídy vytvoříte v kódu Flash aplikace úplně stejným způsobem, jako byste se odkazovali na definici třídy uloženou v AS souboru, tedy např.:


var identifikator:Trida = new Trida();


Navíc SWC soubor obsahuje kód ve zkompilované podobě a podle toho, co jsem se o tomto formátu dočetl, má SWC lepší ochranu proti dekompilaci než SWF, což snižuje pravděpodobnost úniku vašeho know-how. Zkrátka a dobře, řeší to vše, co řešit potřebujete.

Pojďme si tedy ukázat, jak lze definici třídy, kterou máte uloženou ve zdrojovém AS souboru, zkompilovat do SWC-komponenty a tuto komponentu pak importovat do knihovny FLA aplikace. Vyzkoušíme si to na jednoduchém příkladě - v editoru Adobe Flash CS3 Profesional.

Kompilace třídy z AS do SWC

Nejprve si vytvořte speciální adresář pro kompilaci SWC-komponenty. Pojmenujte si ho podle svého. V tomto adresáři si pak založte nový soubor „Pokus.as“, který bude obsahovat kód ActionScriptu 3:


package
{
 import flash.display.MovieClip;

 public class Pokus extends MovieClip
 {
  private var str:String;

  public function Pokus()
  {
   this.str = "Konstruktor funguje";
  }

  public function setStr(str:String = "Zadal jsem řetězec ..."):void
  {
   this.str = str;
  }

  public function getStr():String
  {
   return this.str;
  }
 }
}


Je to velmi jednoduchá třída, jejíž instance bude mít jedinou soukromou vlastnost „str“ a pouze dvě veřejné metody: jednu pro nastavení hodnoty vlastnosti „str“ a druhou pro čtení hodnoty vlastnosti „str“. Pro ilustraci postupu to bohatě postačí. Důležité je to, že třída je odvozena od třídy MovieClip. Je to kvůli způsobu její kompilace do SWC komponenty, který uvádím dále.

Soubor „Pokus.as“ uložte. Můžete ho i zavřít. Dál už do něj zasahovat nebudete.

Otevřete si nový FLA soubor ActionScriptu 3 a uložte ho stejného adresáře jako „Pokus.as“. Dejte mu například vznosné jméno „tovarna_swc.fla“.

V tomto FLA souboru začněte vytvářet nový symbol knihovny. Stiskněte např. CTRL+F8. Objeví se panel pro nastavení jména a typu symbolu. Jméno nastavte na „mc“ a typ symbolu na MovieClip. Zůstaňte ještě na panelu a rozklikněte si ho do režimu „podrobné nastavení“. Zde zaškrtněte „Exportovat pro ActionScript“. Ponechte zaškrtnuté „Exportovat v prvním snímku“. Do políčka Základní třída vepište „Pokus“. Bázová třída zůstane vyplněná jak byla, tzn. „flash.display.MovieClip" (to je důvod, proč musí být třída „Pokus“ odvozena od třídy MovieClip). Mělo by to vše vypadat, jako na následujícím obrázku. Pokud je to tak, klikněte na OK.



V knihovně klikněte pravým tlačítkem na symbol „mc“ a vyberte v kontextovém menu volbu „Definice komponenty“. Objeví se další dialogový panel (viz obrázek níže), kde do kolonky „Třída“ vepište „Pokus“. Pak ještě pro pořádek klikněte na tlačítko volby ikony symbolu a vyberte „Ikona komponenty ActionScript“. Panel zavřete pomocí OK.



Nyní jste již připraveni ke kýženému zkompilování AS definice třídy „Pokus“ do formy SWC-komponenty. Klikněte v knihovně na symbol „mc“ opět pravým tlačítkem myši a vyberte volbu „Exportovat soubor SWC“. Soubor komponenty uložte pod jménem „Pokus.swc“.

Hotovo. Soubor „tovarna_swc.fla“ již můžete zavřít. Nebudete ho dále potřebovat, jako nebudete potřebovat již ani soubor „Pokus.as“.

Použití SWC-komponenty v aplikaci

Otevřete si nový FLA soubor ActionScriptu 3 a uložte ho pod jménem např. „aplikace.fla“ do jiného adresáře, než kde máte uložený soubor „Pokus.as“. Zkontrolujte také, že v nastavení publikování není uvedená cesta ke třídám, která by vedla k souboru „Pokus.as“. Musíte mít prostě jistotu, že při kompilaci FLA do SWF se budete opravdu odkazovat na definici třídy uloženou v SWC-komponentě a nikoli na definici v původním AS souboru.

Soubor „Pokus.swc“ překopírujete do adresáře komponent - např. „C:\Program Files\Adobe\Adobe Flash CS3\cs\Configuration\Components“. Pak v souboru „aplikace.fla“ klikněte na panel komponent a zadejte volbu „Načíst znovu“. Ve stromu komponent by se ve složce „Standardní komponenty“ měla objevit nová položka - symbol „mc“.

Přetáhněte symbol „mc“ na scénu. Tím se zároveň přenese do knihovny v souboru „aplikace.fla“. Nyní zkuste do prvního snímku na hlavní časové ose přidat kód:


var p:Pokus = new Pokus();
trace(p.getStr());


a otestovat aplikaci. Pokud jste neudělali chybu, vypíše se text „Konstruktor funguje“. Zkuste si soubor „aplikace.fla“ uložit, zkompilovat (publikovat) do SWF a otestovat v prohlížeči. Zkuste si pohrát a použít i metodu třídy „setStr“. Nakonec soubor „aplikace.fla“ uložte a zavřete.

Pokud otevřete soubor „aplikace.fla“ na jiném počítači, bude v jeho knihovně stále přítomna komponenta „mc“, která bude obsahovat již předkompilovanou definici třídy „Pokus“. Kompilace FLA do SWF tedy bude možná i zde, aniž byste zdrojový AS soubor třídy „Pokus“ museli mít k dispozici.

Pěkné, že?

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".