Академический Документы
Профессиональный Документы
Культура Документы
XAML(shortforExtensibleApplicationMarkupLanguageandpronouncedzammel)isa markuplanguageusedtoinstantiate.NETobjects.AlthoughXAMLisatechnologythatcan be appliedtomanydifferentproblemdomains,itwasinitiallydesignedasapartofWindows PresentationFoundation(WPF),whereitallowsWindowsdeveloperstoconstructrichuser interfaces.YouusethesamestandardtobuilduserinterfacesforSilverlightapplications. Conceptually,XAMLplaysarolethatsalotlikeHTML,andisevenclosertoitsstricter cousin,XHTML.XHTMLallowsyoutodefinetheelementsthatmakeupanordinaryweb page. Similarly,XAMLallowsyoutodefinetheelementsthatmakeupaXAMLcontentregion.To manipulateXHTMLelements,youcanuseclientsideJavaScript.TomanipulateXAML elements,youwriteclientsideC#code.Finally,XAMLandXHTMLsharemanyofthesame syntaxconventions.LikeXHTML,XAMLisanXMLbasedlanguagethatconsistsof elements thatcanbenestedinanyarrangementyoulike.
XAML Basics
TheXAMLstandardisquitestraightforwardonceyouunderstandafewgroundrules:
EveryelementinaXAMLdocumentmapstoaninstanceofaSilverlightclass.The nameoftheelementmatchesthenameoftheclassexactly.Forexample,theelement <Button>instructsSilverlighttocreateaButtonobject. AswithanyXMLdocument,youcannestoneelementinsideanother.Asyoullsee, XAMLgiveseveryclasstheflexibilitytodecidehowithandlesthissituation. However,nestingisusuallyawaytoexpresscontainmentinotherwords,ifyoufind aButtonelementinsideaGridelement,youruserinterfaceprobablyincludesaGrid thatcontainsaButtoninside. Youcansetthepropertiesofeachclassthroughattributes.However,insome situationsanattributeisntpowerfulenoughtohandlethejob.Inthesecases,youll usenestedtagswithaspecialsyntax.
Beforecontinuing,takealookatthisbarebonesXAMLdocument,whichrepresentsa blankpage(ascreatedbyVisualStudio).Thelineshavebeennumberedforeasyreference: 1 2 3 4 5 6 7 <UserControl x:Class="SilverlightApplication1.MainPage" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d" d:DesignWidth="640" d:DesignHeight="480">
XAML Namespaces
Whenyouuseanelementlike<UserControl>inaXAMLfile,theSilverlightparserrecognizes thatyouwanttocreateaninstanceoftheUserControlclass.However,itdoesntnecessarily knowwhat UserControlclasstouse.Afterall,eveniftheSilverlightnamespacesonlyincludea singleclasswiththatname,theresnoguaranteethatyouwontcreateasimilarlynamedclass ofyourown.Clearly,youneedawaytoindicatetheSilverlightnamespaceinformationinorder touseanelement. InSilverlight,classesareresolvedbymappingXMLnamespacestoSilverlight namespaces.Inthesampledocumentshownearlier,fournamespacesaredefined: 2 3 4 5 xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
Because the domain schemas.microsoft.com is owned by Microsoft, only Microsoft will use it in an XML namespace name. The other reason that there isnt a one-to-one mapping between the XML namespaces used in XAML and Silverlight namespaces is because it would significantly complicate your XAML documents. If each Silverlight namespace had a different XML namespace, youd need to specify the right namespace for each and every control you use, which would quickly get messy. Instead, the creators of Silverlight chose to map all the Silverlight namespaces that include user interface elements to a single XML namespace. This works because within the different Silverlight namespaces, no two classes share the same name.
Design Namespaces
Alongwiththesecorenamespacesaretoomorespecializednamespaces,neitherofwhichis essential: http://schemas.openxmlformats.org/markup-compatibility/2006 istheXAML compatibilitynamespace.YoucanuseittotelltheXAMLparserwhatinformation musttoprocessandwhatinformationtoignore. http://schemas.microsoft.com/expression/blend/2008 isanamespacereservedfor designspecificXAMLfeaturesthataresupportedinExpressionBlend(andnow VisualStudio2010).Itsusedprimarilytosetthesizeofthedesignsurfaceforapage. Bothofthesenamespacesareusedinthesinglelineshownhere: 6 mc:Ignorable="d" d:DesignWidth="640" d:DesignHeight="480"> TheDesignWidthandDesignHeightpropertiesareapartofthe http://schemas.microsoft.com/expression/blend/2008namespace.Theytellthedesigntoolto makethepage640 480pixelslargeatdesigntime.Withoutthisdetail,youwouldbeforcedto workwithasquashedupdesignsurfacethatdoesntgivearealisticpreviewofyouruser interface,orsetahardcodedsizeusingtheWidthandHeightproperties(whichisntideal, becauseitpreventsyourpagefromresizingtofitthebrowserwindowatruntime). TheIgnorablepropertyispartofthehttp://schemas.openxmlformats.org/markupcompatibility/ 2006namespace.IttellstheXAMLdesigntoolthatitssafetoignorethepartsof thedocumentthatareprefixedwithadandplacedinthe http://schemas.microsoft.com/expression/blend/2008.Inotherwords,iftheXAMLparser doesntunderstandtheDesignWidthandDesignHeightdetails,itssafetocontinuebecause theyarentcritical.
Custom Namespaces
Inmanysituations,youllwanttohaveaccesstoyourownnamespacesinaXAMLfile.The mostcommonexampleisifyouwanttouseacustomSilverlightcontrolthatyou(oranother developer)havecreated.Inthiscase,youneedtodefineanewXMLnamespaceprefixandmap ittoyourassembly.Heresthesyntaxyouneed: <UserControl x:Class="SilverlightApplication1.MainPage" xmlns:w="clr-namespace:Widgets;assembly=WidgetLibrary" ... > TheXMLnamespacedeclarationsetsthreepiecesofinformation: The XML namespace prefix:Youllusethenamespaceprefixtorefertothenamespacein yourXAMLpage.Inthisexample,thatsw,althoughyoucanchooseanythingyouwant
thatdoesntconflictwithanothernamespaceprefix. The .NET namespace:Inthiscase,theclassesarelocatedintheWidgetsnamespace.If youhaveclassesthatyouwanttouseinmultiplenamespaces,youcanmapthemto differentXMLnamespacesortothesameXMLnamespace(aslongastherearentany conflictingclassnames). The assembly:Inthiscase,theclassesarepartoftheWidgetLibrary.dllassembly.(You dontincludethe.dllextensionwhennamingtheassembly.)Silverlightwilllookforthat assemblyinthesameXAPpackagewhereyourprojectassemblyisplaced. Ifyouwanttouseacustomcontrolthatslocatedinthecurrentapplication,youcan omittheassemblypartofthenamespacemapping,asshownhere: xmlns:w="clr-namespace:Widgets" Onceyouvemappedyour.NETnamespacetoanXMLnamespace,youcanuseit anywhereinyourXAMLdocument.Forexample,iftheWidgetsnamespacecontainsacontrol namedHotButton,youcouldcreateaninstancelikethis: <w:HotButton Text="Click Me!" Click="DoSomething"></w:HotButton>
Naming Elements
Theresonemoredetailtoconsider.Inyourcodebehindclass,youlloftenwanttomanipulate elementsprogrammatically.Forexample,youmightwanttoreadorchangepropertiesor attachanddetacheventhandlersonthefly.Tomakethispossible,thecontrolmustincludea XAMLNameattribute.Inthepreviousexample,theGridcontrolalreadyincludestheName attribute,soyoucanmanipulateitinyourcodebehindfile. 6 <Grid x:Name="LayoutRoot"> 7 </Grid> TheNameattributetellstheXAMLparsertoaddafieldlikethistotheautomatically generatedportionoftheMainPageclass: private System.Windows.Controls.Grid LayoutRoot; Nowyoucaninteractwiththegridinyourpageclasscodebyusingthename LayoutRoot.
thanyoumightinitiallyrealize.ThevalueinanXMLattributeisalwaysaplaintextstring. However,objectpropertiescanbeany.NETtype.Inthepreviousexample,therearetwo propertiesthatuseenumerations(VerticalAlignmentandHorizontalAlignment),onestring (FontFamily),oneinteger(FontSize),andoneBrushobject(Foreground). Inordertobridgethegapbetweenstringvaluesandnonstringproperties,theXAML parserneedstoperformaconversion.Theconversionisperformedbytype converters,abasic pieceofinfrastructurethatsborrowedfromthefull.NETFramework. Essentially,atypeconverterhasoneroleinlifeitprovidesutilitymethodsthatcan convertaspecific.NETdatatypetoandfromanyother.NETtype,suchasastring representationinthiscase.TheXAMLparserfollowstwostepstofindatypeconverter: 1.Itexaminesthepropertydeclaration,lookingforaTypeConverterattribute.(Ifpresent, theTypeConverterattributeindicateswhatclasscanperformtheconversion.)For example,whenyouuseapropertysuchasForeground,.NETchecksthedeclarationof theForegroundproperty. 2.IftheresnoTypeConverterattributeonthepropertydeclaration,theXAMLparser checkstheclassdeclarationofthecorrespondingdatatype.Forexample,the ForegroundpropertyusesaBrushobject.TheBrushclass(anditsderivatives)usethe BrushConverterbecausetheBrushclassisdecoratedwiththe TypeConverter(typeof(BrushConverter))attributedeclaration. 3.Iftheresnoassociatedtypeconverteronthepropertydeclarationortheclass declaration,theXAMLparsergeneratesanerror.
Complex Properties
Ashandyastypeconvertersare,theyarentpracticalforallscenarios.Forexample,some propertiesarefullfledgedobjectswiththeirownsetofproperties.Althoughitspossibleto createastringrepresentationthatthetypeconvertercoulduse,thatsyntaxmightbedifficultto useandpronetoerror. Fortunately,XAMLprovidesanotheroption:property-element syntax.Withpropertyelement syntax,youaddachildelementwithanameintheformParent.PropertyName.For example,theGridhasaBackgroundpropertythatallowsyoutosupplyabrushthatsusedto painttheareabehindtheelements.Ifyouwanttouseacomplexbrushonemoreadvanced thanasolidcolorfillyoullneedtoaddachildtagnamedGrid.Background,asshownhere: <Grid x:Name="grid1"> <Grid.Background> ... </Grid.Background> ... </Grid> Thekeydetailthatmakesthisworkistheperiod(.)intheelementname.This distinguishespropertiesfromothertypesofnestedcontent. Thisstillleavesonedetailnamely,onceyouveidentifiedthecomplexpropertyyou wanttoconfigure,howdoyousetit?Heresthetrick.Insidethenestedelement,youcanadd anothertagtoinstantiateaspecificclass.Intheeightballexample(showninFigure21),the backgroundisfilledwithagradient.Todefinethegradientyouwant,youneedtocreatea LinearGradientBrushobject. UsingtherulesofXAML,youcancreatetheLinearGradientBrushobjectusingan elementwiththenameLinearGradientBrush: <Grid x:Name="grid1"> <Grid.Background> <LinearGradientBrush> </LinearGradientBrush> </Grid.Background> ... </Grid>
TheLinearGradientBrushispartoftheSilverlightsetofnamespaces,soyoucankeep usingthedefaultXMLnamespaceforyourtags. However,itsnotenoughtosimplycreatetheLinearGradientBrushyoualsoneedto specifythecolorsinthatgradient.YoudothisbyfillingtheLinearGradientBrush.GradientStops propertywithacollectionofGradientStopobjects.Onceagain,theGradientStopspropertyis toocomplextobesetwithanattributevaluealone.Instead,youneedtorelyonthepropertyelement syntax: <Grid x:Name="grid1"> <Grid.Background> <LinearGradientBrush> <LinearGradientBrush.GradientStops> </LinearGradientBrush.GradientStops> </LinearGradientBrush> </Grid.Background> ... </Grid> Finally,youcanfilltheGradientStopscollectionwithaseriesofGradientStopobjects. EachGradientStopobjecthasanOffsetandColorproperty.Youcansupplythesetwovalues usingtheordinarypropertyattributesyntax: <Grid x:Name="grid1"> <Grid.Background> <LinearGradientBrush> <LinearGradientBrush.GradientStops> <GradientStop Offset="0.00" Color="Yellow" /> <GradientStop Offset="0.50" Color="White" /> <GradientStop Offset="1.00" Color="Purple" /> </LinearGradientBrush.GradientStops> </LinearGradientBrush> </Grid.Background> ... </Grid>
Attached Properties
Alongwithordinaryproperties,XAMLalsoincludestheconceptofattached properties propertiesthatmayapplytoseveralelementsbutaredefinedinadifferentclass.InSilverlight, attachedpropertiesarefrequentlyusedtocontrollayout. Hereshowitworks.Everycontrolhasitsownsetofintrinsicproperties.Forexample, atextboxhasaspecificfont,textcolor,andtextcontentasdictatedbypropertiessuchas FontFamily,Foreground,andText.Whenyouplaceacontrolinsideacontainer,itgains additionalfeatures,dependingonthetypeofcontainer.Forexample,ifyouplaceatextbox insideagrid,youneedtobeabletochoosethegridcellwhereitspositioned.Theseadditional detailsaresetusingattachedproperties. Attachedpropertiesalwaysuseatwopartnameinthisform: DefiningType.PropertyName.ThistwopartnamingsyntaxallowstheXAMLparserto distinguishbetweenanormalpropertyandanattachedproperty. Intheeightballexample,attachedpropertiesallowtheindividualelementstoplace themselvesonseparaterowsinthe(invisible)grid: <TextBox ... Grid.Row="0"> </TextBox> <Button ... Grid.Row="1"> </Button> <TextBox ... Grid.Row="2"> </TextBox>
Attachedpropertiesarentreallypropertiesatall.Theyreactuallytranslatedinto methodcalls.TheXAMLparsercallsthestaticmethodthathasthisform: DefiningType.SetPropertyName().Forexample,inthepreviousXAMLsnippet,thedefiningtype istheGridclass,andthepropertyisRow,sotheparsercallsGrid.SetRow(). WhencallingSetPropertyName(),theparserpassestwoparameters:theobjectthats beingmodified,andthepropertyvaluethatsspecified.Forexample,whenyousetthe Grid.RowpropertyontheTextBoxcontrol,theXAMLparserexecutesthiscode: Grid.SetRow(txtQuestion, 0); Thispattern(callingastaticmethodofthedefiningtype)isaconveniencethat concealswhatsreallytakingplace.Tothecasualeye,thiscodeimpliesthattherownumberis storedintheGridobject.However,therownumberisactuallystoredintheobjectthatit applies tointhiscase,theTextBoxobject. ThissleightofhandworksbecausetheTextBoxderivesfromtheDependencyObject baseclass,asdoallSilverlightelements.TheDependencyObjectisdesignedtostoreavirtually unlimitedcollectionofdependencyproperties(andattachedpropertiesareonetypeof dependencyproperty). Infact,theGrid.SetRow()methodisactuallyashortcutthatsequivalenttocallingthe DependencyObject.SetValue()method,asshownhere: txtQuestion.SetValue(Grid.RowProperty, 0); AttachedpropertiesareacoreingredientofSilverlight.Theyactasanallpurpose extensibilitysystem.Forexample,bydefiningtheRowpropertyasanattachedproperty,you guaranteethatitsusablewithanycontrol.
Nesting Elements
Asyouveseen,XAMLdocumentsarearrangedasaheavilynestedtreeofelements.Inthe currentexample,aUserControlelementcontainsaGridelement,whichcontainsTextBoxand Buttonelements. XAMLallowseachelementtodecidehowitdealswithnestedelements.This interactionismediatedthroughoneofthreemechanismsthatareevaluatedinthisorder: IftheparentimplementsIList<T>,theparsercallstheIList<T>.Add()methodand passesinthechild. IftheparentimplementsIDictionary<T>,theparsercallsIDictionary<T>.Add()and passesinthechild.Whenusingadictionarycollection,youmustalsosetthex:Key attributetogiveakeynametoeachitem. IftheparentisdecoratedwiththeContentPropertyattribute,theparserusesthe childtosetthatproperty. Forexample,earlierinthischapteryousawhowaLinearGradientBrushcanholda collectionofGradientStopobjectsusingsyntaxlikethis: <LinearGradientBrush> <LinearGradientBrush.GradientStops> <GradientStop Offset="0.00" Color="Yellow" /> <GradientStop Offset="0.50" Color="White" /> <GradientStop Offset="1.00" Color="Purple" /> </LinearGradientBrush.GradientStops> </LinearGradientBrush> TheXAMLparserrecognizestheLinearGradientBrush.GradientStopselementisa complexpropertybecauseitincludesaperiod.However,itneedstoprocessthetagsinside(the threeGradientStopelements)alittledifferently.Inthiscase,theparserrecognizesthatthe GradientStopspropertyreturnsaGradientStopCollectionobject,andthe GradientStopCollectionimplementstheIListinterface.Thus,itassumes(quiterightly)that eachGradientStopshouldbeaddedtothecollectionusingtheIList.Add()method:
Somepropertiesmightsupportmorethanonetypeofcollection.Inthiscase,you needtoaddatagthatspecifiesthecollectionclass,likethis: <LinearGradientBrush> <LinearGradientBrush.GradientStops> <GradientStopCollection> <GradientStop Offset="0.00" Color="Yellow" /> <GradientStop Offset="0.50" Color="White" /> <GradientStop Offset="1.00" Color="Purple" /> </GradientStopCollection> </LinearGradientBrush.GradientStops> </LinearGradientBrush> Nestedcontentdoesntalwaysindicateacollection.Forexample,considertheGrid element,whichcontainsseveralotherelements: <Grid x:Name="grid1"> ... <TextBox x:Name="txtQuestion" ... > </TextBox> <Button x:Name="cmdAnswer" ... > </Button> <TextBox x:Name="txtAnswer" ... > </TextBox> </Grid>
---------------------------------------------------------------------------------------------------------------------
Dependency Properties
Essentially,adependencypropertyisapropertythatcanbesetdirectly(forexample,byyour code)orbyoneofSilverlightsservices(suchasdatabinding,styles,oranimation).Thekey featureofthissystemisthewaythatthesedifferentpropertyprovidersareprioritized.For example,ananimationwilltakeprecedenceoverallotherserviceswhileitsrunning.These overlappingfactorsmakeforaveryflexiblesystem.Theyalsogivedependencypropertiestheir nameinessence,adependencypropertydepends onmultiplepropertyproviders,eachwith itsownlevelofprecedence. MostofthepropertiesthatareexposedbySilverlightelementsaredependency properties.Forexample,theTextpropertyoftheTextBlock,theContentpropertyoftheButton,
andtheBackgroundpropertyoftheGridallofwhichyousawinthesimpleexamplein Chapter1arealldependencyproperties.theyredesignedtobeconsumedinthesamewayasnormal properties.ThatsbecausethedependencypropertiesintheSilverlightlibrariesarealways wrappedbyordinarypropertydefinitions. Althoughdependencyfeaturescanbereadandsetincodelikenormalproperties, theyreimplementedquitedifferentlybehindthescenes.Thesimplereasonwhyis performance.IfthedesignersofSilverlightsimplyaddedextrafeaturesontopofthe.NET propertysystem,theydneedtocreateacomplex,bulkylayerforyourcodetotravelthrough. Ordinarypropertiescouldnotsupportallthefeaturesofdependencypropertieswithoutthis extraoverhead.
forthepropertyandacallbackthatwillbetriggeredwhenthepropertyischanged.If youdontneedtouseeitherfeature,supplyanullvalue,asinthisexample. Withthesedetailsinplace,youreabletoregisteranewdependencypropertysothat itsavailableforuse.However,whereastypicalpropertyproceduresretrieveorsetthevalueofa privatefield,thepropertyproceduresforaSilverlightpropertyusetheGetValue()and SetValue()methodsthataredefinedinthebaseDependencyObjectclass.Heresanexample: public Thickness Margin { get { return (Thickness)GetValue(MarginProperty); } set { SetValue(MarginProperty, value); } } Whenyoucreatethepropertywrapper,youshouldincludenothingmorethanacallto SetValue()andacalltoGetValue(),asinthepreviousexample.Youshouldnot addanyextra codetovalidatevalues,raiseevents,andsoon.ThatsbecauseotherfeaturesinSilverlightmay bypassthepropertywrapperandcallSetValue()andGetValue()directly.Oneexampleiswhen theSilverlightparserreadsyourXAMLmarkupandusesittoinitializeyouruserinterface. Younowhaveafullyfunctioningdependencyproperty,whichyoucansetjustlikeany other.NETpropertyusingthepropertywrapper: myElement.Margin = new Thickness(5); Dependencypropertiesfollowstrictrulesofprecedenceto determinetheircurrentvalue.Evenifyoudontsetadependencypropertydirectly,itmay alreadyhaveavalueperhapsonethatsappliedbyabindingorastyleoronethatsinherited throughtheelementtree.(Youlllearnmoreabouttheserulesofprecedenceinthenext section.)However,assoonasyousetthevaluedirectly,itoverridestheseotherinfluences. Atsomepointlater,youmaywanttoremoveyourlocalvaluesettingandletthe propertyvaluebedeterminedasthoughyouneversetit.Obviously,youcantaccomplishthis bysettinganewvalue.Instead,youneedtouseanothermethodthatsinheritedfrom DependencyObject:theClearValue()method. ThismethodtellsSilverlighttotreatthevalueasthoughyouneversetit,thereby returningittoitspreviousvalue.Usually,thiswillbethedefaultvaluethatssetforthe property,butitcouldalsobethevaluethatssetthroughpropertyinheritanceorbyastyle,
ButtonoraContentControl),theycascadedowntothecontainedcontentelements(like theTextBlockthatactuallyholdsthetextinside 5.Default value: Ifnootherpropertysetterisatwork,thedependencypropertygetsits defaultvalue.ThedefaultvalueissetwiththePropertyMetadataobjectwhenthe dependencypropertyisfirstcreated,asexplainedintheprevioussection. Oneoftheadvantagesofthissystemisthatitsveryeconomical.Forexample,ifthe valueofapropertyhasnotbeensetlocally,Silverlightwillretrieveitsvaluefromthetemplate orastyle.Inthiscase,noadditionalmemoryisrequiredtostorethevalue.Anotheradvantage isthatdifferentpropertyprovidersmayoverrideoneanother,buttheydontoverwrite each other.Forexample,ifyousetalocalvalueandthentriggerananimation,theanimation temporarilytakescontrol.However,yourlocalvalueisretainedandwhentheanimationendsit comesbackintoeffect.
Attached Properties
Chapter2introducedaspecialtypeofdependencypropertycalledanattached property.An attachedpropertyisafullfledgeddependencypropertyand,likealldependencyproperties,its managedbytheSilverlightpropertysystem.Thedifferenceisthatanattachedpropertyapplies toaclassotherthantheonewhereitsdefined. Themostcommonexampleofattachedpropertiesisfoundinthelayoutcontainers yousawinChapter3.Forexample,theGridclassdefinestheattachedpropertiesRowand Column,whichyousetonthecontainedelementstoindicatewheretheyshouldbepositioned. Similarly,theCanvasdefinestheattachedpropertiesLeftandTopthatletyouplaceelements usingabsolutecoordinates. Todefineanattachedproperty,youusetheDependencyProperty.RegisterAttached() methodinsteadofRegister().HeresthecodefromtheGridclassthatregisterstheattached Grid.Rowproperty: RowProperty = DependencyProperty.RegisterAttached( "Row", typeof(int), typeof(Grid), null); Similarly,theCanvasdefinestheattachedpropertiesLeftandTopthatletyouplaceelements usingabsolutecoordinates. Todefineanattachedproperty,youusetheDependencyProperty.RegisterAttached() methodinsteadofRegister().HeresthecodefromtheGridclassthatregisterstheattached Grid.Rowproperty: RowProperty = DependencyProperty.RegisterAttached( "Row", typeof(int), typeof(Grid), null); TheparametersareexactlythesamefortheRegisterAttached()methodastheyarefor theRegister()method. Whencreatinganattachedproperty,youdontdefinethe.NETpropertywrapper. Thatsbecauseattachedpropertiescanbesetonany dependencyobject.Forexample,the Grid.RowpropertymaybesetonaGridobject(ifyouhaveoneGridnestedinsideanother)or onsomeotherelement.Infact,theGrid.Rowpropertycanbesetonanelementevenifthat elementisntinaGridandevenifthereisntasingleGridobjectinyourelementtree. Insteadofusinga.NETpropertywrapper,attachedpropertiesrequireapairofstatic methodsthatcanbecalledtosetandgetthepropertyvalue.Thesemethodsusethefamiliar SetValue()andGetValue()methods(inheritedfromtheDependencyObjectclass).Thestatic methodsshouldbenamedSetPropertyName()andGetPropertyName(). TheSetPropertyName()methodtakestwoarguments:theelementonwhichyouwish tosettheproperty,andthepropertyvalue.BecausetheGrid.Rowpropertyisdefinedasan integer,thesecondparameteroftheSetRow()methodmustbeaninteger: public static void SetRow(UIElement element, int value) { element.SetValue(Grid.RowProperty, value); } TheGetPropertyName()methodtakestheelementonwhichthepropertyisset,and returnsthepropertyvalue.BecausetheGrid.Rowpropertyisdefinedasaninteger,the
GetRow()methodmustreturnaninteger: public static int GetRow(UIElement element) { return (int)element.GetValue(Grid.RowProperty); } AndheresanexamplethatpositionsanelementinthefirstrowofaGridusingcode: Grid.SetRow(txtElement, 0); ThissetstheGrid.Rowpropertyto0onthetxtElementobject,whichisaTextBox. BecauseGrid.Rowisanattachedproperty,Silverlightallowsyoutoapplyittoanyother element.
AmoreinterestingexperimentistocreateaversionoftheWrapBreakPanelthatuses anattachedproperty.Asyouvealreadylearned,attachedpropertiesareparticularlyusefulin layoutcontainers,becausetheyallowchildrentopassalongextralayoutinformation(suchas rowpositioningintheGridorcoordinatesandlayeringintheCanvas). TheWrapBreakPanelincludesasattachedpropertythatallowsanychildelementto forcealinebreak.Byusingthisattachedproperty,youcanensurethataspecificelement beginsonanewline,nomatterwhatthecurrentwidthoftheWrapBreakPanel.Theattached propertyisnamedLineBreakBefore,andtheWrapBreakPaneldefinesitlikethis: public static DependencyProperty LineBreakBeforeProperty = DependencyProperty.RegisterAttached("LineBreakBefore", typeof(bool), typeof(WrapBreakPanel), null); ToimplementtheLineBreakBeforeproperty,youneedtocreatethestaticgetandset methodsthatcallGetValue()andSetValue()ontheelement: public static bool GetLineBreakBefore(UIElement element) { return (bool)element.GetValue(LineBreakBeforeProperty); } public static void SetLineBreakBefore(UIElement element, bool value) { element.SetValue(LineBreakBeforeProperty, value); } YoucanthenmodifytheMeasureOverride()andArrangeOverride()methodstocheck forforcedbreaks,asshownhere: // Check if the element fits in the line, or if a line break was requested. if ((currentLineSize.Width + desiredSize.Width > constraint.Width) || (WrapBreakPanel.GetLineBreakBefore(element))) { ... } Tousethisfunctionality,yousimplyneedtoaddtheLineBreakBeforepropertytoan element,asshownhere: <local:WrapBreakPanel Margin="5" Background="LawnGreen"> <Button Width="50" Content="Button"></Button> <Button Width="150" Content="Wide Button"></Button> <Button Width="50" Content="Button"></Button> <Button Width="150" Content="Button with a Break" local:WrapBreakPanel.LineBreakBefore="True" FontWeight="Bold"></Button> <Button Width="150" Content="Wide Button"></Button> <Button Width="50" Content="Button"></Button> </local:WrapBreakPanel>
Routed Events
Every.NETdeveloperisfamiliarwiththeideaofeventsmessagesthataresentbyanobject (suchasaSilverlightelement)tonotifyyourcodewhensomethingsignificantoccurs.WPF enhancedthe.NETeventmodelwithanewconceptofevent routing,whichallowsaneventto originateinoneelementbutberaisedbyanotherone.Forexample,eventroutingallowsaclick thatbeginsinashapetoriseuptothatshapescontainerandthentothecontainingpage beforeitshandledbyyourcode. SilverlightborrowssomeofWPFsroutedeventmodel,butinadramaticallysimplified form.WhileWPFsupportsseveraltypesofroutedevents,Silverlightonlyallowsone:bubbled events thatriseupthecontainmenthierarchyfromdeeplynestedelementstotheircontainers. Furthermore,Silverlightseventbubblingislinkedtoafewkeyboardandmouseinputevents (likeMouseMoveandKeyDown)anditssupportedbyjustafewlowlevelelements. Silverlightdoesntuseeventbubblingforhigherlevelcontrolevents(likeClick),andyou cantuseeventroutingwiththeeventsinyourowncustomcontrols.
Event
Bubbles
Occurswhenakeyispressed. Occurswhenakeyisreleased.
Description
Event Bubbling
Eventbubblingisdesignedtosupportcompositioninotherwords,toletyoubuild complexcontrolsoutofsimpleringredients.OneexampleisSilverlightscontent controls, whicharecontrolsthathavetheabilitytoholdasinglenestedelementascontent.These controlsareusuallyidentifiedbythefactthattheyprovideapropertynamedContent.For example,thebuttonisacontentcontrol.Ratherthandisplayingalineoftext,youcanfillitwith aStackPanelthatcontainsawholegroupofelements,likethis: <Button BorderBrush="Black" BorderThickness="1" Click="cmd_Click"> <StackPanel> <TextBlock Margin="3" Text="Image and text label"></TextBlock> <Image Source="happyface.jpg" Stretch="None"></Image> <TextBlock Margin="3" Text="Courtesy of the StackPanel"></TextBlock> </StackPanel> </Button>
Inthissituation,itsimportantthatthebuttonreactstothemouseeventsofits containedelements.Inotherwords,theButton.Clickeventshouldfirewhentheuserclicksthe image,someofthetext,orpartoftheblankspaceinsidethebuttonborder.Ineverycase,youd liketorespondwiththesamecode. Ofcourse,youcouldwireupthesameeventhandlertotheMouseLeftButtonDownor MouseLeftButtonUpeventofeachelementinsidethebutton,butthatwouldresultina significantamountofclutteranditwouldmakeyourmarkupmoredifficulttomaintain.Event bubblingprovidesabettersolution. Whenthehappyfaceisclicked,theMouseLeftButtonDowneventfiresfirstforthe Image,thenfortheStackPanel,andthenforthecontainingbutton.Thebuttonthenreactsto theMouseLeftButtonDownbyfiringitsownClickevent,towhichyourcoderesponds(withits cmd_Clickeventhandler).
Note MouseLeftButtonDown and MouseLeftButtonUp are the only events that controls suppress. The bubbling key events (KeyUp, KeyDown, LostFocus, and GotFocus) arent suppressed by any controls.
Inthisexample,youcanwatchtheMouseLeftButtonDowneventbubblebyattaching eventhandlerstomultipleelements.Astheeventisinterceptedatdifferentlevels,theevent sequenceisdisplayedinalistbox.Figure44showsthedisplayimmediatelyafterclickingthe happyfaceimageinthebutton.Asyoucansee,theMouseLeftButtownDowneventfiresinthe imageandtheninthecontainingStackPanelandisfinallyinterceptedbythebutton,which handlesit.ThebuttondoesnotfiretheMouseLeftButtonDownevent,andthereforethe MouseLeftButtonDowneventdoesnotbubbleuptotheGridthatholdsthebutton. Tocreatethistestpage,theimageandeveryelementaboveitintheelementhierarchy arewireduptothesameeventhandleramethodnamedSomethingClicked().Heresthe XAMLthatdoesit: <UserControl x:Class="RoutedEvents.EventBubbling" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"> <Grid Margin="3" MouseLeftButtonDown="SomethingClicked">
<Grid.RowDefinitions> <RowDefinition Height="Auto"></RowDefinition> <RowDefinition Height="*"></RowDefinition> <RowDefinition Height="Auto"></RowDefinition> <RowDefinition Height="Auto"></RowDefinition> </Grid.RowDefinitions> <Button Margin="5" Grid.Row="0" MouseLeftButtonDown="SomethingClicked"> <StackPanel MouseLeftButtonDown="SomethingClicked"> <TextBlock Margin="3" MouseLeftButtonDown="SomethingClicked" HorizontalAlignment="Center" Text="Image and text label"></TextBlock> <Image Source="happyface.jpg" Stretch="None" MouseLeftButtonDown="SomethingClicked"></Image> <TextBlock Margin="3" HorizontalAlignment="Center" MouseLeftButtonDown="SomethingClicked" Text="Courtesy of the StackPanel"></TextBlock> </StackPanel> </Button> <ListBox Grid.Row="1" Margin="5" x:Name="lstMessages"></ListBox> <Button Grid.Row="3" Margin="5" Padding="3" x:Name="cmdClear" Click="cmdClear_Click" Content="Clear List"></Button> </Grid> </UserControl> TheSomethingClicked()methodsimplyexaminesthepropertiesofthe RoutedEventArgsobjectandaddsamessagetothelistbox: protected int eventCounter = 0; private void SomethingClicked(object sender, MouseButtonEventArgs e) { eventCounter++; string message = "#" + eventCounter.ToString() + ":\r\n" + " Sender: " + sender.ToString() + "\r\n"; lstMessages.Items.Add(message); } private void cmdClear_Click(object sender, RoutedEventArgs e) { lstMessages.Items.Clear(); } WhendealingwithabubbledeventlikeMouseLeftButtonDown,thesenderparameter thatspassedtoyoureventhandleralwaysprovidesareferencetothelastlinkinthechain.For example,ifaneventbubblesupfromanimagetoaStackPanelbeforeyouhandleit,thesender parameterreferencestheStackPanelobject. Insomecases,youllwanttodeterminewheretheeventoriginallytookplace.The eventargumentsobjectforabubbledeventprovidesaSourcepropertythattellsyouthe specificelementthatoriginallyraisedtheevent.Inthecaseofakeyboardevent,thisisthe controlthathadfocuswhentheeventoccurred(forexample,whenthekeywaspressed).Inthe caseofamouseevent,thisisthetopmostelementunderthemousepointerwhentheevent occurred(forexample,whenamousebuttonwasclicked).However,theSourcepropertycan getabitmoredetailedthanyouwantforexample,ifyouclicktheblankspacethatformsthe backgroundofabutton,theSourcepropertywillprovideareferencetotheShapeorPath objectthatactuallydrawsthepartofbackgroundyouclicked. AlongwithSource,theeventargumentsobjectforabubbledeventalsoprovidesa BooleanpropertynamedHandled,whichallowsyoutosuppresstheevent. handletheMouseLeftButtonDowneventintheStackPanelandsetHandledtotrue,the StackPanelwillnotfiretheMouseLeftButtonDownevent.Asaresult,whenyouclickthe StackPanel(oroneoftheelementsinside),theMouseLeftButtonDowneventwillnotreachthe button,andtheClickeventwillneverfire.Youcanusethistechniquewhenbuildingcustom controlsifyouvetakencareofauseractionlikeabuttonclick,andyoudontwanthigherlevel elementstogetinvolved,
Mouse Movements
private void MouseMoved(object sender, MouseEventArgs e) { Point pt = e.GetPosition(this); lblInfo.Text = String.Format("You are at ({0},{1}) in page coordinates", pt.X, pt.Y); }
EachcircleisaninstanceoftheEllipseelement,whichissimplyacoloredshapethats abasicingredientin2Ddrawing.Obviously,youcantdefinealltheellipsesyouneedinyourXAML markup. TheXAMLpageforthisexampleusesasingleeventhandlerforthe Canvas.MouseLeftButtonDownevent.TheCanvas.Backgroundpropertyisalsoset,becausea Canvaswiththedefaulttransparentbackgroundcantcapturemouseevents.Noother elementsaredefined. <Canvas x:Name="parentCanvas" MouseLeftButtonDown="canvas_Click" Background="White"> </Canvas> Inthecodebehindclass,youneedtwomembervariablestokeeptrackofwhetheror notanellipsedraggingoperationiscurrentlytakingplace: private bool isDragging = false; // When an ellipse is clicked, record the exact position // where the click is made. private Point mouseOffset; HerestheeventhandlingcodethatcreatesanellipsewhentheCanvasisclicked: private void canvas_Click(object sender, MouseButtonEventArgs e) { // Create an ellipse (unless the user is in the process // of dragging another one).
if (!isDragging) { // Give the ellipse a 50-pixel diameter and a red fill. Ellipse ellipse = new Ellipse(); ellipse.Fill = new SolidColorBrush(Colors.Red); ellipse.Width = 50; ellipse.Height = 50; // Use the current mouse position for the center of // the ellipse. Point point = e.GetPosition(this); ellipse.SetValue(Canvas.TopProperty, point.Y - ellipse.Height/2); ellipse.SetValue(Canvas.LeftProperty, point.X - ellipse.Width/2); // Watch for left-button clicks. ellipse.MouseLeftButtonDown += ellipse_MouseDown; // Add the ellipse to the Canvas. parentCanvas.Children.Add(ellipse); } } private bool isDragging = false; // When an ellipse is clicked, record the exact position // where the click is made. private Point mouseOffset; HerestheeventhandlingcodethatcreatesanellipsewhentheCanvasisclicked: private void canvas_Click(object sender, MouseButtonEventArgs e) { // Create an ellipse (unless the user is in the process // of dragging another one). if (!isDragging) { // Give the ellipse a 50-pixel diameter and a red fill. Ellipse ellipse = new Ellipse(); ellipse.Fill = new SolidColorBrush(Colors.Red); ellipse.Width = 50; ellipse.Height = 50; // Use the current mouse position for the center of // the ellipse. Point point = e.GetPosition(this); ellipse.SetValue(Canvas.TopProperty, point.Y - ellipse.Height/2); ellipse.SetValue(Canvas.LeftProperty, point.X - ellipse.Width/2); // Watch for left-button clicks. ellipse.MouseLeftButtonDown += ellipse_MouseDown; // Add the ellipse to the Canvas. parentCanvas.Children.Add(ellipse); } } Notonlydoesthiscodecreatetheellipse,italsoconnectsaneventhandlerthat respondswhentheellipseisclicked.Thiseventhandlerchangestheellipsecolorandinitiates theellipsedraggingoperation: private void ellipse_MouseDown(object sender, MouseButtonEventArgs e) { // Dragging mode begins. isDragging = true; Ellipse ellipse = (Ellipse)sender; // Get the position of the click relative to the ellipse // so the top-left corner of the ellipse is (0,0). mouseOffset = e.GetPosition(ellipse); // Change the ellipse color. ellipse.Fill = new SolidColorBrush(Colors.Green); // Watch this ellipse for more mouse events.
ellipse.MouseMove += ellipse_MouseMove; ellipse.MouseLeftButtonUp += ellipse_MouseUp; // Capture the mouse. This way you'll keep receiving // the MouseMove event even if the user jerks the mouse // off the ellipse. ellipse.CaptureMouse(); } TheellipseisntactuallymoveduntiltheMouseMoveeventoccurs.Atthispoint,the Canvas.LeftandCanvas.Topattachedpropertiesaresetontheellipsetomoveittoitsnew position.Thecoordinatesaresetbasedonthecurrentpositionofthemouse,takinginto accountthepointwheretheuserinitiallyclicked.Thisellipsethenmovesseamlesslywiththe mouse,untiltheleftmousebuttonisreleased. private void ellipse_MouseMove(object sender, MouseEventArgs e) { if (isDragging) { Ellipse ellipse = (Ellipse)sender; // Get the position of the ellipse relative to the Canvas. Point point = e.GetPosition(parentCanvas); // Move the ellipse. ellipse.SetValue(Canvas.TopProperty, point.Y - mouseOffset.Y); ellipse.SetValue(Canvas.LeftProperty, point.X - mouseOffset.X); } } Whentheleftmousebuttonisreleased,thecodechangesthecoloroftheellipse, releasesthemousecapture,andstopslisteningfortheMouseMoveandMouseUpevents.The usercanclicktheellipseagaintostartthewholeprocessover. private void ellipse_MouseUp(object sender, MouseButtonEventArgs e) { if (isDragging) { Ellipse ellipse = (Ellipse)sender; // Change the ellipse color. ellipse.Fill = new SolidColorBrush(Colors.Orange); // Don't watch the mouse events any longer. ellipse.MouseMove -= ellipse_MouseMove; ellipse.MouseLeftButtonUp -= ellipse_MouseUp; ellipse.ReleaseMouseCapture(); isDragging = false; } }
Mouse Cursors
Acommontaskinanyapplicationistoadjustthemousecursortoshowwhentheapplicationis busyortoindicatehowdifferentcontrolswork.Youcansetthemousepointerforanyelement usingtheCursorproperty,whichisinheritedfromtheFrameworkElementclass. EverycursorisrepresentedbyaSystem.Windows.Input.Cursorobject.Theeasiestway togetaCursorobjectistousethestaticpropertiesoftheCursorsclass(fromthe System.Windows.Inputnamespace).TheyincludeallthestandardWindowscursors,suchas thehourglass,thehand,resizingarrows,andsoon.Heresanexamplethatsetsthehourglass forthecurrentpage: this.Cursor = Cursors.Wait; Nowwhenyoumovethemouseoverthecurrentpage,themousepointerchangesto thefamiliarhourglassicon(inWindowsXP)ortheswirl(inWindowsVista).
Note The properties of the Cursors class draw on the cursors that are defined on the computer. If the user
has customized the set of standard cursors, the application you create will use those customized cursors.
IfyousetthecursorinXAML,youdontneedtousetheCursorsclassdirectly.Thats becausethetypeconverterfortheCursorpropertyisabletorecognizethepropertynamesand retrievethecorrespondingCursorobjectfromtheCursorsclass.Thatmeansyoucanwrite markuplikethistoshowthehelpcursor(acombinationofanarrowandaquestionmark) whenthemouseispositionedoverabutton: <Button Cursor="Help" Content="Help Me"></Button>
Key Presses
Whenyoureacttoakeypressevent,youreceiveaKeyEventArgsobjectthatprovides twoadditionalpiecesofinformation:KeyandPlatformKeyCode.Keyindicatesthekeythatwas pressedasavaluefromtheSystem.Windows.Input.Keyenumeration(forexample,Key.Sisthe Skey).PlatformKeyCodeisanintegervaluethatmustbeinterpretedbasedonthehardware andoperatingsystemthatsbeingusedontheclientcomputer.Forexample,anonstandardkey thatSilverlightcantrecognizewillreturnaKey.UnknownvaluefortheKeypropertybutwill provideaPlatformKeyCodethatsuptoyoutointerpret.Anexampleofaplatformspecifickey isScrollLockonMicrosoftWindowscomputers. Thebestwaytounderstandthekeyeventsistouseasampleprogramsuchastheone showninFigure47alittlelaterinthischapter.Itmonitorsatextboxforthreeevents: KeyDown,KeyUp,andthehigherlevelTextChangedevent(whichisraisedbytheTextBox control),usingthismarkup: <TextBox KeyDown="txt_KeyDown" KeyUp="txt_KeyUp" TextChanged="txt_TextChanged"></TextBox> Here,theTextBoxhandlestheKeyDown,KeyUp,andTextChangedeventsexplicitly. However,theKeyDownandKeyUpeventsbubble,whichmeansyoucanhandlethemata higherlevel.Forexample,youcanattachKeyDownandKeyUpeventhandlersontherootGrid toreceivekeypressesthataremadeanywhereinthepage. Herearetheeventhandlersthatreacttotheseevents: private void txt_KeyUp(object sender, KeyEventArgs e) { string message = "KeyUp " + " Key: " + e.Key; lstMessages.Items.Add(message); } private void txt_KeyDown(object sender, KeyEventArgs e) { string message = "KeyDown " + " Key: " + e.Key; lstMessages.Items.Add(message); } private void txt_TextChanged(object sender, TextChangedEventArgs e) { string message = "TextChanged"; lstMessages.Items.Add(message); }
Key Modifiers
Whenakeypressoccurs,youoftenneedtoknowmorethanjustwhatkeywaspressed.Itsalso
importanttofindoutwhatotherkeyswerehelddownatthesametime.Thatmeansyoumight wanttoinvestigatethestateofotherkeys,particularlymodifierssuchasShiftandCtrl,bothof whicharesupportedonallplatforms.Althoughyoucanhandletheeventsforthesekeys separatelyandkeeptrackoftheminthatway,itsmucheasiertousethestaticModifiers propertyoftheKeyboardclass. TotestforaKeyboard.Modifier,youusebitwiselogic. if ((Keyboard.Modifiers & ModifierKeys.Control) == ModifierKeys.Control) { message += "You are holding the Control key."; }
Browser Integration
insomecasesyoullneedtocreateawebpagethatisntjustathinshell aroundaSilverlightapplication.Instead,youmaywanttoaddSilverlightcontenttoanexisting pageandallowtheHTMLandSilverlightportionsofyourpagetointeract. Thereareseveralreasonsyoumaychoosetoblendtheclassicbrowserworldwiththe managedSilverlightenvironment.Herearesomepossibilities: Compatibility: YoucantbesureyourvisitorswillhavetheSilverlightplugininstalled.If yourebuildingacorepartofyourwebsite,yourneedtoensurebroadcompatibility (withHTML)maytrumpyourdesiretousethelatestandgreatestuserinterfacefrills (withSilverlight).Inthissituation,youmaydecidetoincludeaSilverlightcontentregion toshownonessentialextrasalongsidethecriticalHTMLcontent. Legacy web pages: Ifyouhaveanexistingwebpagethatdoesexactlywhatyouwant,it maymakemoresensetoextenditwithabitofSilverlightpizzazzthantoreplaceit outright.Onceagain,thesolutionistocreateapagethatincludesbothHTMLand Silverlightcontent. Server-side features: Sometypesoftasksrequireserversidecode.Forexample, Silverlightisapoorfitfortasksthatneedtoaccessserverresourcesorrequirehigh security,whichiswhyitmakesfarmoresensetobuildasecurecheckoutprocesswitha serversideprogrammingframeworklikeASP.NET.ButyoucanstilluseSilverlightto displayadvertisements,videocontent,productvisualizations,andothervalueadded featuresinthesamepages.
Class
HtmlPage
Description
RepresentsthecurrentHTMLpage(wheretheSilverlight controlisplaced).TheHtmlPageclassisajumpingoffpoint formostoftheHTMLinteractionfeatures.Itprovides membersforexploringtheHTMLelementsonthepage(the Documentproperty),retrievingbrowserinformation(the BrowserInformationproperty),interactingwiththecurrent browserwindow(theWindowproperty),andregistering Silverlightmethodsthatyouwanttomakeavailableto
JavaScript(theRegisterCreatableType()and RegisterScriptableType()methods).
BrowserInformation
Providessomebasicinformationaboutthebrowserthats beingusedtorunyourapplication,includingthebrowser name,version,andoperatingsystem.Youcanretrievean instanceoftheBrowserInformationclassfromthe HtmlPage.BrowserInformationproperty. RepresentsacompleteHTMLdocument.Youcangetan instanceofHtmlDocumentthatrepresentsthecurrentHTML pagefromtheHtmlPage.Documentproperty.Youcanthenuse theHtmlDocumentobjecttoexplorethestructureandcontent ofthepage(asnestedlevelsofHtmlElementobjects). RepresentsanyHTMLelementonthepage.Youcanuse methodslikeSetAttribute()andSetProperty()tomanipulate thatelement.Usually,youlookupHtmlElementobjectsinan HtmlDocumentobject.
HtmlDocument
HtmlElement
HtmlWindow
HttpUtility
ScriptableTypeAttributeandAllowsyoutoexposetheclassesandmethodsinyour Silverlightapplication,sotheycanbecalledfromJavaScript codeintheHTMLpage. ScriptableMemberAttributeAllowsyoutoexposetheclassesandmethodsinyour Silverlightapplication,sotheycanbecalledfromJavaScript codeintheHTMLpage. ScriptObject RepresentsaJavaScriptfunctionthatsdefinedinthepage,and allowsyoutoinvokethefunctionfromyourSilverlight application.
system,anduseragentalongstringthatincludestechnicaldetailsaboutthebrowser(for example,inInternetExplorer,itlistsallthecurrentlyinstalledversionsofthe.NETFramework). YoucanalsousetheBooleanCookiesEnabledpropertytodetermineifthecurrentbrowser supportscookiesandhasthemenabled(inwhichcaseitstrue).Youcanthenreadorchange cookiesthroughtheHtmlPageclass. BrowserInformation b = HtmlPage.BrowserInformation; lblInfo.Text = "Name: " + b.Name; lblInfo.Text += "\nBrowser Version: " + b.BrowserVersion.ToString(); lblInfo.Text += "\nPlatform: " + b.Platform; lblInfo.Text += "\nCookies Enabled: " + b.CookiesEnabled; lblInfo.Text += "\nUser Agent: " + b.UserAgent;
Popup Windows
TheHtmlPageclassalsoprovidesaPopupWindow()methodthatallowsyoutoopenapopup windowtoshowanewwebpage.ThePopupWindow()methodisintendedforshowing advertisementsandcontentfromotherwebsites.Itsnotintendedasawaytoshowdifferent partsofthecurrentSilverlightapplication.(Ifyouwanttheabilitytoshowapopupwindow insideaSilverlightapplication,youneedtheChildWindowcontroldescribedinChapter7.) ThePopupWindow()methodisfairlyreliable,anddodgesmostpopupblockers (dependingontheuserssettings).However,italsohasafewquirks,andshouldneverberelied forcreatinganintegralpartofyourapplication.Instead,thepopupwindowcontentshouldbe anoptionalextra.Technically,thePopupWindow()methodworksbytriggeringaJavaScript window.open()call. HeresanexamplethatusesthePopupWindow()method.Notethatthiscodestests theIsPopupWindowAllowedpropertytoavoidpotentialerrors,aspopupwindowarenot supportedinallscenarios: if (HtmlPage.IsPopupWindowAllowed) { // Configure the popup window options. HtmlPopupWindowOptions options = new HtmlPopupWindowOptions(); options.Resizeable = true; // Show the popup window. // You pass in an absolute URI, an optional target frame, and the // HtmlPopupWindowOptions. HtmlPage.PopupWindow(new Uri(uriForAdvertisement),
null, options); } HerearetherulesandrestrictionsofSilverlightpopupwindows: TheydontworkiftheallowHtmlPopupWindowparameterissettofalseintheHTML entrypage.(SeetheSecuringHTMLInteroperabilitysectionattheendofthis chapter.) IfyourHTMLentrypageandSilverlightapplicationaredeployedondifferentdomains, popupwindowsarenotallowedunlesstheHTMLentrypageincludesthe allowHtmlPopupWindowparameterandexplicitlysetsittotrue. ThePopupWindow()canonlybecalledinresponsetoauserinitiatedclickonavisible areaoftheSilverlightapplication. ThePopupWindow()methodcanbecalledonlyonceperevent.Thismeansyoucant showmorethanonepopupwindowatonce. PopupwindowworkwiththedefaultsecuritysettingsinInternetExplorerandFirefox. However,theywontappearinSafari. WhencallingPopupWindow(),youmustsupplyanabsoluteURI.
Member
DocumentUri
Description
DocumentElementProvidesanHtmlElementobjectthatrepresentsthetoplevel<html> elementintheHTMLpage. Body ProvidesanHtmlElementobjectthatrepresentsthe<body>elementin theHTMLpage. ProvidesacollectionofallthecurrentHTTPcookies.Youcanreador setthevaluesinthesecookies.Cookiesprovideoneeasy,lowcostway totransferinformationfromserversideASP.NETcodetoclientside Silverlightcode.However,cookiesarentthebestapproachforstoring smallamountsofdataontheclientscomputerisolatedstorage, whichisdiscussedinChapter18,providesasimilarfeaturewithbetter compatibilityandprogrammingsupport. Returnstrueifthebrowserisidleorfalseifitsstilldownloadingthe page. CreateElement() CreatesanewHtmlElementobjecttorepresentadynamicallycreated HTMLelement,whichyoucantheninsertintothepage. AttachEvent() DetachEvent()
Cookies
IsReady
WhenyouhavetheHtmlDocumentobjectthatrepresentsthepage,youcanbrowse downthroughtheelementtree,startingatHtmlDocument.DocumentElementor HtmlDocument.Body.Tostepfromoneelementtoanother,youusetheChildrenproperty(to seetheelementsnestedinsidethecurrentelement)andtheParentproperty. exampleaSilverlightapplicationthatstartsatthetoplevel <html>elementandusesarecursivemethodtodrillthroughtheentirepage.Itdisplaysthe nameandIDofeachelement. Heresthecodethatcreatesthisdisplaywhenthepagefirstloads: private void Page_Loaded(object sender, RoutedEventArgs e) { // Start processing the top-level <html> element. HtmlElement element = HtmlPage.Document.DocumentElement; ProcessElement(element, 0); } private void ProcessElement(HtmlElement element, int indent) { // Ignore comments. if (element.TagName == "!") return; // Indent the element to help show different levels of nesting. lblElementTree.Text += new String(' ', indent * 4); // Display the tag name. lblElementTree.Text += "<" + element.TagName; // Only show the id attribute if it's set. if (element.Id != "") lblElementTree.Text += " id=\"" + element.Id + "\""; lblElementTree.Text += ">\n"; // Process all the elements nested inside the current element. foreach (HtmlElement childElement in element.Children) { ProcessElement(childElement, indent + 1); } } TheHtmlElementprovidesrelativelyfewproperties.AsidefromtheChildrenand Parentpropertiesthatallowyoutonavigatebetweenelements,italsoincludestheTagName andIddemonstratedshownhere,andaCssClasspropertythatindicatesthenameofthe cascadingstylesheet(CSS)stylethatssetthroughtheclassattributeandusedtoconfigurethe appearanceofthecurrentelement.
Method
AppendChild()
Description
InsertsanewHTMLelementasthelastnestedelement insidethecurrentelement.Tocreatetheelement,youmust firstusetheHtmlDocument.CreateElement()method.
RemoveChild() RemovesthespecifiedHtmlElementobject(whichyou supplyasanargument).ThisHtmlElementmustbeoneof thechildrenthatsnestedinthecurrentHtmlElement. Focus() Givesfocustothecurrentelementsoitreceiveskeyboard events. GetAttribute(),SetAttribute(), andRemoveAttribute() Letyouretrievethevalueofanyattributeintheelement,set thevalue(inwhichcasetheattributeisaddedifitdoesnt alreadyexist),orremovetheattributealtogether, respectively. GetStyleAttribute(), SetStyleAttribute(), RemoveStyleAttribute() LetyouretrieveavalueofaCSSstyleproperty,setthe value,orremovethestyleattributealtogether,respectively. (Asyounodoubtknow,CSSpropertiesarethemodernway toformatHTMLelements,andtheyletyoucontroldetails likefont,foregroundandbackgroundcolor,spacingand positioning,andborders.) GetProperty()andSetProperty()Allowyoutoretrieveorsetvaluesthataredefinedaspartof theHTMLDOM.Thesearethevaluesthatarecommonly manipulatedinJavaScriptcode.Forexample,youcan extractthetextcontentfromanelementusingthe innerHTMLproperty. AttachEvent()and DetachEvent() Connectanddisconnectaneventhandlerinyour SilverlightapplicationtoaJavaScripteventthatsraisedby anHTMLelement.
Forexample,imaginethatyouhavea<p>elementjustunderneathyourSilverlight contentregion(andyourSilverlightcontentregiondoesntfilltheentirebrowserwindow).You wanttomanipulatetheparagraphwithyourSilverlightapplication,soyouassignitauniqueID likethis: <p id="paragraph">...</p> YoucanretrieveanHtmlElementobjectthatrepresentsthisparagraphinany Silverlighteventhandler.Thefollowingcoderetrievestheparagraphandchangesthetext inside: HtmlElement element = HtmlPage.Document.GetElementById("paragraph"); element.SetProperty("innerHTML", "This HTML paragraph has been updated by Silverlight."); ThiscodeworksbycallingtheHtmlElement.SetProperty()methodandsettingthe innerHTMLproperty.LongtimeJavaScriptdeveloperswillrecognizeinnerHTMLasoneofthe fundamentalingredientsintheDOM. YoullnoticethatthetransitionbetweenSilverlightandtheHTMLDOMisntperfect. SilverlightdoesntincludeafullHTMLDOM,justalightweightversionthatstandardizesona basicHtmlElementclass.Tomanipulatethiselementinameaningfulway,youoftenneedto
When you set the innerHTML property, your text is interpreted as raw HTML. That means youre free to use nested elements, like this:
element.SetProperty("innerHTML", "This <b>word</b> is bold.");
If you want to use angle brackets that would otherwise be interpreted as special characters, you need to replace them with the < and > character entities, as shown here:
If you have a string with many characters that need to be escaped, or you dont want reduce the readability of your code with character entities, you can use the static HttpUtility.HtmlEncode() method to do the work:
element.SetProperty("innerHTML", HttpUtility.HtmlEncode("My favorite elements are <b>, <i>, <u>, and <p>."));
If you want to add extra spaces (rather than allow them to be collapsed to a single space character), you need to use the character entity for a nonbreaking space.
Event
Description
onchange
Occurswhentheuserchangesthevalueinaninputcontrol.Intextcontrols, thiseventfiresaftertheuserchangesfocustoanothercontrol. Occurswhentheuserclicksacontrol. Occurswhentheusermovesthemousepointeroveracontrol. Occurswhentheusermovesthemousepointerawayfromacontrol. Occurswhentheuserpressesakey. Occurswhentheuserreleasesapressedkey. Occurswhentheuserselectsaportionoftextinaninputcontrol. Occurswhenacontrolreceivesfocus. Occurswhenfocusleavesacontrol. Occurswhentheusercancelsanimagedownload. Occurswhenanimagecantbedownloaded(probablybecauseofanincorrect URL). Occurswhenanewpagefinishesdownloading. Occurswhenapageisunloaded.(ThistypicallyoccursafteranewURLhas beenenteredoralinkhasbeenclicked.Itfiresjustbeforethenewpageis downloaded.)
Onclick Onmouseover Onmouseout onkeydown Onkeyup onselect onfocus Onblur onabort onerror
onload onunload
Toattachyoureventhandler,youusetheHtmlElement.AttachEvent()method.You cancallthismethodatanypointanduseitwithexistingornewlycreatedelements.Heresan examplethatwatchesfortheonclickeventintheparagraph: element.AttachEvent("onclick", paragraph_Click); TheeventhandlerreceivesanHtmlEventArgsobjectthatprovidesafairbitof additionalinformation.Formouseevents,youcanchecktheexactcoordinatesofthemouse (relativetotheelementthatraisedtheevent)andthestateofdifferentmousebuttons. Inthisexample,theeventhandlerchangestheparagraphstextandbackgroundcolor: private void paragraph_Click(object sender, HtmlEventArgs e) { HtmlElement element = (HtmlElement)sender; element.SetProperty("innerHTML", "You clicked this HTML element, and Silverlight handled it."); element.SetStyleAttribute("background", "#00ff00"); } Thistechniqueachievesanimpressivefeat.UsingSilverlightasanintermediary,you canscriptanHTMLpagewithclientsideC#code,insteadofusingtheJavaScriptthatwould normallyberequired.
Code Interaction
aSilverlightapplicationcanreachintothebrowsertoperform navigationandmanipulateHTMLelements.Theoneweaknessofthisapproachisthatit createstightlyboundcodeinotherwords,aSilverlightapplicationthathashardcoded assumptionsabouttheHTMLelementsonthecurrentpageandtheiruniqueIDs.Changethese detailsintheHTMLpage,andtheSilverlightcodeforinteractingwiththemwontwork anymore. Onealternativethataddressesthisissueistoallowinteractionbetweencode,not elements.Forexample,yourSilverlightapplicationcanupdatethecontentoftheHTMLpage bycallingaJavaScriptmethodthatsinthepage.Essentially,theJavaScriptcodecreatesan extralayerofflexibilityinbetweentheSilverlightcodeandHTMLcontent.Thisway,ifthe HTMLelementsonthepageareeverchanged,theJavaScriptmethodcanbeupdatedtomatch atthesametimeandtheSilverlightapplicationwontneedtoberecompiled.Thesame interactioncanworkinthereversedirectionforexample,youcancreateJavaScriptcodethat callsaSilverlightmethodthatswritteninmanagedC#code.
Providedyoutakeallthesesteps,yourJavaScriptcodewillbeabletocallyour Silverlightmethodthroughthe<object>elementthatrepresentstheSilverlightcontentregion. However,tomakethistaskeasier,itsimportanttogivethe<object>elementauniqueID.By default,VisualStudiocreatesatestpagethatassignsanametothe<div>elementthatcontains the<object>element(silverlightControlHost),butitdoesntgiveanametothe<object> elementinside. <div id="silverlightControlHost"> <object data="data:application/x-silverlight," type="application/x-silverlight-2-b1" width="400" height="300" id="silverlightControl"> ... </object> <iframe style='visibility:hidden;height:0;width:0;border:0px'></iframe> </div> AfteryouvenamedtheSilverlightcontrol,yourereadytocreatethescriptable Silverlightmethod.ConsidertheexampleshowninFigure145.Here,aSilverlightregion(the areawiththegradientbackground)includesasingletextblock(left).UnderneathisanHTML paragraph.Whentheuserclickstheparagraph,aJavaScripteventhandlerspringsintoaction andcallsamethodintheSilverlightapplicationthatupdatesthetextblock(right). Tocreatethisexample,youneedthecustompageclassshownhere.Itincludesa singlescriptablemethod,whichisregisteredwhenthepageisfirstcreated: [ScriptableType()] public partial class ScriptableSilverlight: UserControl { public ScriptableSilverlight() { InitializeComponent(); HtmlPage.RegisterScriptableObject("Page", this); } [ScriptableMember()] public void ChangeText(string newText) { lbl.Text = newText; } } Whenregisteringascriptabletype,youneedtospecifyaJavaScriptobjectnameand passareferencetotheappropriateobject.Here,aninstanceoftheScriptableSilverlightclassis registeredwiththenamePage.ThistellsSilverlighttocreateapropertynamedPageinthe SilverlightcontrolontheJavaScriptpage.Thus,tocallthismethod,theJavaScriptcodeneeds tousethefindtheSilverlightcontrol,getitscontent,andthencallitsPage.ChangeText() method. Heresanexampleofafunctionthatdoesexactlythat: <script type="text/javascript"> function updateSilverlightText() { var control = document.getElementById("silverlightControl"); control.content.Page.ChangeText( "This TextBlock has been updated through JavaScript."); } </script> YoucantriggerthisJavaScriptmethodatanytime.Heresanexamplethatfiresitoff whenaparagraphisclicked: <p onclick="updateSilverlightText()">Click here to change the Silverlight
Tomakethiswork,youmustbeginwithastylethatspecifiesabsolutepositioningfor theSilverlightcontrol.Thisstylerulealsosetsthewidthandheightto0,sothecontroldoesnt appearinitially.(Youcouldusethevisibilitystylepropertytoaccomplishthesamething;butin thiscase,thewidthandheightaresetdynamicallytomatchtheSilverlightpagesize,soitmay aswellstartat0.) #silverlightControlHost { position: absolute; width: 0px; height: 0px; } TheSilverlightcontentregiondoesntappearuntiltheusermovesthemouseoverthe appropriateHTMLelement.Inthisexample,theelementisa<span>placedinablockoftext: <div> <p>This is an ordinary HTML page.</p> <p>The Silverlight control is in a hidden container.</p> <p>The hidden container is placed using absolute coordinates. When you move the mouse over the highlighted word <span id="target">here</span>, the Silverlight control will be dynamically positioned next to the highlighted word and displayed. </div> Thisspanisgivenayellowbackgroundthroughanotherstyle: #target { background-color: Yellow; } WhentheSilverlightpageloads,thecodefindsthetarget<span>elementandattaches aneventhandlertotheJavaScriptonmouseoverevent: private void Page_Loaded(object sender, RoutedEventArgs e) { HtmlElement target = HtmlPage.Document.GetElementById("target"); target.AttachEvent("onmouseover", element_MouseOver);
} Whentheusermovesthemouseovertheelement,theeventhandlerfindsitscurrent positionusingtheHTMLDOMpropertiesoffsetLeftandoffsetTop.ItthenplacestheSilverlight containerinanearbylocationusingtheleftandtopstyleproperties: private void element_MouseOver(object sender, HtmlEventArgs e) { // Get the current position of the <span>. HtmlElement target = HtmlPage.Document.GetElementById("target"); double targetLeft = Convert.ToDouble(target.GetProperty("offsetLeft")) - 20; double targetTop = Convert.ToDouble(target.GetProperty("offsetTop")) - 20; // Get the Silverlight container, and position it. HtmlElement silverlightControl = HtmlPage.Document.GetElementById("silverlightControlHost"); silverlightControl.SetStyleAttribute("left", targetLeft.ToString() + "px"); silverlightControl.SetStyleAttribute("top", targetTop.ToString() + "px"); // Resize the Silverlight container to match the actual page size. // This assumes the Silverlight user control has fixed values set for // Width and Height (in this case, they're set in the XAML markup). silverlightControl.SetStyleAttribute("width", this.Width + "px"); silverlightControl.SetStyleAttribute("height", this.Height + "px"); } TheSilverlightcontentregionishiddenusinganordinarySilverlighteventhandler thatreactstotheMouseLeaveeventofthetoplevelusercontrol: private void Page_MouseLeave(object sender, MouseEventArgs e) { HtmlElement silverlightControl = HtmlPage.Document.GetElementById("silverlightControlHost"); silverlightControl.SetStyleAttribute("width", "0px"); silverlightControl.SetStyleAttribute("height", "0px"); } Togivethisexampleabitmorepizzazz,youcanuseananimationtofadethe Silverlightcontentregionintoview.Heresanexamplethatalternatestheopacityofthetoplevel containerfrom0to1overhalfasecond: <UserControl.Resources> <Storyboard x:Name="fadeUp"> <DoubleAnimation Storyboard.TargetName="LayoutRoot" Storyboard.TargetProperty="Opacity" From="0" To="1" Duration="0:0:0.5" /> </Storyboard> </UserControl.Resources> Tousethisanimation,youneedtoaddthisstatementtotheendofthe element_MouseOver()eventhandler: fadeUp.Begin();
tocalltheHtmlPage.PopupWindow()method.)Ordinarily,enableHtmlAccessissetto true,andyoumustexplicitlyswitchitoff.However,ifyourSilverlightapplicationis hostedonadifferentdomainthanyourHTMLentrypage,enableHtmlAccessissetto falsebydefault,andyoucanchoosetoexplicitlyswitchitontoallowHTML interoperability. allowHtmlPopupwindow: Whenfalse,theSilverlightapplicationcantusethe HtmlPage.PopupWindow()methodtoshowapopupwindow.Bydefault,this parameteristruewhenthetestpageandSilverlightapplicationaredeployedtogether, andfalsewhentheSilverlightapplicationishostedonadifferentdomain. HeresanexamplethatsetsenableHtmlAccessandallowHtmlPopupwindow: <div id="silverlightControlHost"> <object data="data:application/x-silverlight-2," type="application/x-silverlight-2" width="100%" height="100%"> <param name="enableHtmlAccess" value="false" /> <param name="allowHtmlPopupwindow" value="false" /> ... </object> <iframe style='visibility:hidden;height:0;width:0;border:0px'></iframe> </div> SilverlightalsogivesyoutheabilitytoprotectyourSilverlightapplicationfrom JavaScriptcode.Butfirst,itsimportanttorememberthatJavaScriptcodecantinteractwith yourapplicationunlessyouexplicitlydesignatesomeclassesandmethodsasscriptable (which youlearnedtodointheCodeInteractionsectionofthischapter).Onceyoudesignatea methodasscriptable,itwillalwaysbeavailabletotheHTMLentrypage,assumingboththe HTMLentrypageandyourSilverlightapplicationaredeployedtogether. However,SilverlightsfarstricteriftheHTMLentrypageandSilverlightapplicationare hostedondifferentdomains.Inthiscase,theHTMLpagewillnotbeallowedtoaccesstoyour scriptableclassesandmethods.Optionally,youcanoverridethisbehaviorandensurethat scriptablemembersareavailabletoanyHTMLpagebysetting ExternalCallersFromCrossDomainattributeintheapplicationmanifestfileAppManifest.xml, asshownhere: <Deployment xmlns="http://schemas.microsoft.com/client/2007/deployment" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" ExternalCallersFromsCrossDomain="ScriptableOnly" ...> <Deployment.Parts> ... </Deployment.Parts> </Deployment> Usethisoptionwithcaution.Itsentirelypossibleforanunknownindividualtocreate anHTMLpageonanotherserverthathostsyourSilverlightapplicationwithoutyour knowledgeorconsent.Ifyouallowcrossdomainaccesstoyourscriptablemethods,anyone willbeabletocallthesemethodsatanytime,andwithanyinformation.
Application Properties
AlongwiththestaticCurrentproperty,theApplicationclassalsoprovidesseveralmore members,
Member
Host
Description
Thispropertyletsyouinteractwiththebrowserand,throughit,the restoftheHTMLcontentonthewebpage ThispropertyprovidesaccesstothecollectionofXAMLresources thataredeclaredinApp.xaml, Thispropertyprovidesaccesstotherootvisualforyourapplication typically,theusercontrolthatscreatedwhenyourapplicationfirst starts.Onceset,therootvisualcantbechanged,althoughyoucan manipulatethecontentintherootvisualtochangewhatsdisplayed inthepage.Forexample,ifitstheGridcontrol,youcanremoveone ormoreofitscurrentchildrenandinsertnewcontrolsintheirplace.
Resources
RootVisual
InstallState
InstallStateprovidesavaluefromthe InstallStateenumerationthatindicateswhetherthecurrent
Application Events
1.TheuserrequeststheHTMLentrypageinthebrowser. 2.ThebrowserloadstheSilverlightplugin.ItthendownloadstheXAPfilethatcontains yourapplication. 3.TheSilverlightpluginreadstheAppManifest.xmlfilefromtheXAPtofindoutwhat assembliesyourapplicationuses.ItcreatestheSilverlightruntimeenvironmentand thenloadsyourapplicationassembly 4.TheSilverlightplugincreatesaninstanceofyourcustomapplicationclass 5.ThedefaultconstructoroftheapplicationclassraisestheStartupevent. 6.YourapplicationhandlestheStartupeventandcreatestherootvisualobjectforyour application. Fromthispointon,yourpagecodetakesover,untilitencountersanunhandlederror (UnhandledException)orfinallyends(Exit).TheseeventsStartup,UnhandledException,and ExitarethecoreeventsthattheApplicationclassprovides. IfyoulookatthecontentsoftheApp.xaml.csfile,youllseethatinVisualStudio,the applicationconstructorcontainssomepregeneratedcode.Thiscodeattachesaneventhandler tothethreeapplicationevents: public App() { this.Startup += this.Application_Startup; this.Exit += this.Application_Exit; this.UnhandledException += this.Application_UnhandledException; InitializeComponent(); } Aswiththepageandelementeventsyouveconsideredinearlierchapters,thereare twowaystoattachapplicationeventhandlers.Insteadofusingcode,youcanaddevent attributestotheXAMLmarkup,asshownhere: <Application ... x:Class="SilverlightApplication1.App" Startup="Application_Startup" > Theresnoreasontopreferoneapproachtotheother.Bydefault,VisualStudiouses thecodeapproachshownfirst. Inthefollowingsections,youllseehowyoucanwritecodethatplugsintothe applicationevents.
Application Startup
Bydefault,theApplication_Startupmethodcreatesthefirstpageandassignsittothe Application.RootVisualproperty,ensuringthatitbecomesthetoplevelapplicationelement thevisualcoreofyourapplication: private void Application_Startup(object sender, StartupEventArgs e)
Initialization Parameters
TheStartupeventpassesinaStartupEventArgsobject,whichincludesoneadditionaldetail: initializationparameters.ThismechanismallowsthepagethathoststheSilverlightcontrolto passincustominformation.IfyouwanttheSilverlightapplicationtovarybasedonuserspecific orsessionspecificinformation.Forexample,youcancustomizetheapplicationsview dependingonwhetherusersareenteringfromthecustomerpageortheemployeepage.Or, youmaychoosetoloaddifferentinformationbasedontheproducttheuseriscurrently viewing.JustrememberthattheinitializationparameterscomefromthetagsoftheHTML entrypage,andamalicioususercanalterthem. Forexample,imagineyouwanttopassaViewModeparameterthathastwopossible values,CustomerorEmployee,asrepresentedbythisenumeration: public enum ViewMode { Customer, Employee } Youneedtochangeavarietyofdetailsbasedonthisinformation,soitmakessenseto storeitsomewherethatsaccessiblethroughoutyourapplication.Thelogicalchoiceistoadda propertytoyourcustomapplicationclass,likethis: private ViewMode viewMode = ViewMode.Customer; public ViewMode ViewMode { get { return viewMode; } } Thispropertydefaultstocustomerview,soitneedstobechangedonlyifthewebpage specificallyrequeststheemployeeview. TopasstheparameterintoyourSilverlightapplication,youneedtoadda<param> elementtothemarkupintheSilverlightcontentregion.Thisparametermusthavethename initParams.Itsvalueisacommaseparatedlistofnamevaluepairsthatsetyourcustom parameters.Forexample,toaddaparameternamedviewMode,youaddthefollowingline (showninbold)toyourmarkup: <div id="silverlightControlHost"> <object data="data:application/x-silverlight," type="application/x-silverlight-2" width="100%" height="100%"> <param name="source" value="TransparentSilverlight.xap"/> <param name="onerror" value="onSilverlightError" /> <param name="background" value="white" /> <param name="initParams" value="viewMode=Customer" /> ... </object> <iframe style='visibility:hidden;height:0;width:0;border:0px'></iframe> </div> Then,youcanretrievethisfromtheStartupEventArgs.InitParamscollection.However, youmustcheckfirstthatitexists: private void Application_Startup(object sender, StartupEventArgs e) { // Take the view mode setting, and store in an application property. if (e.InitParams.ContainsKey("viewMode"))
{ string view = e.InitParams["viewMode"]; if (view == ""Employee"") this.viewMode = ViewMode.Employee; } // Create the root page. this.RootVisual = new Page(); } Ifyouhavemanypossiblevalues,youcanusethefollowingleanercodetoconvertthe stringtothecorrespondingenumerationvalue,assumingthetextmatchesexactly: string view = e.InitParams["viewMode"]; try { this.viewMode = (ViewMode)Enum.Parse(typeof(ViewMode), view, true); } catch { } Now,differentpagesarefreetopassinadifferentparameterandlaunchyour applicationwithdifferentviewsettings.Becausetheviewinformationisstoredasapropertyin thecustomapplicationclass(namedApp),youcanretrieveitanywhereinyourapplication: lblViewMode.Text = "Current view mode: " + ((App)Application.Current).ViewMode.ToString(); Ifyouhavemorethanoneinitializationparameter,passthemallinonecommadelimited string.Initializationvaluesshouldbemadeupofalphanumericcharacters.Theres currentlynosupportforescapingspecialcharacterslikecommasinparametervalues: <param name="initParams" value="startPage=Page1,viewMode=Customer" /> Now,theeventhandlerfortheStartupeventcanretrievetheStartPagevalueanduseit tochoosetheapplicationsrootpage.Youcanloadthecorrectpageusingablockofconditional logicthatdistinguishesbetweentheavailablechoices,oryoucanwriteamoregeneralsolution thatusesreflectiontoattempttocreatetheclasswiththerequestedname,asshownhere: UserControl startPage = null; if (e.InitParams.ContainsKey("startPage")) { string startPageName = e.InitParams["startPage"]; try { // Create an instance of the page. Type type = this.GetType(); Assembly assembly = type.Assembly; startPage = (UserControl)assembly.CreateInstance( type.Namespace + "." + startPageName); } catch { startPage = null; } } // If no parameter was supplied or the class couldn't be created, use a default. if (startPage == null) startPage = new MenuPage(); this.RootVisual = startPage;
Application Shutdown
Atsomepoint,yourSilverlightapplicationends.Mostcommonly,thisoccurswhentheuser surfstoanotherpageinthewebbrowserorclosesthebrowserwindow.Italsooccursifthe usersrefreshesthepage(effectivelyabandoningthecurrentinstanceoftheapplicationand launchinganewone),ifthepagerunsJavaScriptcodethatremovestheSilverlightcontent
Unhandled Exceptions
Ifyourapplicationencountersanerrorthatisnthandled,itwillend,andtheSilverlightcontentregion willreverttoablankspace.IfyouveincludedJavaScriptcodethatreactstopotentialerrorsfromthe Silverlight plugin,thatcodewillrun.Otherwise,youwontreceiveanyindicationabouttheerrorthats justoccurred. TheApplication.UnhandledExceptioneventgivesyoualastditchchancetorespond toanexceptionbeforeitreachestheSilverlightpluginandterminatesyourapplication.This codeisnotablydifferentthantheJavaScripterrorhandlingcodethatyoumayaddtothepage, becauseithastheabilitytomarkanexceptionashandled.Doingsoeffectivelyneutralizesthe exception,preventingitfromrisingtothepluginandendingyourapplication. Heresanexamplethatcheckstheexceptiontypeanddecideswhethertoallowthe applicationtocontinue: public void Application_UnhandledException(object sender, ApplicationUnhandledExceptionEventArgs e) { if (e.ExceptionObject is FileNotFoundException) { // Suppress the exception and allow the application to continue. e.Handled = true; } } Ideally,anexceptionlikethisshouldbehandledclosertowhereitoccursfor example,inyourpagecode,whenyoureperformingataskthatmayresultina FileNotFoundException.Applicationlevelerrorhandlingisntideal,becauseitsdifficultto identifytheoriginalprocessthatcausedtheproblemanditsawkwardtonotifytheuserabout whatwentwrong.Butapplicationlevelerrorhandlingdoesoccasionallyofferasimplerand morestreamlinedwaytohandlecertainscenariosforexample,whenaparticulartypeof exceptioncropsupinnumerousplaces. Afteryouveneutralizedtheerror,itmakessensetonotifytheuser.Oneoptionisto callacustommethodinyourrootvisual.Forexample,thiscodecallsacustomReportError() methodintheMainPageclass,whichistherootvisualforthisapplication: MainPage rootPage = (MainPage)this.RootVisual; rootPage.ReportError(e.ExceptionObject); NowtheMainPage.ReportError()methodcanexaminetheexceptionobjectand displaytheappropriatemessageinanelementonthepage. Inanefforttomakeyourapplicationsalittlemoreresilient,VisualStudioaddsabitof boilerplateerrorhandlingcodetoeverynewSilverlightapplication.Thiscodecheckswhether adebuggeriscurrentlyattached(whichindicatesthattheapplicationisrunningintheVisual Studiodebugenvironment).Iftheresnodebugger,thecodehandlestheerror(renderingit harmless)andusestheHTMLinteroperabilityfeaturesyoulllearnaboutinChapter14toraise aJavaScripterrorinitsplace.Herestheslightlysimplifiedcodethatshowshowtheprocess works: public void Application_UnhandledException(object sender, ApplicationUnhandledExceptionEventArgs e)
{ if (!System.Diagnostics.Debugger.IsAttached) { // Suppress the exception and allow the application to continue. e.Handled = true; try { // Build an error message. string errorMsg = e.ExceptionObject.Message + e.ExceptionObject.StackTrace; errorMsg = errorMsg.Replace('"', '\'').Replace("\r\n", @"\n"); // Use the Window.Eval() method to run a line of JavaScript code that // will raise an error with the error message. System.Windows.Browser.HtmlPage.Window.Eval( "throw new Error(\"Unhandled Error in Silverlight 2 Application " + errorMsg + "\");"); } catch {} } }
Testing a custom splash screen requires some work. Ordinarily, you dont see the splash screen during testing because the application is sent to the browser too quickly. To slow down your application enough to see the splash screen, you need to first ensure that youre using an ASP.NET test website, which ensures that your Silverlight application is hosted by Visual Studio test web server (as described in Chapter 1). Then, you need to add multiple large resource files to your Silverlight project say, a handful of MP3 filesand set the build action of each one to Resource so its added to the XAP file. Another trick is to temporarily remove the line of code in the Application_Startup() method that sets the root visual for your application. This way, after your application has been completely downloaded, it wont display anything. Instead, the splash screen will remain visible, displaying a progress percentage of 100%.
TocreatetheexampleshowninFigure64,beginbycreatinganewSilverlightproject withanASP.NETtestwebsite,asdescribedinChapter1.Then,addanewXAMLfiletoyour ASP.NETwebsite(nottheSilverlightproject).Todoso,selecttheASP.NETwebsiteinthe
HerestheXAMLforthesplashscreenshowninFigure64.ItincludesaGridwitha TextBlockandtwoRectangleelements.(Rectangleisashapedrawingelementyoulllearn aboutinChapter8.)Thefirstrectanglepaintsthebackgroundoftheprogressbar,andthe secondpaintstheforeground.ThetwoRectangleobjectsareplacedtogetherinasinglecelled gridsothatonerectangleissuperimposedovertheother: <Grid xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"> <StackPanel VerticalAlignment="Center"> <Grid> <Rectangle x:Name="progressBarBackground" Fill="White" Stroke="Black" StrokeThickness="1" Height="30" Width="200"></Rectangle> <Rectangle x:Name="progressBar" Fill="Yellow" Height="28" Width="0"> </Rectangle> </Grid> <TextBlock x:Name="progressText" HorizontalAlignment="Center" Text="0% downloaded ..."></TextBlock>
</StackPanel> </Grid> Next,youneedtoaddaJavaScriptfunctiontoyourHTMLentrypageorASP.NETtest page.(Ifyouplantouseboth,placetheJavaScriptfunctioninaseparatefileandthenlinktoit inbothfiles,usingthesourceattributeofthescriptblock.)TheJavaScriptcodecanlookup namedelementsonthepageusingthesender.findName()methodandmanipulatetheir properties.ItcanalsodeterminethecurrentprogressusingtheeventArgs.progressproperty.In thisexample,theeventhandlingcodeupdatesthetextandwidenstheprogressbarbasedon thecurrentprogresspercentage: <script type="text/javascript"> function onSourceDownloadProgressChanged(sender, eventArgs) { sender.findName("progressText").Text = Math.round((eventArgs.progress * 100)) + "% downloaded ..."; sender.findName("progressBar").Width = eventArgs.progress * sender.findName("progressBarBackground").Width; } </script> Tousethissplashscreen,youneedtoaddthesplashscreensourceparameterto identifyyourXAMLsplashscreenandtheonsourcedownloadprogresschangedparameterto hookupyourJavaScripteventhandler.Ifyouwanttoreactwhenthedownloadisfinished,you canhookupadifferentJavaScripteventhandlerusingtheonsourcedownloadcomplete parameter: <object data="data:application/x-silverlight," type="application/x-silverlight-2" width="100%" height="100%"> <param name="source" value="ClientBin/SplashScreen.xap"/> <param name="onerror" value="onSilverlightError" /> <param name="background" value="white" /> <param name="splashscreensource" value="SplashScreen.xaml" /> <param name="onsourcedownloadprogresschanged" value="onSourceDownloadProgressChanged" /> ... </object> Ifyouwantmoreflexibilitytocreateaneyecatchingsplashscreen,youneedtousea completelydifferenttechnique.First,makeyourapplicationassmallaspossible.Moveits functionalitytoclasslibraryassemblies,andplacelargeresources(likegraphicsandvideos)in separatefilesorinseparateclasslibraryassemblies.Nowthatyourapplicationisstripped downtoahollowshell,itcanbedownloadedquickly.Afteritsdownloaded,yourapplication canshowitsfancypreloaderandstarttherealworkprogrammaticallydownloadingthe resourcesandassembliesitneedstofunction.