Вы находитесь на странице: 1из 47

XAML

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

8 <Grid x:Name="LayoutRoot"> 9 </Grid> 10 </UserControl>

ThenamespaceinformationallowstheXAMLparsertofindtherightclass.For example,whenitlooksattheUserControlandGridelements,itseesthattheyareplacedin the defaulthttp://schemas.microsoft.com/winfx/2006/xaml/presentationnamespace.Itthen searchesthecorrespondingSilverlightnamespaces,untilitfindsthematchingclasses System.Windows.UserControlandSystem.Windows.Controls.Grid.

Core Silverlight Namespaces


Thefirsttwonamespacesarethemostimportant.Youllneedthemtoaccessessentialpartsof theSilverlightruntime: http://schemas.microsoft.com/winfx/2006/xaml/presentation isthecoreSilverlight namespace.ItencompassesalltheessentialSilverlightclasses,includingthe UserControlandGrid.Ordinarily,thisnamespaceisdeclaredwithoutanamespace prefix,soitbecomesthedefaultnamespacefortheentiredocument.Inother words,everyelementisautomaticallyplacedinthisnamespaceunlessyouspecify otherwise. http://schemas.microsoft.com/winfx/2006/xaml istheXAMLnamespace.Itincludes variousXAMLutilityfeaturesthatallowyoutoinfluencehowyourdocumentis interpreted.Thisnamespaceismappedtotheprefixx.Thatmeansyoucanapplyit byplacingthenamespaceprefixbeforethenameofanXMLelementorattribute (asin<x:ElementName>andx:Class="ClassName").

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"

XML NAMESPACES AND SILVERLIGHT NAMESPACES


The XML namespace name doesnt correspond to a single Silverlight namespace. Instead, all the Silverlight namespaces share the same XML namespace. There are a couple of reasons the creators of XAML chose this design. By convention, XML namespaces are often URIs (as they are here). These URIs look like they point to a location on the Web, but they dont. The URI format is used because it makes it unlikely that different organizations will inadvertently create different XML-based languages with the same namespace.

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>

The Code-Behind Class


XAMLallowsyoutoconstructauserinterface,butinordertomakeafunctioningapplication, youneedawaytoconnecttheeventhandlersthatcontainyourapplicationcode.XAMLmakes thiseasyusingtheClassattributethatsshownhere: 1 <UserControl x:Class="SilverlightApplication1.MainPage" Thex namespaceprefixplacestheClassattributeintheXAMLnamespace,which meansthisisamoregeneralpartoftheXAMLlanguage,notaspecificSilverlightingredient. Infact,theClassattributetellstheSilverlightparsertogenerateanewclasswiththe specifiedname.ThatclassderivesfromtheclassthatsnamedbytheXMLelement.Inother words,thisexamplecreatesanewclassnamedSilverlightProject1.MainPage,whichderives fromtheUserControlclass.Theautomaticallygeneratedportionofthisclassismergedwiththe codeyouvesuppliedinthecodebehindfile. Usually,everyXAMLfilewillhaveacorrespondingcodebehindclasswithclientside C#code.VisualStudiocreatesacodebehindclassfortheMainPage.xamlfilenamed MainPage.xaml.cs.HereswhatyoullseeintheMainPage.xaml.csfile: using System; using System.Collections.Generic; using System.Linq; using System.Net; using System.Windows; using System.Windows.Controls; using System.Windows.Documents; using System.Windows.Input; using System.Windows.Media; using System.Windows.Media.Animation; using System.Windows.Shapes; namespace SilverlightApplication1 { public partial class MainPage : UserControl { public MainPage() { InitializeComponent(); } } } Currently,theMainPageclasscodedoesntincludeanyrealfunctionality.However,it doesincludeoneimportantdetailthedefaultconstructor,whichcallsInitializeComponent() whenyoucreateaninstanceoftheclass.Thisparsesyourmarkup,createsthecorresponding objects,setstheirproperties,andattachesanyeventhandlersyouvedefined.

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.

Properties and Events in XAML


Theeightballpageincludesfourelements:aGrid(themostcommontoolfor arranginglayoutinSilverlight),twoTextBoxobjects,andaButton.Themarkupthatsrequired toarrangeandconfiguretheseelementsissignificantlylongerthanthepreviousexamples. Heresanabbreviatedlistingthatreplacessomeofthedetailswithanellipsis(...)toexposethe overallstructure: <UserControl x:Class="EightBall.MainPage" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Width="400" Height="300"> <Grid x:Name="grid1"> <Grid.Background> ... </Grid.Background> <Grid.RowDefinitions> ... </Grid.RowDefinitions> <TextBox x:Name="txtQuestion" ... > </TextBox> <Button x:Name="cmdAnswer" ... > </Button> <TextBox x:Name="txtAnswer" ... > </TextBox> </Grid> </UserControl>

Simple Properties and Type Converters


Asyouvealreadyseen,theattributesofanXMLelementsetthepropertiesofthe correspondingSilverlightobject.Forexample,thetextboxesintheeightballexampleconfigure thealignment,margin,andfont: <TextBox x:Name="txtQuestion" VerticalAlignment="Stretch" HorizontalAlignment="Stretch" FontFamily="Verdana" FontSize="24" Foreground="Green" ... > Inorderforthistowork,theSystem.Windows.Controls.TextBoxclassmustprovidethe followingproperties:VerticalAlignment,HorizontalAlignment,FontFamily,FontSize,and Foreground.Youlllearnthespecificmeaningforeachofthesepropertiesinthefollowing chapters. Tomakethepropertysystemwork,theXAMLparserneedstoperformabitmorework

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 and Routed Events


BothoftheseconceptsfirstappearedinSilverlightsbigbrothertechnology,WPF. Theycameasquiteasurprisetomostdevelopersafterall,fewexpectedauserinterface technologytoretoolcorepartsof.NETsobjectabstraction.However,WPFschangeswerent designedtoimprove.NETbuttosupportkeyWPFfeatures.Thenewpropertymodelallowed WPFelementstoplugintoservicessuchasdatabinding,animation,andstyles.Thenewevent modelallowedWPFtoadoptalayeredcontentmodel(asdescribedinthenextchapter) withouthorriblycomplicatingthetaskofrespondingtouseractionslikemouseclicksandkey presses.

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.

Defining and Registering a Dependency Property


Youllspendmuchmoretimeusingdependencypropertiesthancreatingthem.However,there arestillmanyreasonsthatyoullneedtocreateyourowndependencyproperties.Obviously, theyreakeyingredientifyouredesigningacustomSilverlightelement.Theyrealsorequired insomecasesifyouwanttoadddatabinding,animation,oranotherSilverlightfeaturetoa portionofcodethatwouldntotherwisesupportit. Thefirststepistodefineanobjectthatrepresents yourproperty.Thisisaninstanceof theDependencyPropertyclass(whichisfoundintheSystem.Windowsnamespace).The informationaboutyourpropertyneedstobeavailableallthetime.Forthatreason,your DependencyPropertyobjectmustbedefinedasastaticfieldintheassociatedclass. Forexample,considertheFrameworkElementclassfromwhichallSilverlight elementsinherit.FrameworkElementdefinesaMargindependencypropertythatallelements share.Itsdefinedlikethis: public class FrameworkElement: UIElement { public static readonly DependencyProperty MarginProperty; ... } Byconvention,thefieldthatdefinesadependencypropertyhasthenameofthe ordinaryproperty,plusthewordProperty attheend. DefiningtheDependencyPropertyobjectisjustthefirststep.Inorderforittobecome usable,youneedtoregisteryourdependencypropertywithSilverlight.Thisstepneedstobe completedbeforeanycodeusestheproperty,soitmustbeperformedinastaticconstructorfor theassociatedclass. SilverlightensuresthatDependencyPropertyobjectscantbeinstantiateddirectly, becausetheDependencyPropertyclasshasnopublicconstructor.Instead,a DependencyPropertyinstancecanbecreatedonlyusingthestatic DependencyProperty.Register()method.SilverlightalsoensuresthatDependencyProperty objectscantbechangedaftertheyrecreated,becauseallDependencyPropertymembersare readonly.Instead,theirvaluesmustbesuppliedasargumentstotheRegister()method. ThefollowingcodeshowsanexampleofhowaDependencyPropertycanbecreated. Here,theFrameworkElementclassusesastaticconstructortoinitializetheMarginProperty: static FrameworkElement() { MarginProperty = DependencyProperty.Register("Margin", typeof(Thickness), typeof(FrameworkElement), null); ... } TheDependencyProperty.Register()methodacceptsthefollowingarguments: Thepropertyname(Margininthisexample) Thedatatypeusedbytheproperty(theThicknessstructureinthisexample) Thetypethatownsthisproperty(theFrameworkElementclassinthisexample) APropertyMetadataobjectthatprovidesadditionalinformation.Currently,Silverlight usesthePropertyMetadatatostorejustoptionalpiecesofinformation:adefaultvalue

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,

Dynamic Value Resolution


Asyouvealreadylearned,dependencypropertiesdependonmultipledifferentservices,called propertyproviders.Todeterminethecurrentvalueofaproperty,Silverlighthastodecide whichonetakesprecedence.Thisprocessiscalleddynamic value resolution. Whenevaluatingaproperty,Silverlightconsidersthefollowingfactors,arrangedfrom highesttolowestprecedence: 1.Animations: Ifananimationiscurrentlyrunning,andthatanimationischangingthe propertyvalue,Silverlightusestheanimatedvalue. 2.Local value: IfyouveexplicitlysetavalueinXAMLorincode,Silverlightusesthelocal value.Remember,youcansetavalueusingtheSetValue()methodortheproperty wrapper.Ifyousetapropertyusingaresource(Chapter2)ordatabinding(Chapter16), itsconsideredtobealocallysetvalue. 3.Styles: Silverlightstyles(Chapter12)allowyoutoconfiguremultiplecontrolswithone rule.Ifyouvesetastylethatappliestothiscontrol,itcomesintoplaynow. 4.Property value inheritance.:Silverlightusesproperty value inheritance withasmallsetof controlproperties,includingForeground,FontFamily,FontSize,FontStretch,FontStyle, andFontWeight.Thatmeansifyousetthesepropertiesinahigherlevelcontainer(likea

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.

The WrapBreakPanel Example


Nowthatyouunderstandthetheorybehinddependencyproperties,itstimetogroundyour knowledgeinarealisticexample. InChapter3,youlearnedhowtocreatecustompanelsthatusedifferentlayoutlogicto getexactlytheeffectyouwant.Forexample,youtookalookatacustomUniformGridpanel considerspartofadifferentcustomlayoutpanel,whichiscalledtheWrapBreakPanel.Hereis itsclassdeclaration: public class WrapBreakPanel : System.Windows.Controls.Panel { ... } Ordinarily,theWrapBreakPanelbehavesliketheWrapPanel(althoughitdoesnt inheritdirectlyfromWrapPanel,anditslayoutlogiciswrittenfromscratch).Likethe WrapPanel,theWrapBreakPanellaysoutitschildrenoneaftertheother,movingtothenext lineoncethewidthinthecurrentlineisusedup.However,theWrapBreakPaneladdsanew featurethattheWrapPaneldoesntofferitallowsyoutoforceanimmediatelinebreak whereveryouwant,simplybyusinganattachedproperty. BecausetheWrapBreakPanelisaSilverlightelement,itspropertiesshouldalmost alwaysbedependencypropertiessoyouhavetheflexibilitytousethemwithotherSilverlight featureslikedatabindingandanimation.Forexample,itmakessensetogivethe WrapBreakPanelanOrientationpropertylikeitsrelative,thebasicWrapPanel.Thatway,you couldsupportdisplaysthatneedtoflowelementsintomultiplecolumns.Heresthecodeyou needtoaddtotheWrapBreakPanelclasstodefineanOrientationpropertythatusesthedata typeSystem.Windows.Controls.Orientation: public static readonly DependencyProperty OrientationProperty = DependencyProperty.Register("Orientation", typeof(Orientation), typeof(WrapBreakPanel), new PropertyMetadata(Orientation.Horizontal)); Thiscodeusesoneminortimesaver.RatherthandefinetheDependencyPropertyand registeritwithcodeinastaticconstructor,thisdefinitiontakescareofthedefinitionand registration(andthecompiledcodedoesntchange).Italsosetsthedefaultvalueto Orientation.Horizontal. Next,youneedtoaddthepropertywrapper,whichisperfectlystraightforward: public Orientation Orientation { get { return (Orientation)GetValue(OrientationProperty); } set { SetValue(OrientationProperty, value); } } WhenusingtheWrapBreakPanelinaSilverlightpage,youcansettheOrientation propertyasyousetanyotherproperty: <local:WrapBreakPanel Margin="5" Orientation="Vertical"> ... </local:WrapBreakPanel>

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.

The Core Element Events

Elementsinherittheirbasicsetofeventsfromtwocoreclasses:UIElementand FrameworkElement.AsFigure42shows,allSilverlightelementsderivefromtheseclasses. TheUIElementclassdefinesthemostimportanteventsforhandlinguserinputand theonlyeventsthatuseeventbubbling.

Event

KeyDownYes KeyUp Yes

Bubbles

Occurswhenakeyispressed. Occurswhenakeyisreleased.

Description

GotFocusYesOccurswhenthefocuschangestothiselement(whenthe userclicksitortabstoit).Theelementthathasfocusis thecontrolthatwillreceivekeyboardeventsfirst. LostFocusYes Occurswhenthefocusleavesthiselement.

Insomecases,higherleveleventsmayeffectivelyreplacesomeoftheUIElement events.Forexample,theButtonclassprovidesaClickeventthatstriggeredwhentheuser pressesandreleasesthemousebuttonorwhenthebuttonhasfocusandtheuserpressesthe spacebar.Thus,whenhandlingbuttonclicks,youshouldalwaysrespondtotheClickevent, notMouseLeftButtonDownorMouseLeftButtonUp(whichitsuppresses).Similarly,the TextBoxprovidesaTextChangedeventwhichfireswhenthetextischangedbyanymechanism inadditiontothebasicKeyDownandKeyUpevents.

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

Handled (Suppressed) Events


WhenthebuttoninFigure43receivestheMouseLeftButtonDownevent,ittakesanextrastep andmarkstheeventashandled.Thispreventstheeventfrombubblingupthecontrol hierarchyanyfurther.MostSilverlightcontrolsusethishandlingtechniquetosuppress MouseLeftButtonDownandMouseLeftButtonUpsotheycanreplacethemwithmoreuseful, higherleveleventslikeClick. TheImageclassusedtodisplaybitmaps TheTextBlockclassusedtoshowtext TheMediaElementclassusedtodisplayvideo Theshapeclassesusedfor2Ddrawing(Line,Rectangle,Ellipse,Polygon,Polyline, Path) Thelayoutcontainersusedforarrangingelements(Canvas,StackPanel,andGrid)and theBorderclass Theseexceptionsallowyoutousetheseelementsincontentcontrolslikethebutton withoutanylimitations.Forexample,ifyouplaceaTextBlockinabutton,whenyouclickthe TextBlock,theMouseLeftButtonUpeventwillbubbleuptothebutton,whichwillthenfireits Clickevent.However,ifyoutakeacontrolthatisntintheprecedinglistandplaceitinsidethe buttonsay,alistbox,checkbox,oranotherbuttonyoullgetdifferentbehavior.Whenyou clickthatnestedelement,theMouseLeftButtonUpeventwontbubbletothecontaining button,andthebuttonwontregisteraclick.

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.

An Event Bubbling Example

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); }

The Mouse Wheel


Tocreatetheexample,youneedtwocontrolsyoufirstconsideredinChapter3the ScrollViewerandViewbox.TheViewboxpowersthemagnification,whiletheScrollViewer simplyallowstheusertoscrolloverthewholesurfaceoftheViewboxwhenitstoobigtofitin thebrowserwindow. <UserControl x:Class="RoutedEvents.MouseWheelZoom" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:toolkit= "clrnamespace:System.Windows.Controls;assembly=System.Windows.Controls.Toolkit" MouseWheel="Page_MouseWheel"> <ScrollViewer VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Auto"> <toolkit:Viewbox x:Name="viewbox" Height="250" Width="350"> <Grid Background="White" Height="250" Width="350"> ... </Grid> </toolkit:Viewbox> </ScrollViewer> </UserControl> NoticethatinitiallytheViewboxisgivenexactlythesamehardcodedsizeastheGrid inside.ThisensuresthattheViewboxdoesntneedtoperformanyinitialscalinginstead,the Gridisatitsnaturalsizewhentheapplicationfirststarts. Whentheuserturnsthemousewheel,aMouseWheeleventhandlerchecksthedelta andsimplyadjuststheWidthandHeightpropertiesoftheViewboxproportionately.This expandsorshrinkstheViewbox,andrescaleseverythinginside: private void Page_MouseWheel(object sender, MouseWheelEventArgs e) { // The Delta is in units of 120, so dividing by 120 gives // a scale factor of 1.09 (120/110). In other words, one // mouse wheel notch expands or shrinks the Viewbox by about 9%. double scalingFactor = (double)e.Delta / 110; // Check which way the wheel was turned. if (scalingFactor > 0) { // Expand the viewbox. viewbox.Width *= scalingFactor; viewbox.Height *= scalingFactor; } else { // Shrink the viewbox. viewbox.Width /= -scalingFactor; viewbox.Height /= -scalingFactor; }

Capturing the Mouse


Ordinarily,everytimeanelementreceivesamousebuttondownevent,itwillreceivea correspondingmousebuttonupeventshortlythereafter.However,thisisntalwaysthecase. Forexample,ifyouclickanelement,holddownthemouse,andthenmovethemousepointer offtheelement,theelementwontreceivethemouseupevent. Insomesituations,youmaywanttohaveanotificationofmouseupevents,evenif theyoccurafterthemousehasmovedoffyourelement.Todoso,youneedtocapture the mousebycallingtheMouseCapture()methodoftheappropriateelement definedbythebaseUIElementclass,soitssupportedbyallSilverlightelements). Whilethemousehasbeencapturedbyanelement,otherelementswontreceive mouseevents.Thatmeanstheuserwontbeabletoclickbuttonselsewhereinthepage,click insidetextboxes,andsoon.Mousecapturingissometimesusedtoimplementdraggableand resizableelements.

A Mouse Event Example


Youcanputallthesemouseinputconceptstogether(andlearnabitaboutdynamiccontrol creation)byreviewingasimpleexample. Figure46showsaSilverlightapplicationthatallowsyoutodrawsmallcirclesona Canvasandmovethemaround.EverytimeyouclicktheCanvas,aredcircleappears.Tomove acircle,yousimplyclickanddragittoanewposition.Whenyouclickacircle,itchangescolor fromredtogreen.Finally,whenyoureleaseyourcircle,itchangescolortoorange.Theresno limittohowmanycirclesyoucanaddorhowmanytimesyoucanmovethemaroundyour drawingsurface.

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.

Interacting with HTML Elements


SilverlightincludesasetofmanagedclassesthatreplicatetheHTMLdocumentobjectmodel (DOM)inmanagedcode.TheseclassesletyourSilverlightcodeinteractwiththeHTMLcontent onthesamepage.Dependingonthescenario,thisinteractionmayinvolvereadingacontrol value,updatingtext,oraddingnewHTMLelementstothepage. TheclassesyouneedtoperformthesefeatsarefoundintheSystem.Windows.Browser Namespace

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

Representsthebrowserwindow,andprovidesmethodsfor navigatingtoanewpageortoadifferentanchorinthecurrent page.YoucangetaninstanceofHtmlWindowthatholdsthe currentpagefromtheHtmlPage.Windowproperty. ProvidesstaticmethodsforafewcommonHTMLrelated tasks,includingHTMLencodinganddecoding(makingtext safefordisplayinawebpage)andURLencodinganddecoding (makingtextsafeforuseinaURLforexample,asaquery stringargument).

HttpUtility

ScriptableTypeAttributeandAllowsyoutoexposetheclassesandmethodsinyour Silverlightapplication,sotheycanbecalledfromJavaScript codeintheHTMLpage. ScriptableMemberAttributeAllowsyoutoexposetheclassesandmethodsinyour Silverlightapplication,sotheycanbecalledfromJavaScript codeintheHTMLpage. ScriptObject RepresentsaJavaScriptfunctionthatsdefinedinthepage,and allowsyoutoinvokethefunctionfromyourSilverlight application.

Getting Browser Information


Mostofthetime,youshouldntworryaboutthespecificbrowserthatsbeingusedtoaccess yourapplication.Afterall,oneofthekeyadvantagesofSilverlightisthatitsavesyoufromthe browsercompatibilityhasslesofordinarywebprogrammingandletsyouwritecodethat behavesinthesamewayineverysupportedenvironment.However,insomescenariosyou maychoosetotakeacloserlookatthebrowserforexample,whendiagnosinganunusual errorthatcanbebrowserrelated. ThebrowserinformationthatsavailableintheBrowserInformationclassisfairly modest.Youregivenfourstringpropertiesthatindicatethebrowsername,version,operating

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;

The HTML Window


SilverlightalsogivesyoualimitedabilitytocontrolthebrowserthroughtheHtmlWindowclass. Itprovidestwomethodsthatallowyoutotriggernavigation:Navigate()and NavigateToBookmark(). Navigate()sendsthebrowsertoanotherpage.Youcanuseanoverloadedversionof theNavigate()methodtospecifyatargetframe.WhenyouuseNavigate(),youabandonthe currentSilverlightapplication.ItsthesameasiftheuserhadtypedanewURLinthebrowsers addressbar. NavigateToBookmark()scrollstoaspecificbookmarkinthecurrentpage.Abookmark isan<a>elementwithanID(orname)butnotarget: <a id="myBookmark">...</a> Tonavigatetoabookmark,youaddthenumbersign(#)andbookmarknametothe endofyourURL: <a href="page.html#myBookmark">Jump to bookmark</a> YoucanretrievethebookmarkfromthecurrentbrowserURLatanytimeusingthe HtmlWindow.CurrentBookmarkproperty,whichistheonlypropertytheHtmlWindowclass includes. TheNavigateToBookmark()methodandCurrentBookmarkpropertyraisean interestingpossibility.Youcanuseabookmarktostoresomestateinformation.Becausethis stateinformationispartoftheURL,itspreservedinthebrowserhistory

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.

Inspecting the HTML Document


Tostartyourexploration,youuseoneoftwostaticpropertiesfromtheHtmlPageclass. ThePluginpropertyprovidesareferencetothe<object>elementthatrepresentstheSilverlight control,asanHtmlElementobject.TheDocumentpropertyprovidessomethingmore interesting:anHtmlDocumentobjectthatrepresentstheentirepage, Members of the HtmlDocument Class

Member

DocumentUri

ReturnstheURLofthecurrentdocumentasaUriobject. QueryStringReturnsthequerystringportionoftheURLasasinglelongstringthat youmustparse.

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

ConnectaneventhandlerinyourSilverlightapplicationtoaJavaScript eventthatsraisedbythedocument. Submit() Submitsthepagebypostingaformanditsdatabacktotheserver.This isusefulifyourehostingyourSilverlightcontrolinanASP.NETpage, becauseittriggersapostbackthatallowsserversidecodetorun.

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.

Manipulating an HTML Element


TheParentandChildrenpropertiesarenttheonlywaytotravelthroughanHtmlDocument object.YoucanalsosearchforanelementwithaspecificnameusingtheGetElementByID()or GetElementsByTagName()method. Methods of the HtmlElement Class

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

setanHTMLDOMproperty(suchasinnerHTMLinthepreviousexample)usingthe SetProperty()methodandsupplythenameofthepropertyasastring.Ifyouplantodoalotof workwithspecificHTMLelements,youmaywanttowraptheminhigherlevelcustomclasses (forexample,bycreatingacustomParagraphclass)andreplacetheirDOMpropertiesorCSS stylepropertieswithstronglytypedproperties.Manydevelopersusethisapproachtoprevent minortypographicerrorsinpropertynamesthatwontbecaughtatcompiletime.

ESCAPING SPECIAL CHARACTERS

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 &lt; and &gt; character entities, as shown here:

element.SetProperty("innerHTML", "To get bold text use the &lt;b&gt; element.");

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 &nbsp; character entity for a nonbreaking space.

Inserting and Removing Elements


ThepreviousexamplemodifiedanexistingHTMLelement.Itsjustaseasytoaddelementsto orremovethemfromanHTMLpage,usingthreemethods:HtmlDocument.CreateElement(), HtmlElement.AppendChild(),andHtmlElement.RemoveChild(). Forexample,thefollowingcodeassumesthattheparagraphdoesntexistinthetext page,andcreatesit: HtmlElement element = HtmlPage.Document.CreateElement("p"); element.Id = "paragraph"; element.SetProperty("innerHTML", "This is a new element. Click to change its background color."); HtmlPage.Document.Body.AppendChild(element); Inthisexample,theelementisinsertedasthelastchildofthe<body>element,which meansitsplacedattheendofthedocument.Ifyouhaveaplacewhereyouwanttoinsert dynamicSilverlightcontent,itseasiesttodefineanempty<div>containerwithauniqueID. YoucanthenretrievetheHtmlElementforthat<div>anduseAppendChild()toinsertyournew content. Ordinarily,theAppendChild()methodplacesthenewelementattheendofthe collectionofnestedchildren.Butitspossibletopositionanelementmorepreciselybyusingan overloadedversionofAppendChild()thatacceptsanotherHtmlElementobjecttoactasa reference.Whenyouusethisapproach,theelementisinsertedjustbefore thereferenced element: // Get a reference to the first element in the <body>. HtmlElement referenceElement = HtmlPage.Document.Body.Children[0]; // Make the new element the very first child in the <body> element, // before all other nested elements. HtmlPage.Document.Body.AppendChild(element, referenceElement); Incidentally,itseveneasiertoremoveanelement.Theonlytrickisthatyouneedto usetheRemoveChild()methodoftheparent,nottheelementyouwanttoremove. Heresthecodethatremovestheparagraphelementifitexists: HtmlElement element = HtmlPage.Document.GetElementById("paragraph");

if (element != null) element.Parent.RemoveChild(element);

Changing Style Properties


SettingstyleattributesisjustaseasyassettingDOMproperties.Youhaveessentiallythree options. First,youcansettheelementtouseanexistingstyleclass.Todothis,yousetthe HtmlElement.CssClassproperty: element.CssClass = "highlightedParagraph"; Forthistowork,thenamedstylemustbedefinedinthecurrentHTMLdocumentorin alinkedstylesheet.HeresanexamplethatdefinesthehighlightedParagraphstyleinthe <head>oftheHTMLpage: <html xmlns="http://www.w3.org/1999/xhtml"> <head> <style type="text/css"> .highlightedParagraph { color: White; border: solid 1px black; background-color: Lime; } ... </style> ... </head> <body>...</body> </html> ThisapproachrequirestheleastcodeandkeepstheformattingdetailsinyourHTML markup.However,itsanallornothingapproachifyouwanttofinetuneindividualstyle properties,youmustfollowupwithadifferentapproach. Anotheroptionistosettheelementsstyleallatonce.Todothis,youusethe HtmlElement.SetAttribute()methodandsetthestyleproperty.Heresanexample: element.SetAttribute("style", "color: White; border: solid 1px black; background-color: Lime;"); Butaneaterapproachistosetthestylepropertiesseparatelyusingthe SetStyleAttribute()methodseveraltimes: element.SetStyleAttribute("color", "White"); element.SetStyleAttribute("border", "solid 1px black"); element.SetStyleAttribute("background", "Lime"); YoucanusetheSetStyleAttribute()atanypointtochangeasinglestyleproperty, regardlessofhowyousetthestyleinitially(orevenifyouhaventsetanyotherstyleproperties).

Handling JavaScript Events


Notonlycanyoufind,examine,andchangeHTMLelements,youcanalsohandletheirevents. Onceagain,youneedtoknowthenameoftheHTMLDOMevent.Inotherwords,youneedto haveyourJavaScriptskillshandyinordertomaketheleapbetweenSilverlightandHTML.

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.

Calling Browser Script from Silverlight


UsingtheSilverlightclassesintheSystem.Windows.Browsernamespace,youcaninvokea JavaScriptfunctionthatsdeclaredinascriptblock.Thisgivesyouadisciplined,carefully controlledwayforSilverlightcodetointeractwithapage.Itsparticularlyusefulifyoualready haveaselfsufficientpagewithafullcomplementofJavaScriptfunctions.Ratherthanduplicate thecodethatmanipulatestheelementsinthatpage,youcancalloneoftheexistingmethods. Forexample,assumeyouhavethisfunctiondefinedinthe<head>sectionofyour HTMLpage: <script type="text/javascript"> function changeParagraph(newText) { var element = document.getElementById("paragraph"); element.innerHTML = newText; } </script> Tocallthismethod,youneedtousetheHtmlWindow.GetProperty()methodandpass inthenameofthefunction.YoureceiveaScriptObject,whichyoucanexecuteatanytimeby callingInvokeSelf(). ScriptObject script = (ScriptObject)HtmlPage.Window.GetProperty("changeParagraph"); WhenyoucallInvokeSelf(),youpassinalltheparameters.ThechangeParagraph() functionrequiresasinglestringparagraph,soyoucancallitlikethis: script.InvokeSelf("Changed through JavaScript.");

Calling Silverlight Methods from the Browser


Interestingly,SilverlightalsohasthecomplementaryabilitytoletJavaScriptcodecallamethod writteninmanagedcode.Thisprocessisabitmoreinvolved.Inordertomakeitwork,you needtotakethefollowingsteps: 1.CreateapublicmethodinyourSilverlightcodethatexposestheinformationor functionalityyouwantthewebpagetouse.Youcanplacethemethodinyourpageclass orinaseparateclass.Youllneedtosticktosimpledatatypes,likestrings,Boolean values,andnumbers,unlessyouwanttogothroughtheadditionalworkofserializing yourobjectstoasimplerform. 2.AddtheScriptableMemberattributetothedeclarationofthemethodthatyouwantto callfromJavaScript. 3.AddtheScriptableTypeattributetothedeclarationoftheclassthatincludesthe scriptablemethod. 4.ToexposeyourSilverlightmethodtoJavaScript,callthe HtmlPage.RegisterScriptableObject()method.

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

TextBlock.</p> Now,clickingtheparagraphtriggerstheupdateSilverlight()JavaScriptfunction,which inturncallstheChangeText()methodthatsapartofyourScriptableSilverlightclass.

Instantiating Silverlight Objects in the Browser


ThepreviousexampledemonstratedhowyoucancallaSilverlightmethodforJavaScriptcode. Silverlighthasonemoretrickforcodeinteraction:itallowsJavaScriptcodetoinstantiatea Silverlightobject. Asbefore,youstartwithascriptabletypethatincludesscriptablemethods.Heresan exampleofaverybasicSilverlightclassthatreturnsrandomnumbers: [ScriptableType()] public class RandomNumbers { private Random random = new Random(); [ScriptableMember()] public int GetRandomNumberInRange(int from, int to) { return random.Next(from, to+1); } } Aswiththepreviousexample,youneedtoregisterthisclasstomakeitavailableto JavaScriptcode.However,insteadofusingtheRegisterScriptableObject()method,youusethe RegisterCreateableType()method,asshownhere: HtmlPage.RegisterCreateableType("RandomNumbers", typeof(RandomNumbers)); Tocreateaninstanceofaregisteredtype,youneedtofindtheSilverlightcontroland callitscontent.services.createObject()method.HeresanexamplewithaJavaScriptfunction thatdisplaysarandomnumberfrom1to6usinganinstanceoftheSilverlight RandomNumbersclass: <script type="text/javascript"> function getRandom1To6() { var control = document.getElementById("silverlightControl"); var random = control.content.services.createObject("RandomNumbers"); alert("Your number is: " + random.GetRandomNumberInRange(1, 6)); } </script> ThefinaldetailisanHTMLelementthatcallsgetRandom1To6(): <p onclick="getRandom1To6()">Click here to get a random number from 1 to 6.</p>

Placing the Silverlight Control Next to an HTML Element


MuchasyoucanresizetheSilverlightcontrolusingstyleproperties,youcanalsorepositionit. ThetrickistouseaCSSstylethatspecifiesabsolutepositioningfortheSilverlightcontrol(or the<div>elementthatwrapsit).YoucanthenplacetheSilverlightcontrolattheappropriate coordinatesbysettingtheleftandtopstyleproperties. Forexample,inFigure149,thegoalistopopuptheSilverlightapplicationina floatingwindowoverofthepagebutnexttoaspecificHTMLelement(whichishighlightedin yellow).ThespecificpositionofthehighlightedHTMLelementchangesdependingonthesize ofthebrowserwindow.Thus,toputtheSilverlightcontentintherightplace,youneedto positionitdynamicallywithcode.

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();

Securing HTML Interoperability


SilverlightsHTMLinteroperabilityfeaturesraisesomenewsecurityconsiderations.Thisis particularlytrueiftheSilverlightapplicationandthehostingwebpagearedevelopedby differentparties.Inthissituation,theresariskthatmaliciouscodeinaSilverlightapplication couldtamperwiththeHTMLelsewhereonthepage.Or,JavaScriptcodeintheHTMLpage couldcallintotheSilverlightapplicationwithmaliciousinformation,potentiallytrickingitinto carryingoutthewrongaction. Iftheseissuesareaconcern,youcanuseafewoptionstoclampdownonSilverlights HTMLinteroperability.TopreventtheSilverlightapplicationfromoversteppingitsbounds, youcansetoneoftwoparametersintheHTMLentrypage: enableHtmlAccess: Whenfalse,theSilverlightapplicationwontbeabletousemostof theHTMLinteroperabilityfeatures,includingtheDocument,Window,Plugin,and BrowserInformationpropertiesoftheHtmlPageclass.(However,youwillstillbeallowed

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.

The Application Model


The Application Class
EveryXAMLpageisatemplateforacustomclassthatderivesfromSystem.Windows.UserControl,the App.xamlfileisatemplateforacustomclass(namedAppbydefault)thatderivesfrom System.Windows.Application.YoullfindtheclassdefinitionintheApp.xaml.csfile:

public partial class App : Application { ... } WhentheSilverlightpluginloadsyourapplication,itbeginsbycreatinganinstanceof theAppclass.Fromthatpointon,theapplicationobjectservesasyourentrypointforavariety ofapplicationspecificfeatures,includingapplicationevents,resources,andservices.

Accessing the Current Application


Youcanretrieveareferencetotheapplicationobjectatanytime,atanypointinyourcode, usingthestaticApplication.Currentproperty.However,thispropertyistypedasa System.Windows.Applicationobject.Touseanycustompropertiesormethodsthatyouve addedtothederivedapplicationclass,youmustcastthereferencetotheApptype.For example,ifyouveaddedamethodnamedDoSomething()totheApp.xaml.csfile,youcan invokeitwithcodelikethis: ((App)Application.Current).DoSomething(); Thistechniqueallowsyoutouseyourcustomapplicationclassasasortof switchboardforglobaltasksthataffectyourentireapplication.Forexample,youcanadd methodstoyourapplicationclassthatcontrolnavigationorregistration,andaddproperties thatstoreglobaldata.

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

IsRunningOutOf Browser Thesepropertiesletyourecognizeandmonitoroutofbrowser applications.IsRunningOutOfBrowserindicateswhetherthe applicationiscurrentlyrunningoutofthebrowser(true)orinthe browserwindow(false).

InstallState

InstallStateprovidesavaluefromthe InstallStateenumerationthatindicateswhetherthecurrent

applicationisinstalledasanoutofprocessapplicationonthe currentcomputer(Installed),notinstalled(NotInstalledor InstallFailed),orintheprocessofbeinginstalled(Installing). GetResourceStream()Thisstaticmethodisusedtoretrieveresourcesincode. LoadComponent()ThisstaticmethodacceptsaXAMLfileandinstantiatesthe correspondingelements(muchasSilverlightdoesautomatically whenyoucreateapageclassandtheconstructorcallsthe InitializeComponent()method).

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)

{ this.RootVisual = new MainPage(); }

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

regionorchangesitssource,oranunhandledexceptionderailsyourcode. Justbeforetheapplicationisreleasedfrommemory,Silverlightgivesyouthechanceto runsomecodebyrespondingtotheApplication.Exitevent.Thiseventiscommonlyusedto storeuserspecificinformationlocallyinisolatedstorage(seeChapter18),soitsavailablethe nexttimetheuserrunsyourapplication. TheExiteventdoesntprovideanyadditionalinformationinitseventarguments.

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 {} } }

Custom Splash Screens


IfaSilverlightapplicationissmall,itdownloadsquicklyandappearsinthebrowser.Ifa Silverlightapplicationislarge,itmaytakeafewsecondstodownload.Aslongasyour applicationtakeslongerthan500millisecondstodownload,Silverlightshowsananimated splashscreen. Ifyoudontlikethestocksplashscreen,youcaneasilycreateyourown(seeFigure6 4).Essentially,acustomsplashscreenisaXAMLfilewiththegraphicalcontentyouwantto displayandadashofJavaScriptcodethatupdatesthesplashscreenastheapplicationis downloaded.YoucantuseC#codeatthispoint,becausetheSilverlightprogramming environmenthasntbeeninitializedyet.However,thisisntamajorsetback,becausethecode youneedisrelativelystraightforward.Itlivesinoneortwoeventhandlingfunctionsthatare triggeredascontentisbeingdownloadedandafteritsfinished,respectively.Andbecause JavaScriptissyntacticallysimilartoC#,youwonthavemuchtroubleputtingtogetherthecode youneed. TheXAMLfileforyoursplashscreencantbeapartofyourSilverlightXAPfile.Thats becausethesplashscreenneedstobeshownwhiletheXAPfileisstillintheprocessofbeing downloaded.Forthatreason,thesplashscreenXAMLmustbeaseparatefilethatsplaced alongsideyourXAPfileatthesameweblocation.

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

SolutionExplorer,andchooseWebsite AddNewItem.ChoosetheSilverlightgroupand selecttheSilverlightJScriptpagetemplate.ThenenteranameandclickAdd.ThisXAMLfile willholdthemarkupforyoursplashscreen. WhenyouaddanewXAMLfile,VisualStudiocreatesabasicXAMLskeletonthat definesaCanvas.ThatsbecauseVisualStudioassumesyourebuildingaSilverlight1.0 application,whichsupportsamuchsmallersetofelementsanddoesntincludeanyofthe moreadvancedlayoutcontainers.

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.

Вам также может понравиться