Allora una bella applicazioncina in XML è oramai d’obbligo per tutti. Infatti oramai TUTTO è Xml, non che la cosa mi dispiaccia , in fin dei conti lavorare con xml è facile come lavorare con un grafo, di fatto un file xml E’ un grafo, un grafo fatto molto bene in verità con tutti gli archi giusti al posto giusto … insomma un piacere, soprattutto dopo aver trattato quei grafi stortissimi del corso di Algoritmi 2.

Avevo pensato inizialmente di postare un lettore di feed rss che pure avevo cominciato a sviluppare in CS, ma durante la lavorazione sono stato preso dall’entusiasmo e mi si è imputtanatto tutto il codice, e visto che mi scoccio di ripulirlo (e sopratutto di renderlo nuovamente funzionante), posto questo menu.

Si tratta sostanzialmente di un menu (anche abbastanza brutto ad ora) le cui voci sono prelevate da un file xml i cui nodi rappresentano ciascuno una voce del menu e cui sottonodi (i figli) sono sottomenu associati a quella voce. Quindi poichè un nodo xml puo contenere uno o piu altri nodi xml che a loro volta contengono altri nodi (e cosi via), i livelli possibili di una struttura xml sono infiniti e cosi saranno i livelli possibili del mio menu.

Bene, comincio a postare i file, cosi magari ce li si può scaricare subito senza stare a perdere tempo a leggerne la descrizione nel mio italiano stentato.

eccoli qua tutti zippati (anzi rarrati): [menuxml.rar]

Allora, il tar che avete appena scaricato (e che probabilmente avete anche già cestinato) contiene vari files, tra i quali menuXml.flp che è il file di progetto che sto prendendo l’abitudine di usare e da cui, come al solito, vi consiglierei di cominciare se volete dare uno sguardo al codice.

Aperto quindi il progetto, troviamo innanzitutto che il file menuXml.fla è come al solito vuoto di qualsiasi cosa, salvo l’indicazione della Document Class menuXmlMain.as:

package org.Main{
import flash.events.*;
import flash.display.*;
import org.XMLutils.*;
public class menuXmlMain extends MovieClip {
private var fetcherXML:XMLFetcher=null;
public function menuXmlMain(){
fetcherXML=new XMLFetcher("menu.xml");
fetcherXML.addEventListener(Event.COMPLETE,xmlLoaded);
}
private function xmlLoaded(e:Event):void{
var menu:XMLmenu=new XMLmenu(fetcherXML.menuxml);
addChild(menu);
}
}
}

UnaDocument Class molto scarna quindi. Non faccio molto altro infatti, che istanziare un’istanza (scusate il gioco di parole) di un oggetto XMLFetcher che ho scritto per incapsulare il load del file xml. Pasto qui il codice della classe, ma è proprio il minimo indispensabile per ottenere un puntatore alla radice di una struttura XML partendo dall’indirizzo del file che la contiene.

XMLFetcher:

package org.XMLutils{
import flash.events.*;
import flash.net.*;
public class XMLFetcher extends EventDispatcher {
private var XMLmenu:XML=null;
private var myLoader:URLLoader=null;
public function XMLFetcher(XML_URL:String) {
var myXMLURL:URLRequest=new URLRequest(XML_URL);
myLoader=new URLLoader(myXMLURL);
myLoader.addEventListener(Event.COMPLETE,xmlLoaded);
}
public function get menuxml():XML {
return XMLmenu;
}
private function xmlLoaded(e:Event):void {
XMLmenu=new XML(myLoader.data);
dispatchEvent(e);
}
}
}

Ne do giusto una descrizione di massima per dovere di cronaca: All’interno del costruttore, a cui si passa come argomento il link al file xml, utilizzo un oggetto URLRequest per comporre l’url al file e un oggetto URLLoader per caricarlo e concludo registrando la funzione xmlLoaded all’evento Event.COMPLETE generato dall’oggetto URLLoader che ha caricato il file. All’interno di questa funzione inizializzo un oggetto XML con i dati caricati dall’URLLoader. Infine propago l’evento Event.COMPLETE chiamando dispatchEvent(e).

Quindi una volta caricato il file xml e inizializzato l’albero XML, un evento Event.COMPLETE viene catturato nella Document Class e viene eseguita la funzione xmlLoaded (sì, si chiama allo stesso modo sia nella Document Class che nella classe XMLFetcher :P ) che istanzia finalmente un nuovo XMLmenu passandogli la radice della struttura xml letta dall’XMLFetcher (in realtà c’è anche un secondo parametro al costruttore di XMLmenu, che è il nodo genitore, ma lo vedremo in seguito, ora lo omettiamo e lasciamo il valore di default che null.

Veniamo quindi al cuore del progetto: la classe XMLmenu.

Innanzitutto gli attributi:

private var XMLroot:XML=null;
private var button:gxButton=null;
private var XMLparent:XMLmenu=null;
private var childMenuArr:Array=new Array();

Ogni voce nel menu è rappresentata da un’istanza di questa classe, che a sua volta è composta da un’istanza di gxButton, che è un oggetto che disegna un quadrato con una scritta sopra e con poc’altro d’interessante e di cui semmai dopo parlerò, un puntatore a un oggetto XMLparent:XMLmenu che rappresenta il menu padre della voce corrente, e tra poco vedremo perchè ci serve questo riferimento, un array childMenuArr che conterrà i riferimenti ai sottomenu e naturalmente un nodo XML da cui attingere ai dati per la voce del menù.

Il costruttore della classe anch’esso fa molto poco: valorizza l’attributo XMLroot con il nodo xml corrente e l’attributo XMLparent con il menu che ha generato questo sottomenu, entrambi valori passati come argomenti al costruttore.

public function XMLmenu(XMLroot_:XML=null,XMLparent_:XMLmenu=null) {
XMLroot=XMLroot_;
XMLparent=XMLparent_;
button=new gxButton(uint(XMLroot.@color));
button.setText(String(XMLroot.@nome));
button.doDrawRect();
button.addEventListener(MouseEvent.CLICK,clickFunc);
addChild(button);
}

L’unica cosa da notare è la registrazione dell’evento MouseEvent.CLICK generato dal bottone alla funzione clickFunc():

private function clickFunc(event:MouseEvent):void {
if (XMLparent!=null) {
XMLparent.closeAllButMe(this);
}
visita();
}

questa funzione una volta accertatatisi che il nodo non sia la radice dell’albero, chiama il metodo closeAllButMe(this) del menu che ha generato il bottone che è stato clikkato, tale menu è il genitore il cui riferimento è salvato nell’attributo XMLParent, a questa funzione passiamo anche un riferimento a this cioè all’oggetto chiamante. Questo perchè? già perchè? Bhè fondamentalmente l’idea è che una volta che si è clicckato su una voce del menu si deve aprire il sotto menu, e questo lo fa la chiamata a visita() alla fine della funzione, ma contemporaneamente si devono anche chiudere tutti i sotto menu dei fratelli della voce chiamata, e questo compito è svolto ovviamente dal padre della voce chiamata che cancellerà quindi tutti i sui figli tranne quello che è stato appena clikkato (e per questo passiamo il riferimento this).

E’ più contorto da dire che da capire….

Vediamo prima la funzione visita() che apre i sottomenu:

public function visita():void {
var i:uint=0;
for each (var child:XML in XMLroot.voce) {
var childMenu:XMLmenu=new XMLmenu(child,this);
childMenu.y=i*childMenu.height+5*i;
childMenu.x=childMenu.width+5;
childMenuArr.push(childMenu);
addChild(childMenu);
i++;
}
}

Molto semplice anche questa. Con un ciclo for each scorro tutti i figli del nodo xml rappresentato dal bottone cliccato, e per ogni figlio istanzio un nuovo menu figlio che ora come secondo parametro avrà this cioè il riferimento a suo padre. Dopo aver creato e posizionato come più mi aggrada il nuovo menu lo inserisco nell’array dei menu figli del menu corrente (childMenuArr.push(childMenu);) e naturalmente chiamo addChild per posizionarlo sullo stage.

La funzione closeAllButMe() è leggerissimamente più complessa:

private function closeAllButMe(me:XMLmenu):void {
for each (var sub:XMLmenu in childMenuArr) {
if (sub!=me) {
var subsub:XMLmenu=null;
while ((subsub=sub.childMenuArr.pop())!=null) {
subsub.kill();
}
}
}
}

Innanzitutto ricordiamo che questa funzione viene chiamata da un padre che deve chiudere tutti i menu suoi figli menu uno (cioè il nodo passato come argomento). A questo scopo si scorre l’array dei menu figli e, tranne per il sottomenu che si deve salvare, si cicla sui loro figli estrandoli (metodo pop() dell’oggetto Array. mai sentito parlare di stack?) e chiamando il loro metodo kill():

private function kill():void {
var sub:XMLmenu=null;
while ((sub=childMenuArr.pop())!=null) {
sub.kill();
}
button.addEventListener("morto",morto);
button.muoio();
}

questo metodo viene chiamato ricorsivamente su tutti i sottomenu del menu in questione registrando la funzione morto() per quando l’oggetto button:gxButton emetterà l’evento “morto“, infine chiamo il metodo muoio() del bottone che è poi quello che appunto genererà l’evento.

bhè ora mi sono scocciato di scrivere e devo scendere per andare a un festino.
continuerò domani/bho