Silverlight 1.0 - Downloader
Förord
Så var man här igen. Här innebär på väg till jobbet med en laptop i knät skrivandes en Silverlight artikel. Det saknades en del i mina två tidigare artiklar om Silverlight 1.0 om man vill ha en komplett översikt av. Jag kommer inte kunna ge en komplett bild, men lite mer skall jag väl kunna ge. Bland annat har jag inte pratat så mycket om ett av de kanske mest användbara objekten tillgängliga, Downloadern. Jag har snuddat vid den i föregående artikel, men lite mer information än så krävs för att förstå dess förträfflighet. Det här blir sannolikt den sista artikeln om Silverlight 1.0 från mig. 2.0 har nått beta stadie och dessutom växt mycket mer från alfa till beta än jag ens vågat hoppas på. Det är dags att gå vidare, men trots det säger jag absolut inte att ni bör strunta i 1.0. Det är en bra plattform som möjliggör frigörandet av en del av ens kreative idéer. Dessutom är det man lär sig genom att arbeta med 1.0 ofta applicerbart på 2.0.Innehåll
»»
»
»
Downloadern
Downloadern är ett mycket enkelt, men ändå kraftfullt objekt. Det används till att skicka http-requests till servern från applikationen. Eftersom applikationen exekvererar på klientsidan behövs det ofta ett sätt att tanka ner information från servern, vilket downloadern möjliggör. Ett scenario kan nedladdning av en större applikation medan användaren för se en progressbar. Detta är relativt enkelt gjort. Först skapar man en enkel xaml-fil som kommer användas medan den större delen laddas ner. Denna enkla "downloader" applkation använder sedan downloadern för att ladda ner alla de resurser man behöver i form av xaml, bilder, texter, fonter mm. Därefter tömmer man root canvasen på dess children och skapar upp ett innehåll med xaml från downloadern. Detta kommer vara scenariot för min artikel.
Paketerade resurser
Downloadern har en ganska finurig implementation som gör att man kan paketera sina resurser i en zip-fil och tanka ner en stor fil. Alternativet skulle vara att skriva kod för att ladda ner en fil i taget, vilket inte känns så lockande i de flesta fall. Därefter kan man med hjälp av downloadern extrahera de resurser man behöver. Downloadern är dessuom intressant på det vis att den inte returnerar något fil-objekt utan själv tar skepnaden av den fil man laddat ner. Den "kapslar in" filen. Man gör därför all "fil-access" genom sin downloader. Man kan självklart skapa flera downloader objekt om man skulle behöva det.
Begränsningar
Av säkerhetsmässiga skäl får man med downloader enbart hämta filer från den egna domänen. Detta kan absolut vara ett problem och en irriterande hake, men den går att ta sig runt med lite smidig serverside kod. Det är inte direkt svårt att skapa en ashx eller aspx på serversidan som tar emot en path till en fil, tankar ner denna till servern och sedan vidarebefodrar den till klienten. Det är självklart ingen optimal lösning, men den kan lösa en del situationer som kan uppstå. Dock bör filen inte vara för stor eftersom den först skall tankas till servern och sedan till klienten, vilket kan ta lite tid om den är stor. Downloadern jobbar dock asynkront, vilket gör att det inte är ett problem om nedladdningen drar ut på tiden. Men glöm inte att visa en progressbar så att användaren vet att något händer.
Så till koden
Scenariot är, som jag skrev tidigare, att det behöver skapas en splash-sida som ger användaren nedladdningsinformation medan de resurser som behövs laddas ner. Applikationen kommer se ut som skräp, men i ett verkligt scenario bör man ju lägga lite energi på att designa splashen med tanke på att den kan synas under en stund medan man tankar ner informationen man behöver.Men innan vi sätter igång måste vi skapa ett "resurs paket", dvs en zip med de filer vi behöver. I zippen behövs det 4 filer för mitt den här demon:
- en bild (image.jpg)
- en True Type font fil (font.ttf)
- en xaml-fil (demo.xaml)
- en text fil innehållande texten "Hello World" (text.txt).
Dessutom bör man slänga i en STOR fil i zippen. Anledningen till detta är att applikationen kommer köra lokalt, vilket gör att nedtankningen kommer gå väldigt snabbt. Med en "bloat" fil så blir zippen förhoppningsvis så stor att man hinner se progressbaren innan den är nedladdad. Demo.xaml filen bör se ut som följer:
Scene.xaml
Efter zippen är skapad drar vi igång en ny Silverlight web i VS 2008. I VS 2008 har de löst en bugg/feature som man hade i VS 2005. I 2005:an skapades projektet som en html applikation, dvs den laddades från filsystemet när man startade den. Problemet med det är att downloadern inte riktigt vill vara med och leka eftersom den vill tanka ner filen över "nätverket". (Det går att lösa detta också, men då blir det olika kod på utvecklingsmaskinen och på produktionsmaskinen vilket känns jobbigt.) Använder man 2005 så måste man därför även skapa en webapplikation och lägga till en "Silverlight link" i projektet. Men använder man 2008 så löser VS det själv.
Töm allt innehåll i rot canvasen i Scene.xaml. Plocka även bort alla eventhandlers från SilverlightDownloader.Scene.prototype i Scene.js. Ta även bort utom "this.plugIn=..." i prototypens handleLoad metod.
Scene.xaml
Scene.js
SilverlightDownloader.Scene.prototype =
{
handleLoad: function(plugIn, userContext, rootElement)
{
this.plugIn = plugIn;
}
}
Lägg till en TextBlock kontroll mitt i canvasen för att visa hur långt nedladdningen kommit. Kontrollen behöver dessutom ett namn för att vi ska kunna uppdatera innehållet från kod.
Scene.xaml
Vi behöver även en referens till TextBlock kontrollen för att kunna uppdatera texten. Så uppdatera handleLoad metoden enligt följande
Scene.js
handleLoad: function(plugIn, userContext, rootElement)
{
this.plugIn = plugIn;
this.progressTextBlock = rootElement.findName("tbProgress");
}
För att kunna tömma innehållet i i rot canvasen måste vi hålla en referens till denna, så spara undan rootElement i handleLoad metoden.
Scene.js
handleLoad: function(plugIn, userContext, rootElement)
{
this.plugIn = plugIn;
this.progressTextBlock = rootElement.findName("tbProgress");
this.root = rootElement;
}
Sen är det dags att skapa en downloader och börja jobba. Ett downloader objekt skapar med hjälp av createControl() metoden på pluginen. Man behöver spara undan en referens till downloadern om vi skall kunna använda den i andra delar av applikationen, men i vårt fall behöver vi inte detta. Uppdatera handleLoad funktionen med följande kod
Scene.js
handleLoad: function(plugIn, userContext, rootElement)
{
this.plugIn = plugIn;
this.progressTextBlock = rootElement.findName("tbProgress");
var downloader = plugIn.createObject("downloader")
}
Därefter måste vi "ställa in" downloadern. Först och främst måste vi ange vad som skall hämtas och hur. Det gör vi genom att anropa open() metoden, som tar 2 parametrar. Vilket http-verb som skall användas samt namnet på filen som ska laddas ner.
Dessutom finns det ett antal intressanta event som vi vill lyssna på. Först och främst "måste" vi lyssna på Completed. All nedladdning är asynkron och Completed eventet är självklart intressant för att kunna jobba med det som nedladdats ner när nedladdningen är klar. Men även DownloadFailed och DownloadProgressChanged eventen är användbara.
DownloadFailed är lämpligt för debugging och felhantering. Dock får man i princip alltid ett genereskt AG_E_NETWORK_ERROR fel vilket inte ger så mycket i debug syfte. DownloadProgressChanged är intressant för vår progressbar.
I DownloadProgressChanged eventet kan vi använda downloaderns downloadProgress property. Downloadern är tillgänglig till oss genom sender parametern som i alla eventhandlers i .NET. Man bör dock komma ihåg att downloadProgress är en double med ett värde mellan 0 och 1. Behöver vi det i procent får vi multiplicera med 100. Till sist måste vi initiera själva nedladdningen med send() metoden. Nedan har ni koden, som bör vara ganska enkel att följa om ni läst tidigare artiklar.
Scene.js
SilverlightDownloader.Scene.prototype =
{
handleLoad: function(plugIn, userContext, rootElement)
{
this.plugIn = plugIn;
this.progressTextBlock = rootElement.findName("tbProgress");
var downloader = plugIn.createObject("downloader")
downloader.addEventListener("Completed", Silverlight.createDelegate(this, this.downloadComplete));
downloader.addEventListener("DownloadFailed", Silverlight.createDelegate(this, this.downloadFailed));
downloader.addEventListener("DownloadProgressChanged", Silverlight.createDelegate(this, this.downloadProgressChanged));
downloader.Open("GET","Resources.zip");
downloader.Send();
},
downloadComplete: function(sender, eventArgs)
{
},
downloadFailed: function(sender, eventArgs)
{
this.progressTextBlock["Text"] = "Error: "+eventArgs.ErrorMessage;
},
downloadProgressChanged: function(sender, eventArgs)
{
this.progressTextBlock["Text"] = "Progress: "+(sender.downloadProgress*100);
}
}
Men till sist måste vi göra något med filen vi laddat ned. Det blir tre olika delar. Vi börjar med Xaml-filen. Genom vår plug-in har vi tillgång till createFromXaml() som jag visat tidigare, men vi har också tillgång till createFromXamlDownloader() som kan skapa Silverlight objekt från en downloaders nerladdade idata. Den klarar till och med av att läsa ut en xaml fil från en zip fil. Det enda vi behöver göra är alltså att skapa ett element med hjälp av createFromXamlDownloader(), tömma rot-elementets child collection och lägga till vårt nyskapa element. Typ:
Scene.js
downloadComplete: function(sender, eventArgs)
{
var el = this.plugIn.content.createFromXamlDownloader(sender,"demo.xaml");
this.rootElement.children.clear();
this.rootElement.children.add(el);
}
Testar man att köra applikationen nu så inser man att nånting händer, progress informationen försvinner, men det är allt. Image elementet i xaml-filen har inget att visa och syns därför inte. Vi får tag i Image elementet genom det nyskapade elmentets child collection. Därefter använder vi en metod som heter setSource. Det är en metod som gör att man kan sätta en downloader som source för bilden. Vi skickar dessutom med en parameter som säger vilken fil i vår zip som skall användas.
Scene.js
downloadComplete: function(sender, eventArgs)
{
var el = this.plugIn.content.createFromXamlDownloader(sender,"demo.xaml");
el.children.getItem(0).setSource(sender,"image.jpg");
this.rootElement.children.clear();
this.rootElement.children.add(el);
}
Nu kör applikationen och bilden visas. Men jag sa ju att det skulle finnas en true type font fil i filen också. Den ska vi självklart använda. Börja med att ta fram TextBlock elementet med hjälp av child collectionen. Använd sedan setFontSource för att berätta för TextBlocket att det kan leta efter fonter i vår zip. Vi anger dock inte vilken ttf fil som skall användas, utan den använder alla tillgängliga. Därför måste vi sätta FontFamily egenskapen också.
Sist men inte minst vill jag dessutom sätta Text egenskapen på TextBlocket till innehållet i vår text fil. Det kan vi göra med hjälp av downloaderns metod getResponseText(), som hämtar ut texten i en fil inuti en zip. Enligt följande:
Scene.js
downloadComplete: function(sender, eventArgs)
{
var el = this.plugIn.content.createFromXamlDownloader(sender,"demo.xaml");
el.children.getItem(0).setSource(sender,"image.jpg");
el.children.getItem(1).setFontSource(sender);
el.children.getItem(1)["FontFamily"] = "Silkscreen";
el.children.getItem(1)["Text"] = sender.getResponseText("text.txt");
this.rootElement.children.clear();
this.rootElement.children.add(el);
}
Applikationen fungerar nu. Den kommer visa en enkel "progress" indikation (om än väldigt snabbt). Under tiden laddas resurs zippen ner. Applikationen packar upp zippens innehåll och sätter de egenskaper som behövs. Som synes är downloadern ett flexibelt och användbart objekt. Mitt exempel är kanske inte helt uttömmande på ämnet, men ger ett smakprov på vad man kan göra. Jag har försökt få med de olika varienterna på att hämta värden och sätta egenskaper med downloadern, men jag vet inte om jag fått med alla. Den fullständiga koden syns nedan:
Scene.xaml
Scene.js
if (!window.SilverlightDownloader)
window.SilverlightDownloader = {};
SilverlightDownloader.Scene = function()
{
}
SilverlightDownloader.Scene.prototype =
{
handleLoad: function(plugIn, userContext, rootElement)
{
this.plugIn = plugIn;
this.rootElement = rootElement;
this.progressTextBlock = rootElement.findName("tbProgress");
var downloader = plugIn.createObject("downloader")
downloader.addEventListener("Completed", Silverlight.createDelegate(this, this.downloadComplete));
downloader.addEventListener("DownloadFailed", Silverlight.createDelegate(this, this.downloadFailed));
downloader.addEventListener("DownloadProgressChanged", Silverlight.createDelegate(this, this.downloadProgressChanged));
downloader.Open("GET","resources.zip");
downloader.Send();
},
downloadComplete: function(sender, eventArgs)
{
var el = this.plugIn.content.createFromXamlDownloader(sender,"demo.xaml");
el.children.getItem(0).setSource(sender,"image.jpg");
el.children.getItem(1).setFontSource(sender);
el.children.getItem(1)["FontFamily"] = "Silkscreen";
el.children.getItem(1)["Text"] = sender.getResponseText("text.txt");
this.rootElement.children.clear();
this.rootElement.children.add(el);
},
downloadFailed: function(sender, eventArgs)
{
this.progressTextBlock["Text"] = "Error: "+eventArgs.ErrorMessage;
},
downloadProgressChanged: function(sender, eventArgs)
{
this.progressTextBlock["Text"] = "Progress: "+(sender.downloadProgress*100);
}
}
Text.txt
Hello World
demo.xaml
0 Kommentarer