Att skapa kontroller i Silverlight 1.0
Förord
Så sitter man här igen, framför datorn med mål att skriva något vettigt om Silverlight. Ja, vad som är en vettig uppföljare till min föregående artikel är självklart baserat på min personliga åsikt. Jag har valt att skriva om kontroller.Innehåll
Relaterade artiklar
» Introduktion till Silverlight 1.0Silverlight 1.0 levereras helt utan ett kontrollbibliotek, vilket kan uppfattas som en begränsning. Personligen tycker jag det är kul eftersom vi får ser fler kreativa applikationer på det viset. Jag har som synes även bestämt mig för att fortsätta skriva om Silverlight 1.0 trots att Beta 1 på Silverlight 2.0 släpptes i början av mars på Mix '08. Releasen innehöll mycket intressant och användbart, men jag väljer ändå att stanna på 1.0 ett tag framöver. Bland annat för att det är en färdig produkt, men också för att visa att den är användbar, även om 2.0 är smidigare och kompetentare på vissa områden.
Jag är van att hålla kurser i .NET och har insett att det är väldigt mycket lättare att få folk som jobbat i klassisk ASP att inse det fenomenala i ASP.NET än folk som aldrig jobbat med "gammaldags" webbutveckling. Jag tror att det är samma sak med Silverlight 1.0 och 2.0. Lär man sig först 1.0 och förstår principerna där, så kommer 2.0 vara lättare att förstå och lättare att använda när man vet vad som händer under skalet. Dessutom kommer man inte helt ifrån JavaScriptandet ens i 2.0.
Så till pudelns kärna. Jag har aldrig förstått det uttrycket, men jag tror det passar in här. Att försöka bygga en Silverlight applikation utan att skapa kontroller kommer orsaka massor av jobbiga situationer. Det är självklart möjligt, men det är betydligt mycket lättare att göra det när man kapslar in delar av applikationen i kontroller. Det kan vara så enkelt som att man vill återanvända utseendet och funktionen på en knapp genom hela applikationen, men det kan också vara en avancerad kontroll som gör mycket mer.
Tanken bakom en kontroll i Silverlight 1.0 är inte speciellt annorlunda från en vanlig "sida", dvs xaml-kod och ett javascript. Den stora frågan är bara hur man förpckar detta till en användbar enhet. Det lättaste är att baka in xaml-koden i javascriptet, men det lider av samma problem som webcontrols gör. Webcontrols, som de flesta som har testat skapa dem vet, kompilerar in layouten i koden och är därför svår att göra layout ändringar på. Det blir samma sak om vi bakar in xaml i javascriptet, dvs man kommer inte kunna visualisera kontrollen, i t ex Expression Blend, och därmed få det svårare att göra ändringar i layouten. Alternativen är ett flertal, men vi börjar med den modellen trots att den kanske inte är optimal i många fall.
Första steget är att skapa ett nytt "namespace". Det kommer att underlätta om man kombinerar flera Silverlight applikationer eller kontroller på samma sida. Det går att göra med hjälp av ASP.NET AJAXs javascript bibliotek, men det finns två anledningar till att inte använda dem till just detta. För det första ger de oss ett bibliotek på 200-nånting kilobyte och är dessutom ännu en sak att lära sig. Vi ska ju hålla det här relativt enkelt. Jag vill dock inte säga att de inte är användbara, men just nu lämnar vi dem utanför. Skapandet av ett namespace är inte så svårt och görs lättast som följer.
Därefter behöver vi en ny funktion och en ny prototyp, dvs en ny klass för att hantera kontrollens funktionalitet. Detta är oberoende av hur vi tänkt skapa xaml delen av kontrollen. Anledningen till att vi skapar en prototyp är att vi vill att alla kontroller skall dela på funktionaliteten. Man kan skapa allt i en funktion, men då delar inte kontrollerna funktionalitet, utan har en egen uppsättning av allt inkl. fuktions-definitioner. Se prototypen som en klassdeklaration. En enkel definition skulle kunna se ut som följande.
Inuti prototypen kan vi skapa metoder som vår klass skall implementera. Bland annat måste vi skapa en konstruktor. En kontroll är svår att använda utan en sådan. Jag brukar skapa en initialize metod i min prototyp som jag använder från funktionen. Anledning att jag gör detta är att metoden måste befinna sig i prototypen. Om den inte gör det kommer "this" att peka på fel objekt. Så för att komma åt "klassens" egenskaper och funktioner måste vi lägga initialiserings koden i prototypen. Den kan dock anropas från funktionen.
Men för att få ett fungerande objekt måste vi ju ha lite xaml kod, så det lägger vi till. Initialt väljer jag att baka in xaml-kloden i JavaScript-koden, vilket kommer ändras på slutet. För enkelhetens skulle skapar vi en enkel rektangel och ger den knappfunktionalitet. Men självklart kan vi göra precis hur avancerad xaml-kod som helst.
Därefter måste ju initialiseringskoden faktiskt använda xaml-koden. Men för att kunna göra det måste vi ha en referens till pluginen. Det skulle vi kunna få med hjälp av document.getElementById, men det skulle innebära att vi aldrig fick döpa html-elementet som innehöll pluginen. Alltså ingen bra lösning. Alternativt skulle vi kunna låta kontrollen som skapar objektet skicka in en referens. Eller kanske lägga en referens i en global variabel. Ja, alternativen till att få tag i en referens är många, men jag väljer en som initialt kanske kan se lite udda ut. Det kommer en förklaring senare. Dock är även förklaringen kanske lite udda i sitt sätt att fungera, men den underlättar. Men för att inte komma för långt framåt i tankegångarna så kan vi bara konstatera att det hela börjar med att man skickar in kontrollens tilltänkta förälder i konstruktorn. För att kunna göra det så måste vi lägga till den parametern både i funktionen och initialize enligt följande.
Nästan alla kontroller i Silverlight ärver från en basklass som ger oss tillgång till metoden getHost() som ger oss den plugin, eller host, som vår kontroll befinner sig i. Det kan vi utnyttja här. Genom den metoden kan vi få en referens till pluginen och därigenom tillgång till en metod som heter createFromXAML som skapar ett Silverlight objekt utifrån t ex en xaml-sträng. Pluginen har en egenskap som heter content som vi använder för att få tillgång till pluginens content relaterade egenskaper, dvs allt inuti kontrollen och metoder relaterade till innehåll. Dessutom vill vi ju spara undan en referens till det nyskapade objektet, vilket vi gör i en variabel som jag döper till rootControl i detta fall. Root kommer ifrån att min xaml ju faktiskt kan innehålla mer en en kontroll. Den kommer alltid innehålla en rot-kontroll, tex en Canvas, vilket är den kotroll jag får referens till.
Parameter nummer två, true, är kopplat till till något som heter namescope. Om xaml-koden innehåller ett x:Name, dvs ett namn på kontrollen, så riskerar man ju att få flera kontroller i kontrollträdet med samma namn om man skapar upp flera kontroller. Detta är inte tillåtet. Därför kan man skicka in true som andra parameter och säga till createFromXAML att lägga namnet i ett eget scope. Detta gör att det inte spelar någon roll vilket namn man har skrivit i xaml-koden, det kommer ändå läggas i ett annat scope. Detta gör tyvärr att vi inte kan söka fram vårt element per namn, men det gör inte så mycket när vi jobbar med kontroller. Dock gör det att vi måste hålla en referens till roten av det vi skapar.
I initialize kan vi dessutom passa på att leta fram andra kontroller som deklarerats inne i roten i xaml-koden. Detta görs lättast med this.rootControl.findName() eller this.rootControl.children.getItem(). Dessutom kan vi lägga till eventhandlers i detta skede. Vad vi vill ha är eventhandlers som hanterar MouseOver, MouseLeave, MouseButtonLeftDown och MouseLeftButtonUp. När vi jobbar med eventhandlers i Silverlight använder vi en metod som heter addEventListener som tar två parametrar, namnet på det event vi vill hantera och en referens till metoden som skall hantera det. Referensen till den metod som skall anropas, måste skapas upp med hjälp av en hjälpmetod som skapas per automatik i ett Silverlight-projekt i VS, Silverlight.createDelegate. Den metoden i sin tur tar in ett "kontext" som skall användas när metoden anropas, och en metod som skall anropas. Kontextet är det här objektet och metoden är en metod vi skapat på objektet. Handlern ser ut precis som i .NET dvs "delegtate void EventHandler(object sender, EventArgs e)".
Nu har vi en fullt fungerande kontroll. Om vi i vår OnLoad handler för applikationen nu väljer att skapa en kontroll så fungerar det alldeles utmärkt, men antingen så måste vi använda ett lite ointuitivt sätt att lägga till den i children kollektionen eller så får vi göra lite ändringar i koden.
Nuvarande sätt skulle vara som följer:
Tyvärr blir det som sagt var lite skumt att behöva använda kontrollens control egenskap för att lägga till den. Dessutom kan vi inte sätta någon placering utan att använda liknande syntax. Detta kan lösas på flera sätt. Personligen så brukar jag låta kontrollen lägga till sig själv i sin parents children kollektion. Kanske inte helt korrekt, men det gör skapandet av kontrollen väldigt enkel. Dessutom brukar jag bifoga placering i konstruktorn. Dessutom lägger jag normalt till accessors för de egenskaper som skall användas. I mångt och mycket eftersträvar jag en kontroll som inte kräver att man använder rootControl egenskapen. Dvs:
Jag är van att hålla kurser i .NET och har insett att det är väldigt mycket lättare att få folk som jobbat i klassisk ASP att inse det fenomenala i ASP.NET än folk som aldrig jobbat med "gammaldags" webbutveckling. Jag tror att det är samma sak med Silverlight 1.0 och 2.0. Lär man sig först 1.0 och förstår principerna där, så kommer 2.0 vara lättare att förstå och lättare att använda när man vet vad som händer under skalet. Dessutom kommer man inte helt ifrån JavaScriptandet ens i 2.0.
Så till pudelns kärna. Jag har aldrig förstått det uttrycket, men jag tror det passar in här. Att försöka bygga en Silverlight applikation utan att skapa kontroller kommer orsaka massor av jobbiga situationer. Det är självklart möjligt, men det är betydligt mycket lättare att göra det när man kapslar in delar av applikationen i kontroller. Det kan vara så enkelt som att man vill återanvända utseendet och funktionen på en knapp genom hela applikationen, men det kan också vara en avancerad kontroll som gör mycket mer.
Tanken bakom en kontroll i Silverlight 1.0 är inte speciellt annorlunda från en vanlig "sida", dvs xaml-kod och ett javascript. Den stora frågan är bara hur man förpckar detta till en användbar enhet. Det lättaste är att baka in xaml-koden i javascriptet, men det lider av samma problem som webcontrols gör. Webcontrols, som de flesta som har testat skapa dem vet, kompilerar in layouten i koden och är därför svår att göra layout ändringar på. Det blir samma sak om vi bakar in xaml i javascriptet, dvs man kommer inte kunna visualisera kontrollen, i t ex Expression Blend, och därmed få det svårare att göra ändringar i layouten. Alternativen är ett flertal, men vi börjar med den modellen trots att den kanske inte är optimal i många fall.
Första steget är att skapa ett nytt "namespace". Det kommer att underlätta om man kombinerar flera Silverlight applikationer eller kontroller på samma sida. Det går att göra med hjälp av ASP.NET AJAXs javascript bibliotek, men det finns två anledningar till att inte använda dem till just detta. För det första ger de oss ett bibliotek på 200-nånting kilobyte och är dessutom ännu en sak att lära sig. Vi ska ju hålla det här relativt enkelt. Jag vill dock inte säga att de inte är användbara, men just nu lämnar vi dem utanför. Skapandet av ett namespace är inte så svårt och görs lättast som följer.
if (!window.myNewNamespace)
window.myNewNamespace = {};
Därefter behöver vi en ny funktion och en ny prototyp, dvs en ny klass för att hantera kontrollens funktionalitet. Detta är oberoende av hur vi tänkt skapa xaml delen av kontrollen. Anledningen till att vi skapar en prototyp är att vi vill att alla kontroller skall dela på funktionaliteten. Man kan skapa allt i en funktion, men då delar inte kontrollerna funktionalitet, utan har en egen uppsättning av allt inkl. fuktions-definitioner. Se prototypen som en klassdeklaration. En enkel definition skulle kunna se ut som följande.
myNewNamspace.myControl = new function()
{
}
myNewNamespace.myControl.prototype = {
}
Inuti prototypen kan vi skapa metoder som vår klass skall implementera. Bland annat måste vi skapa en konstruktor. En kontroll är svår att använda utan en sådan. Jag brukar skapa en initialize metod i min prototyp som jag använder från funktionen. Anledning att jag gör detta är att metoden måste befinna sig i prototypen. Om den inte gör det kommer "this" att peka på fel objekt. Så för att komma åt "klassens" egenskaper och funktioner måste vi lägga initialiserings koden i prototypen. Den kan dock anropas från funktionen.
myNewNamspace.myControl = new function()
{
this.initialize();
}
myNewNamespace.myControl.prototype = {
initialize: function()
{
}
}
Men för att få ett fungerande objekt måste vi ju ha lite xaml kod, så det lägger vi till. Initialt väljer jag att baka in xaml-kloden i JavaScript-koden, vilket kommer ändras på slutet. För enkelhetens skulle skapar vi en enkel rektangel och ger den knappfunktionalitet. Men självklart kan vi göra precis hur avancerad xaml-kod som helst.
myNewNamespace.myControl.prototype = {
xaml: " ",
initialize: function()
{
}
}
Därefter måste ju initialiseringskoden faktiskt använda xaml-koden. Men för att kunna göra det måste vi ha en referens till pluginen. Det skulle vi kunna få med hjälp av document.getElementById, men det skulle innebära att vi aldrig fick döpa html-elementet som innehöll pluginen. Alltså ingen bra lösning. Alternativt skulle vi kunna låta kontrollen som skapar objektet skicka in en referens. Eller kanske lägga en referens i en global variabel. Ja, alternativen till att få tag i en referens är många, men jag väljer en som initialt kanske kan se lite udda ut. Det kommer en förklaring senare. Dock är även förklaringen kanske lite udda i sitt sätt att fungera, men den underlättar. Men för att inte komma för långt framåt i tankegångarna så kan vi bara konstatera att det hela börjar med att man skickar in kontrollens tilltänkta förälder i konstruktorn. För att kunna göra det så måste vi lägga till den parametern både i funktionen och initialize enligt följande.
myNewNamspace.myControl = new function(parent)
{
this.initialize(parent);
}
myNewNamespace.myControl.prototype = {
xaml: " ",
initialize: function(parent)
{
}
}
Nästan alla kontroller i Silverlight ärver från en basklass som ger oss tillgång till metoden getHost() som ger oss den plugin, eller host, som vår kontroll befinner sig i. Det kan vi utnyttja här. Genom den metoden kan vi få en referens till pluginen och därigenom tillgång till en metod som heter createFromXAML som skapar ett Silverlight objekt utifrån t ex en xaml-sträng. Pluginen har en egenskap som heter content som vi använder för att få tillgång till pluginens content relaterade egenskaper, dvs allt inuti kontrollen och metoder relaterade till innehåll. Dessutom vill vi ju spara undan en referens till det nyskapade objektet, vilket vi gör i en variabel som jag döper till rootControl i detta fall. Root kommer ifrån att min xaml ju faktiskt kan innehålla mer en en kontroll. Den kommer alltid innehålla en rot-kontroll, tex en Canvas, vilket är den kotroll jag får referens till.
myNewNamespace.myControl.prototype = {
xaml: " ",
initialize: function(parent)
{
this.rootControl = parent.getHost().content.createFromXAML(this.xaml,true);
}
}
Parameter nummer två, true, är kopplat till till något som heter namescope. Om xaml-koden innehåller ett x:Name, dvs ett namn på kontrollen, så riskerar man ju att få flera kontroller i kontrollträdet med samma namn om man skapar upp flera kontroller. Detta är inte tillåtet. Därför kan man skicka in true som andra parameter och säga till createFromXAML att lägga namnet i ett eget scope. Detta gör att det inte spelar någon roll vilket namn man har skrivit i xaml-koden, det kommer ändå läggas i ett annat scope. Detta gör tyvärr att vi inte kan söka fram vårt element per namn, men det gör inte så mycket när vi jobbar med kontroller. Dock gör det att vi måste hålla en referens till roten av det vi skapar.
I initialize kan vi dessutom passa på att leta fram andra kontroller som deklarerats inne i roten i xaml-koden. Detta görs lättast med this.rootControl.findName() eller this.rootControl.children.getItem(). Dessutom kan vi lägga till eventhandlers i detta skede. Vad vi vill ha är eventhandlers som hanterar MouseOver, MouseLeave, MouseButtonLeftDown och MouseLeftButtonUp. När vi jobbar med eventhandlers i Silverlight använder vi en metod som heter addEventListener som tar två parametrar, namnet på det event vi vill hantera och en referens till metoden som skall hantera det. Referensen till den metod som skall anropas, måste skapas upp med hjälp av en hjälpmetod som skapas per automatik i ett Silverlight-projekt i VS, Silverlight.createDelegate. Den metoden i sin tur tar in ett "kontext" som skall användas när metoden anropas, och en metod som skall anropas. Kontextet är det här objektet och metoden är en metod vi skapat på objektet. Handlern ser ut precis som i .NET dvs "delegtate void EventHandler(object sender, EventArgs e)".
myNewNamespace.myControl.prototype = {
xaml: " ",
initialize: function(parent)
{
this.rootControl = parent.getHost().content.createFromXAML(this.xaml,true);
this.rootControl.addEventListener("MouseEnter",Silverlight.createDelegate(this,this.mouseEnterHandler));
this.rootControl.addEventListener("MouseLeave",Silverlight.createDelegate(this,this.mouseLeaveHandler));
this.rootControl.addEventListener("MouseButtonLeftDown",Silverlight.createDelegate(this,this.mouseLeftButtonDownHandler));
this.rootControl.addEventListener("MouseLeftButtonUp",Silverlight.createDelegate(this,this.mouseLeftButtonUpHandler));
},
mouseEnterHandler: function(sender,e)
{
this.rootControl["Fill"] = "#ff336699";
},
mouseLeaveHandler: function(sender,e)
{
this.rootControl["Fill"] = "Grey";
},
mouseLeftButtonDownHandler: function(sender,e)
{
this.rootControl["Fill"] = "Black";
},
mouseLeftButtonUpHandler: function(sender,e)
{
this.rootControl["Fill"] = "#ff336699";
alert("You clicked the button!");
}
}
Nu har vi en fullt fungerande kontroll. Om vi i vår OnLoad handler för applikationen nu väljer att skapa en kontroll så fungerar det alldeles utmärkt, men antingen så måste vi använda ett lite ointuitivt sätt att lägga till den i children kollektionen eller så får vi göra lite ändringar i koden.
Nuvarande sätt skulle vara som följer:
myNewNamespace.Scene.prototype =
{
handleLoad: function(plugIn, userContext, rootElement)
{
this.plugIn = plugIn;
this.root = rootElement;
var myCtrll = myNewNamespace.myControl(this.root);
this.root.children.add(myCtrl.rootControl);
}
}
Tyvärr blir det som sagt var lite skumt att behöva använda kontrollens control egenskap för att lägga till den. Dessutom kan vi inte sätta någon placering utan att använda liknande syntax. Detta kan lösas på flera sätt. Personligen så brukar jag låta kontrollen lägga till sig själv i sin parents children kollektion. Kanske inte helt korrekt, men det gör skapandet av kontrollen väldigt enkel. Dessutom brukar jag bifoga placering i konstruktorn. Dessutom lägger jag normalt till accessors för de egenskaper som skall användas. I mångt och mycket eftersträvar jag en kontroll som inte kräver att man använder rootControl egenskapen. Dvs:
myNewNamspace.myControl = new function(parent,x,y)
{
this.initialize(parent,x,y);
}
myNewNamespace.myControl.prototype = {
xaml: " ",
initialize: function(parent,x,y)
{
this.rootControl = parent.getHost().content.createFromXAML(this.xaml,true);
this.rootControl["Canvas.Top"] = y;
this.rootControl["Canvas.Left"] = x;
parent.children.add(this.rootControl);
this.rootControl.addEventListener("MouseEnter",Silverlight.createDelegate(this,this.mouseEnterHandler));
this.rootControl.addEventListener("MouseLeave",Silverlight.createDelegate(this,this.mouseLeaveHandler));
this.rootControl.addEventListener("MouseButtonLeftDown",Silverlight.createDelegate(this,this.mouseLeftButtonDownHandler));
this.rootControl.addEventListener("MouseLeftButtonUp",Silverlight.createDelegate(this,this.mouseLeftButtonUpHandler));
},
...
}
Vilket gör att det från den skapande metoden bara är att anropa konstruktorn så är allt klart. Typ:
myNewNamespace.Scene.prototype =
{
handleLoad: function(plugIn, userContext, rootElement)
{
this.plugIn = plugIn;
this.root = rootElement;
var myCtrll = myNewNamespace.myControl(this.root,100,100);
myCtrl.set_Left(30);
var left = myCtrl.get_Left();
}
}
Som ni ser så går det att göra ganska bra kontroller även med JavaScript. Självklart blir det både lättare och snyggare i 2.0, men det är inte så illa som alla påstår i 1.0. Dock leder det ju till att det bli väldigt många JavaScript referenser på de sidor som använder kontrollerna. Standard är en js fil per kontroll och sedan en extra för själva applikationen.
Men som jag sa kan man lägga xaml koden på andra platser, så det får jag väl visa också. Eftersom Silverlight är en klientsides applikation så innebär det att så fort vi väljer att inte ha det vi behöver i de JavaScript som inkluderas eller i den xaml fil som utgör applikationens rot så måste vi hämta det på något vis. Ett par tänkbara alternativ för att hämta xaml kod skulle vara en webservice, en websida som returnerar en xaml sträng (typ REST), en xaml fil på servern eller en zip-fil. Silverlight stödjer att kompilera resurser i en zip-fil för att sedan kunna tanka ner dem och packa upp dem lokalt. Hur vi än gör så kommer vi nästan alltid till att behöva ett sätt att hämta data från servern. I fallet webservice får vi förlita oss på AJAX, men i de andra scenariona finns det ett objekt som kallas Downloader. Precis som namnet påvisar så används kontrollen för att ladda ner saker från servern. Tyvärr är vi begränsade till filer från den egna domänen. Cross domain anrop blir möjliga i 2.0, men med vissa restriktioner. För oss fungerar det utmärkt utan cross domain. Vi börjar med det enklaste scenariot, att hämta en xaml-fil. Det skulle lika gärna kunna vara en txt- eller aspx-fil, men nu är det en xaml-fil som gäller.
Det första vi måste göra är att plocka ut den xaml som vi bakade in i prototypen för kontrollen och lägga den i en xaml-fil på servern. Vi döper filen till myCtrl.xaml.
Obs! Om du jobbar med Downloadern måste du anropa applikationen via en webserver och inte över filsystemet.
Downloadern är ett ganska enkelt objekt. Man definierar vad som skall hämtas med open() metoden, skapar eventhandlers som lyssnar på Completed och DownloadFailed eventen och sedan anropar man send(). Mycket svårare än så är det inte. Completed eventet kommer skicka med downloadern som sender, så vi kan använda den direkt. Downloadern har en egenskap som heter "responseText" som returnerar responsen från servern i form av en sträng, vilket funkar utmärkt för vårt exempel.
Först måste vi förändra vår kontroll till att ta emot xaml-koden i konstruktorn och sedan kan vi sätta igång.
myNewNamspace.myControl = new function(parent,xaml,x,y)
{
this.initialize(parent,xaml,x,y);
}
myNewNamespace.myControl.prototype = {
initialize: function(parent,xaml,x,y)
{
this.rootControl = parent.getHost().content.createFromXAML(xaml,true);
this.rootControl["Canvas.Top"] = y;
this.rootControl["Canvas.Left"] = x;
parent.children.add(this.rootControl);
this.rootControl.addEventListener("MouseEnter",Silverlight.createDelegate(this,this.mouseEnterHandler));
this.rootControl.addEventListener("MouseLeave",Silverlight.createDelegate(this,this.mouseLeaveHandler));
this.rootControl.addEventListener("MouseButtonLeftDown",Silverlight.createDelegate(this,this.mouseLeftButtonDownHandler));
this.rootControl.addEventListener("MouseLeftButtonUp",Silverlight.createDelegate(this,this.mouseLeftButtonUpHandler));
},
...
}
Hämtandet av filen gör vi för enkelhetens skull i applikationens load event. Det är dock inte tvunget, men enklast att visa.
myNewNamespace.Scene.prototype =
{
handleLoad: function(plugIn, userContext, rootElement)
{
this.plugIn = plugIn;
this.root = rootElement;
var downloader = plugIn.createObject("downloader
downloader.open("GET","myCtrl.xaml");
downloader.addEventListener("Completed", Silverlight.createDelegate(this, this.ResourceDownloadCompleted));
downloader.addEventListener("DownloadFailed", Silverlight.createDelegate(this, this.ResourceDownloadFailed));
downloader.send();");
},
ResourceDownloadFailed: function(sender, eventArgs)
{
alert("Download of resources failed.");
},
ResourceDownloadCompleted: function(sender, eventArgs)
{
var dl = this.plugIn.createObject("downloader");
var ctrl = new myNewNamspace.myControl(this.root,sender.responseText,100,100);
}
}
Om vi sedan vill använda en zip-fil för att förpacka alla våra xaml-filer till ett komprimerat objekt så blir det inte mycket svårare. Dock ska vi vara lite uppmärksamma på hur stor zippen blir om vi bakar in bilder och annat i den.
Det är fullt möjligt att baka in xaml, bilder, filmer, TrueType fonter med mera i zip-filen. Det går att baka in vad som helst, men det är de vanligaste typerna. Blir zippen stor bör man överväga att lägga in en liten progressbar så att användaren förstår vad som händer. För att göra detta enklare har Downloadern ett event som heter DonwloadProgressChanged och en egenskap som heter DownloadProgress. Vi kommer inte använda det här, men det är ett tips att titta på detta om zippen blir stor.
Så vi ändrar väl exemplet och packar vår myCtrl.xaml i en resources.zip och skriver om koden.
myNewNamespace.Scene.prototype =
{
handleLoad: function(plugIn, userContext, rootElement)
{
this.plugIn = plugIn;
this.root = rootElement;
var downloader = plugIn.createObject("downloader
downloader.open("GET","resources.zip");
downloader.addEventListener("Completed", Silverlight.createDelegate(this, this.ResourceDownloadCompleted));
downloader.addEventListener("DownloadFailed", Silverlight.createDelegate(this, this.ResourceDownloadFailed));
downloader.send();");
},
ResourceDownloadFailed: function(sender, eventArgs)
{
alert("Download of resources failed.");
},
ResourceDownloadCompleted: function(sender, eventArgs)
{
var dl = this.plugIn.createObject("downloader");
var xaml = dl.getResponseText("myCtrl.xaml");
var ctrl = new myNewNamspace.myControl(this.root,xaml,100,100);
}
}
Som ni ser så kapslar downloadern zippen och ger oss möjlighet att plocka ut xaml-filen ur den. Det kommer en separat artikel om downloadern vid tillfälle. Det är ett mycket användbart objekt som kan hjälpa oss att hämta bilder, filmer och till och med TrueType fonts för att kunna använda egna fonts i våra applikationer.
Men som ni ser så blir det lite jobbigt i den här koden att hantera xaml-koden. Vi vill ju inte ladda ner zippen varje gång utan bara initialt och sedan kunna återanvända den, dvs vi måste spara undan downloader objektet någonstanns. Det kan lösas på massor av vis. Ett alternativ kan vara att spara undan den i namespacet, typ:
myNewNamespace.Scene.prototype =
{
handleLoad: function(plugIn, userContext, rootElement)
{
this.plugIn = plugIn;
this.root = rootElement;
var downloader = plugIn.createObject("downloader
downloader.open("GET","resources.zip");
downloader.addEventListener("Completed", Silverlight.createDelegate(this, this.ResourceDownloadCompleted));
downloader.addEventListener("DownloadFailed", Silverlight.createDelegate(this, this.ResourceDownloadFailed));
downloader.send();");
},
ResourceDownloadFailed: function(sender, eventArgs)
{
alert("Download of resources failed.");
},
ResourceDownloadCompleted: function(sender, eventArgs)
{
var dl = this.plugIn.createObject("downloader");
myNewNamespace.Resource = dl;
var ctrl = new myNewNamspace.myControl(this.root,100,100);
}
}
På så vis kan vi ändra kontrollen enligt följande:
myNewNamspace.myControl = new function(parent,x,y)
{
this.initialize(parent,x,y);
}
myNewNamespace.myControl.prototype = {
initialize: function(parent,x,y)
{
this.rootControl = parent.getHost().content.createFromXAML(myNewNamespace.Resource.getResponseText("myCtrl.xaml"),true);
this.rootControl["Canvas.Top"] = y;
this.rootControl["Canvas.Left"] = x;
parent.children.add(this.rootControl);
this.rootControl.addEventListener("MouseEnter",Silverlight.createDelegate(this,this.mouseEnterHandler));
this.rootControl.addEventListener("MouseLeave",Silverlight.createDelegate(this,this.mouseLeaveHandler));
this.rootControl.addEventListener("MouseButtonLeftDown",Silverlight.createDelegate(this,this.mouseLeftButtonDownHandler));
this.rootControl.addEventListener("MouseLeftButtonUp",Silverlight.createDelegate(this,this.mouseLeftButtonUpHandler));
}
...
}
Som ni ser finns det massor av alternativ och då har jag inte ens tagit upp alla de alternativ som finns inbyggda rent tekniskt och inte heller alla de olika sätt man kan implementera dem.
Det kommer förhoppningsvis snart en artikel om downloadern också då det finns massor av möjligheter med det objektet. Men målet med artikeln var att påvisa möjligheterna kring kontroller i Silverlight 1.0 och inte downloadern.
Hoppas ni har uppskattattat artikeln, och om så är fallet så får ni självklart gärna lämna en kommentar om vad ni tyckte, vad ni skulle vilja veta mer och var jag har fel. Även önskemål om framtida artiklar mottages självklart med glädje.
Slutligen så får jag väl summera koden som vi kom fram till, dvs den med zip-filen.
if (!window.myNewNamespace)
window.myNewNamespace = {};
myNewNamespace.Scene = new function() {}
myNewNamespace.Scene.prototype =
{
handleLoad: function(plugIn, userContext, rootElement)
{
this.plugIn = plugIn;
this.root = rootElement;
var downloader = plugIn.createObject("downloader
downloader.open("GET","resources.zip");
downloader.addEventListener("Completed", Silverlight.createDelegate(this, this.ResourceDownloadCompleted));
downloader.addEventListener("DownloadFailed", Silverlight.createDelegate(this, this.ResourceDownloadFailed));
downloader.send();");
},
ResourceDownloadFailed: function(sender, eventArgs)
{
alert("Download of resources failed.");
},
ResourceDownloadCompleted: function(sender, eventArgs)
{
var dl = this.plugIn.createObject("downloader");
myNewNamespace.Resource = dl;
var ctrl = new myNewNamspace.myControl(this.root,100,100);
}
}
myNewNamspace.myControl = new function(parent,x,y)
{
this.initialize(parent,x,y);
}
myNewNamespace.myControl.prototype = {
initialize: function(parent,x,y)
{
this.rootControl = parent.getHost().content.createFromXAML(myNewNamespace.Resource.getResponseText("myCtrl.xaml"),true);
this.rootControl["Canvas.Top"] = y;
this.rootControl["Canvas.Left"] = x;
parent.children.add(this.rootControl);
this.rootControl.addEventListener("MouseEnter",Silverlight.createDelegate(this,this.mouseEnterHandler));
this.rootControl.addEventListener("MouseLeave",Silverlight.createDelegate(this,this.mouseLeaveHandler));
this.rootControl.addEventListener("MouseButtonLeftDown",Silverlight.createDelegate(this,this.mouseLeftButtonDownHandler));
this.rootControl.addEventListener("MouseLeftButtonUp",Silverlight.createDelegate(this,this.mouseLeftButtonUpHandler));
},
mouseEnterHandler: function(sender,e)
{
this.rootControl["Fill"] = "#ff336699";
},
mouseLeaveHandler: function(sender,e)
{
this.rootControl["Fill"] = "Grey";
},
mouseLeftButtonDownHandler: function(sender,e)
{
this.rootControl["Fill"] = "Black";
},
mouseLeftButtonUpHandler: function(sender,e)
{
this.rootControl["Fill"] = "#ff336699";
alert("You clicked the button!");
},
set_Left:function(value)
{
this.rootControl["Canvas.Left"] = value;
},
get_Left:function()
{
return this.rootControl["Canvas.Left"];
},
set_Top:function(value)
{
this.rootControl["Canvas.Top"] = value;
},
get_Top:function(value)
{
return this.rootControl["Canvas.Top"];
}
}
Pelle Johansson
Tack!!!!!!!
Oskar Johansson
Mycket bra artikel, även om jag inte börjat tittat något på Silverlight än :P