WPF-arkitektur
Det här avsnittet innehåller en guidad rundtur i klasshierarkin för Windows Presentation Foundation (WPF). Den täcker de flesta av de viktigaste delsystemen i WPF och beskriver hur de interagerar. Det beskriver också några av de val som gjorts av arkitekterna av WPF.
System.Object
Den primära WPF-programmeringsmodellen exponeras via hanterad kod. Tidigt i designfasen av WPF fanns det ett antal debatter om var linjen ska dras mellan systemets hanterade komponenter och de ohanterade. CLR tillhandahåller ett antal funktioner som gör utvecklingen mer produktiv och robust (inklusive minneshantering, felhantering, vanligt typsystem osv.) men de kommer till en kostnad.
De viktigaste komponenterna i WPF illustreras i bilden nedan. De röda delarna i diagrammet (PresentationFramework, PresentationCore och milcore) är de viktigaste koddelarna i WPF. Av dessa är bara en ohanterad komponent – milcore. Milcore är skrivet i ohanterad kod för att möjliggöra nära integrering med DirectX. All visning i WPF sker via DirectX-motorn, vilket möjliggör effektiv maskinvaru- och programvarurendering. WPF krävde också fin kontroll över minne och exekvering. Kompositionsmotorn i milcore är extremt prestandakänslig och kräver att du ger upp många fördelar med CLR för att få prestanda.
Kommunikationen mellan de hanterade och ohanterade delarna av WPF beskrivs senare i det här avsnittet. Resten av den hanterade programmeringsmodellen beskrivs nedan.
System.Threading.DispatcherObject
De flesta objekt i WPF härleds från DispatcherObject, som tillhandahåller grundläggande konstruktioner för att hantera samtidighet och trådning. WPF baseras på ett meddelandesystem som implementeras av avsändaren. Detta fungerar ungefär som den välbekanta Win32 meddelandepump; I själva verket använder WPF-avsändaren User32-meddelanden för att utföra korstrådsanrop.
Det finns egentligen två grundläggande begrepp att förstå när du diskuterar samtidighet i WPF – dispatcher och trådtillhörighet.
Under designfasen för WPF var målet att gå över till en enda exekveringstråd, men en icke-trådberoende modell. Trådtillhörighet sker när en komponent använder identiteten för den körbara tråden för att lagra någon typ av tillstånd. Den vanligaste formen av detta är att använda det lokala trådarkivet (TLS) för att lagra tillstånd. Trådtillhörighet kräver att varje logisk tråd hanteras av endast en fysisk tråd i operativsystemet, vilket kan bli minnesintensivt. Till slut hölls WPF:s trådmodell synkroniserad med den befintliga User32-trådningsmodellen för enkel trådad körning med trådtillhörighet. Den främsta orsaken till detta var samverkan – system som OLE 2.0, Urklipp och Internet Explorer kräver alla körning av enkel trådtillhörighet (STA).
Med tanke på att du har objekt med STA-trådning behöver du ett sätt att kommunicera mellan trådar och verifiera att du är på rätt tråd. Häri ligger rollen av dispatcheren. Dispatcher är ett grundläggande system för meddelandesändning med flera prioriterade köer. Exempel på meddelanden är meddelanden med råa indata (mus flyttas), ramverksfunktioner (layout) eller användarkommandon (kör den här metoden). Genom att härleda från DispatcherObjectskapar du ett CLR-objekt som har STA-beteende och får en pekare till en dispatcher vid skapandetillfället.
System.Windows.DependencyObject
En av de primära arkitekturfilosofierna som användes för att skapa WPF var en inställning för egenskaper framför metoder eller händelser. Egenskaper är deklarativa och gör att du enklare kan ange avsikt i stället för åtgärd. Detta har också stöd för ett modelldrivet eller datadrivet system för att visa användargränssnittsinnehåll. Den här filosofin hade den avsedda effekten att skapa fler egenskaper som du kunde binda till för att bättre kontrollera beteendet för ett program.
För att få mer av systemet drivet av egenskaper behövdes ett rikare egenskapssystem än vad CLR tillhandahåller. Ett enkelt exempel på den här rikedomen är ändringsmeddelanden. För att aktivera tvåvägsbindning behöver du båda sidor av bindningen för att stödja ändringsmeddelanden. För att kunna ha ett beteende kopplat till egenskapsvärden måste du meddelas när egenskapsvärdet ändras. Microsoft .NET Framework har ett gränssnitt, INotifyPropertyChange, som gör att ett objekt kan publicera ändringsmeddelanden, men det är valfritt.
WPF tillhandahåller ett rikare egenskapssystem som härleds från DependencyObject typ. Egenskapssystemet är verkligen ett "beroende"-egenskapssystem eftersom det spårar beroenden mellan egenskapsuttryck och automatiskt omvalidaterar egenskapsvärden när beroendena ändras. Om du till exempel har en egenskap som ärver (till exempel FontSize) uppdateras systemet automatiskt om egenskapen ändras på en överordnad till ett element som ärver värdet.
Grunden för WPF-egenskapssystemet är begreppet egenskapsuttryck. I den här första versionen av WPF stängs egenskapsuttryckssystemet och uttrycken tillhandahålls som en del av ramverket. Uttryck är anledningen till att egenskapssystemet inte har databindning, stilinställning eller arv hårdkodade, utan tillhandahålls av senare lager i ramverket.
Egenskapssystemet tillhandahåller också sparsam lagring av egenskapsvärden. Eftersom objekt kan ha dussintals (om inte hundratals) egenskaper, och de flesta av värdena är i sitt standardtillstånd (ärvda, angivna efter format osv.), behöver inte alla instanser av ett objekt ha hela vikten för varje egenskap som definieras på den.
Den sista nya funktionen i egenskapssystemet är begreppet anslutna egenskaper. WPF-element bygger på principen om återanvändning av sammansättning och komponenter. Det är ofta så att ett innehållande element (till exempel ett Grid-layoutelement) behöver ytterligare data över underordnade element för att styra sitt beteende, såsom rad/kolumn-information. I stället för att associera alla dessa egenskaper med varje element, tillåts alla objekt att tillhandahålla egenskapsdefinitioner för andra objekt. Detta liknar "expando"-funktionerna i JavaScript.
System.Windows.Media.Visual
Med ett system definierat är nästa steg att få bildpunkter ritade till skärmen. Klassen Visual möjliggör skapandet av ett träd med visuella objekt där varje objekt kan innehålla ritningsinstruktioner och metadata om hur dessa instruktioner återges (klippning, transformation osv.). Visual är utformat för att vara extremt enkelt och flexibelt, så de flesta funktioner har ingen offentlig API-exponering och är starkt beroende av skyddade återanropsfunktioner.
Visual är verkligen startpunkten för WPF-kompositionssystemet. Visual är anslutningspunkten mellan dessa två undersystem, det hanterade API:et och den ohanterade milcoreen.
WPF visar data genom att gå igenom de ohanterade datastrukturer som hanteras av milcore. Dessa strukturer, som kallas kompositionsnoder, representerar ett hierarkiskt visningsträd med återgivningsinstruktioner vid varje nod. Det här trädet, som illustreras på höger sida av bilden nedan, är endast tillgängligt via ett meddelandeprotokoll.
När du programmerar WPF skapar du Visual element och härledda typer som internt kommunicerar med kompositionsträdet via det här meddelandeprotokollet. Varje Visual i WPF kan skapa en, ingen eller flera kompositionsnoder.
Det finns en mycket viktig detalj i arkitekturen att notera här – hela trädet med visuella objekt och ritningsinstruktioner cachas. Grafiskt sett använder WPF ett återhållet återgivningssystem. Detta gör det möjligt för systemet att måla om vid höga uppdateringshastigheter utan att kompositionssystemet blockerar återanrop till användarkod. Detta förhindrar att ett program som inte svarar visas.
En annan viktig detalj som inte riktigt märks i diagrammet är hur systemet faktiskt utför komposition.
I User32 och GDI fungerar systemet på ett urklippssystem i omedelbart läge. När en komponent behöver återges upprättar systemet ett urklippsgränser utanför vilka komponenten inte får röra pixlarna, och sedan uppmanas komponenten att måla pixlar i rutan. Det här systemet fungerar mycket bra i minnesbegränsade system eftersom när något ändras behöver du bara röra den berörda komponenten – inga två komponenter bidrar någonsin till färgen på en enda pixel.
WPF använder en målarmodell med 'painter's algorithm'. Det innebär att i stället för att klippa ut varje komponent uppmanas varje komponent att återges från baksidan till framsidan av skärmen. På så sätt kan varje komponent måla över den tidigare komponentens visning. Fördelen med den här modellen är att du kan ha komplexa, delvis transparenta former. Med dagens moderna grafikmaskinvara är den här modellen relativt snabb (vilket inte var fallet när User32/ GDI skapades).
Som tidigare nämnts är en kärnfilosofi för WPF att gå över till en mer deklarativ, "egenskapscentrerad" modell för programmering. I det visuella systemet visas detta på ett par intressanta platser.
För det första, om du tänker på det bevarade lägets grafiska system, rör detta sig verkligen bort från en imperativ RitaLinje-/RitaLinje-typmodell till en dataorienterad modell – ny Linje()/ny Linje(). Den här övergången till datadriven rendering gör att komplexa åtgärder i ritningsinstruktionerna kan uttryckas med hjälp av egenskaper. De typer som härleds från Drawing är i praktiken objektmodellen för återgivning.
För det andra, om du utvärderar animeringssystemet ser du att det nästan är helt deklarativt. I stället för att kräva att en utvecklare beräknar nästa plats, eller nästa färg, kan du uttrycka animeringar som en uppsättning egenskaper för ett animeringsobjekt. Dessa animeringar kan sedan uttrycka utvecklarens eller designerns avsikt (flytta den här knappen härifrån till den på 5 sekunder), och systemet kan fastställa det mest effektiva sättet att åstadkomma detta.
System.Windows.UIElement
UIElement definierar kärnundersystem, inklusive layout, indata och händelser.
Layout är ett grundläggande begrepp i WPF. I många system finns det antingen en fast uppsättning layoutmodeller (HTML stöder tre modeller för layout, flöde, absolut och tabeller) eller ingen modell för layout (User32 stöder egentligen bara absolut positionering). WPF började med antagandet att utvecklare och designers ville ha en flexibel, utökningsbar layoutmodell, som kan drivas av egenskapsvärden snarare än imperativ logik. På UIElement-nivån introduceras det grundläggande kontraktet för layout – en tvåfasmodell med Measure och Arrange pass.
Measure gör det möjligt för en komponent att avgöra hur stor storlek den vill ta. Det här är en separat fas från Arrange eftersom det finns många situationer där ett överordnat element ber ett underordnat element att mäta flera gånger för att fastställa dess optimala position och storlek. Det faktum att överordnade element ber underordnade element att mäta visar en annan nyckelfilosofi för WPF – storlek på innehåll. Alla kontroller i WPF har stöd för möjligheten att anpassa till innehållets naturliga storlek. Detta gör lokaliseringen mycket enklare och möjliggör dynamisk layout av element när saker och ting ändrar storlek. Med Arrange-fasen kan en förälder placera och bestämma den slutliga storleken på varje underordnad.
Mycket tid ägnas ofta åt att prata om utdatasidan av WPF – Visual och relaterade objekt. Det finns dock en enorm mängd innovation på indatasidan också. Förmodligen är den mest grundläggande ändringen i indatamodellen för WPF den konsekventa modell genom vilken indatahändelser dirigeras genom systemet.
Indata kommer som en signal på en enhetsdrivrutin i kernel-läge och dirigeras till den korrekta processen och tråden genom en invecklad process som involverar Windows-kerneln och User32. När user32-meddelandet som motsvarar indata dirigeras till WPF konverteras det till ett WPF-råindatameddelande och skickas till avsändaren. WPF gör att råa indatahändelser kan konverteras till flera faktiska händelser, vilket gör att funktioner som "MouseEnter" implementeras på en låg nivå av systemet med garanterad leverans.
Varje indatahändelse konverteras till minst två händelser – en "förhandsversion"-händelse och den faktiska händelsen. Alla händelser i WPF har en uppfattning om routning genom elementträdet. Händelser brukar kallas för att "bubbla" när de rör sig uppåt från ett mål i trädet till roten, och "tunnla" när de rör sig neråt från roten till ett mål. Händelsetunnel för indataförhandsgranskning, vilket gör att alla element i trädet kan filtrera eller vidta åtgärder för händelsen. De vanliga händelserna (icke-förhandsversion) bubblar sedan från målet upp till roten.
Den här uppdelningen mellan tunnel- och bubbelfasen gör att implementering av funktioner som tangentbordsacceleratorer fungerar konsekvent i en sammansatt värld. I User32 implementerar du tangentbordsacceleratorer genom att ha en enda global tabell som innehåller alla acceleratorer som du vill stödja (Ctrl+N-mappning till "Ny"). I dispatcher-funktionen för ditt program anropar du TranslateAccelerator som skannar inmatningsmeddelandena i User32 och avgör om något matchar en registrerad accelerator. I WPF skulle detta inte fungera eftersom systemet är helt "komposterbart" – alla element kan hantera och använda valfri tangentbordsaccelerator. Med den här två fasmodellen för indata kan komponenter implementera sin egen "TranslateAccelerator".
För att ta detta ett steg längre introducerar UIElement även begreppet CommandBindings. WPF-kommandosystemet gör det möjligt för utvecklare att definiera funktioner i termer av en kommandoslutpunkt – något som implementerar ICommand. Med kommandobindningar kan ett element definiera en mappning mellan en indatagest (Ctrl+N) och ett kommando (Nytt). Både indatagester och kommandodefinitioner är utökningsbara och kan kopplas samman vid användningstid. Detta gör det till exempel trivialt att låta en slutanvändare anpassa de nyckelbindningar som de vill använda i ett program.
Till den här punkten i ämnet har "kärnfunktioner" i WPF – funktioner som implementerats i PresentationCore-sammansättningen varit i fokus. När du byggde WPF var en ren separation mellan grundläggande delar (som kontraktet för layout med Measure och Ordna) och ramverksdelar (som implementeringen av en specifik layout som Grid) det önskade resultatet. Målet var att ge en utökningspunkt låg i stacken som skulle göra det möjligt för externa utvecklare att skapa egna ramverk om det behövs.
System.Windows.FrameworkElement
FrameworkElement kan ses över på två olika sätt. Den introducerar en uppsättning principer och anpassningar på de undersystem som introduceras i lägre lager av WPF. Den introducerar också en uppsättning nya undersystem.
Den primära principen som introduceras av FrameworkElement handlar om programlayout. FrameworkElement bygger på det grundläggande layoutkontraktet som introducerades av UIElement och lägger till begreppet layout-"slot" som gör det enklare för layoututvecklare att ha en konsekvent uppsättning egenskapsdrivna layoutsemantik. Egenskaper som HorizontalAlignment, VerticalAlignment, MinWidthoch Margin (för att nämna några) ger alla komponenter som härleds från FrameworkElement konsekvent beteende i layoutcontainrar.
FrameworkElement ger också enklare API-exponering för många funktioner som finns i kärnskikten i WPF. Till exempel ger FrameworkElement direkt åtkomst till animering via metoden BeginStoryboard. En Storyboard ger ett sätt att skripta flera animeringar mot en uppsättning egenskaper.
De två viktigaste sakerna som FrameworkElement introducerar är databindning och formatmallar.
Undersystemet för databindning i WPF bör vara relativt bekant för alla som har använt Windows Forms eller ASP.NET för att skapa ett användargränssnitt (UI). I vart och ett av dessa system finns det ett enkelt sätt att uttrycka att du vill att en eller flera egenskaper från ett visst element ska bindas till en datadel. WPF har fullt stöd för egenskapsbindning, transformering och listbindning.
En av de mest intressanta funktionerna i databindning i WPF är införandet av datamallar. Med datamallar kan du deklarativt ange hur en del av data ska visualiseras. I stället för att skapa ett anpassat användargränssnitt som kan bindas till data kan du i stället vända på problemet och låta data bestämma vilken visning som ska skapas.
Styling är verkligen en lätt form av databindning. Med formatering kan du binda en uppsättning egenskaper från en delad definition till en eller flera instanser av ett element. Format tillämpas på ett element antingen med explicit referens (genom att ange egenskapen Style) eller implicit genom att associera ett format med CLR-typen för elementet.
System.Windows.Controls.Control
Kontrollens viktigaste funktion är templating. Om du tänker på WPF:s sammansättningssystem som ett återgivningssystem för behållet läge kan en kontroll beskriva dess återgivning på ett parametriserat, deklarativt sätt. En ControlTemplate är egentligen inget annat än ett skript för att skapa en uppsättning underordnade element, med bindningar till egenskaper som erbjuds av kontrollen.
Control innehåller en uppsättning lageregenskaper, Foreground, Background, Padding, för att nämna några, som mallförfattare sedan kan använda för att anpassa visningen av en kontroll. Implementeringen av en kontroll ger en datamodell och interaktionsmodell. Interaktionsmodellen definierar en uppsättning kommandon (till exempel Stäng för ett fönster) och bindningar till indatagester (som att klicka på det röda X:et i det övre hörnet av fönstret). Datamodellen innehåller en uppsättning egenskaper för att antingen anpassa interaktionsmodellen eller anpassa visningen (bestäms av mallen).
Den här uppdelningen mellan datamodellen (egenskaper), interaktionsmodellen (kommandon och händelser) och visningsmodellen (mallar) möjliggör fullständig anpassning av kontrollens utseende och beteende.
En vanlig aspekt av datamodellen för kontroller är innehållsmodellen. Om du tittar på en kontroll som Buttonser du att den har en egenskap med namnet "Innehåll" av typen Object. I Windows Forms och ASP.NET är den här egenskapen vanligtvis en sträng , men den begränsar vilken typ av innehåll du kan lägga till i en knapp. Innehåll för en knapp kan antingen vara en enkel sträng, ett komplext dataobjekt eller ett helt elementträd. När det gäller ett dataobjekt används datamallen för att konstruera en visning.
Sammanfattning
WPF är utformat för att du ska kunna skapa dynamiska, datadrivna presentationssystem. Varje del av systemet är utformad för att skapa objekt via egenskapsuppsättningar som driver beteende. Databindning är en grundläggande del av systemet och är integrerad i varje lager.
Traditionella program skapar en visning och binder sedan till vissa data. I WPF genereras allt om kontrollen, varje aspekt av skärmen, av någon typ av databindning. Texten som finns i en knapp visas genom att skapa en sammansatt kontroll inuti knappen och binda dess visning till knappens innehållsegenskap.
När du börjar utveckla WPF-baserade program bör det kännas mycket bekant. Du kan ange egenskaper, använda objekt och databindning på ungefär samma sätt som du kan använda Windows Forms eller ASP.NET. Med en djupare undersökning av WPF-arkitekturen upptäcker du att det finns en möjlighet att skapa mycket rikare program som i grunden behandlar data som kärndrivrutiner för programmet.
Se även
.NET Desktop feedback