Architettura WPF

In questo argomento viene fornita una presentazione guidata della gerarchia di classi Windows Presentation Foundation (WPF). Illustra la maggior parte dei sottosistemi principali di WPF e descrive come interagiscono. Descrive anche alcune delle scelte effettuate dagli architetti di WPF.

System.Object

Il modello di programmazione WPF primario viene esposto tramite codice gestito. All'inizio della fase di progettazione di WPF ci sono stati diversi dibattiti su dove la linea dovrebbe essere tracciata tra i componenti gestiti del sistema e quelli non gestiti. CLR offre una serie di funzionalità che rendono lo sviluppo più produttivo e affidabile (tra cui gestione della memoria, gestione degli errori, sistema di tipi comuni e così via), ma sono a un costo.

I componenti principali di WPF sono illustrati nella figura seguente. Le sezioni rosse del diagramma (PresentationFramework, PresentationCore e milcore) sono le parti di codice principali di WPF. Di queste, solo milcore è un componente non gestito. Milcore viene scritto in codice non gestito per consentire una stretta integrazione con DirectX. Tutto lo schermo in WPF viene eseguito tramite il motore DirectX, consentendo un rendering efficiente di hardware e software. WPF ha anche richiesto un controllo fine sulla memoria e l'esecuzione. Il motore di composizione in milcore è estremamente sensibile alle prestazioni ed è necessario rinunciare a molti vantaggi di CLR per ottenere prestazioni.

The position of WPF within the .NET Framework.

La comunicazione tra le parti gestite e non gestite di WPF viene descritta più avanti in questo argomento. Il resto del modello di programmazione gestito viene descritto di seguito.

System.Threading.DispatcherObject

La maggior parte degli oggetti in WPF deriva da DispatcherObject, che fornisce i costrutti di base per gestire la concorrenza e il threading. WPF si basa su un sistema di messaggistica implementato dal dispatcher. Questo funziona molto come la nota pompa di messaggi Win32; Infatti, il dispatcher WPF usa messaggi User32 per l'esecuzione di chiamate tra thread.

Esistono due concetti fondamentali da comprendere quando si discute di concorrenza in WPF, ovvero il dispatcher e l'affinità di thread.

Durante la fase di progettazione di WPF, l'obiettivo era passare a un singolo thread di esecuzione, ma un modello "affinizzato" non thread. L'affinità di thread si verifica quando un componente utilizza l'identità del thread in esecuzione per archiviare un tipo di stato. Nella forma più comune, per archiviare lo stato viene utilizzata l'archiviazione locale di thread (TLS). L'affinità di thread richiede che ciascun thread di esecuzione logico appartenga a un solo thread fisico del sistema operativo, che può richiedere un elevato consumo di memoria. Alla fine, il modello di threading di WPF è stato tenuto in sincronia con il modello di threading User32 esistente dell'esecuzione a thread singolo con affinità di thread. Il motivo principale di questo problema era l'interoperabilità: i sistemi come OLE 2.0, gli Appunti e Internet Explorer richiedono l'esecuzione dell'affinità a thread singolo (STA).

Disponendo di oggetti con threading STA, è necessaria una modalità di comunicazione tra thread e di conferma di utilizzo del thread corretto. Qui si colloca il ruolo del dispatcher. Il dispatcher è un sistema di base di invio di messaggi con più code in ordine di priorità. Gli esempi di messaggi includono le notifiche di input non elaborato (spostamenti del mouse), le funzionalità del framework (layout) o i comandi utente (esecuzione di metodi). Derivando da DispatcherObject, si crea un oggetto CLR con comportamento STA e viene assegnato un puntatore a un dispatcher in fase di creazione.

System.Windows.DependencyObject

Una delle filosofie principali dell'architettura usata per la compilazione di WPF era una preferenza per le proprietà rispetto a metodi o eventi. Le proprietà sono dichiarative e consentono di specificare più facilmente lo scopo anziché l'azione. In tal modo veniva anche supportato un sistema basato sui modelli o sui dati per la visualizzazione dei contenuti dell'interfaccia utente. Lo scopo di tale approccio era di creare un maggior numero di proprietà con cui stabilire l'associazione per poter controllare meglio il comportamento di un'applicazione.

Per avere più del sistema basato sulle proprietà, un sistema di proprietà più completo rispetto a quello fornito da CLR. Un semplice esempio è rappresentato dalle notifiche di modifica. Per abilitare l'associazione bidirezionale, è necessario che entrambi i lati supportino la notifica di modifica. Per associare il comportamento a valori di proprietà, è necessario ricevere una notifica quando tale valore viene modificato. Microsoft .NET Framework dispone di un'interfaccia INotifyPropertyChange, che consente a un oggetto di pubblicare notifiche di modifica, ma è facoltativo.

WPF offre un sistema di proprietà più completo, derivato dal DependencyObject tipo . Il sistema di proprietà è effettivamente un sistema di proprietà delle "dipendenze" nel senso che tiene traccia delle dipendenze tra espressioni di proprietà e riconvalida automaticamente i valori delle proprietà quando tali dipendenze vengono modificate. Ad esempio, se si dispone di una proprietà che eredita (ad FontSizeesempio ), il sistema viene aggiornato automaticamente se la proprietà cambia in un elemento padre di un elemento che eredita il valore.

La base del sistema di proprietà WPF è il concetto di espressione di proprietà. In questa prima versione di WPF, il sistema di espressioni di proprietà viene chiuso e tutte le espressioni vengono fornite come parte del framework. Le espressioni rappresentano il motivo per cui il sistema di proprietà non dispone di associazione dati, stili o ereditarietà hardcoded forniti invece dai livelli successivi all'interno del framework.

Il sistema di proprietà, inoltre, presenta possibilità limitate di archiviazione per i valori di proprietà. Poiché gli oggetti hanno decine, se non centinaia, di proprietà e la maggior parte dei valori si trova nel relativo stato predefinito (ereditato, impostato per stile e così via), non tutte le istanze di un oggetto devono avere il peso di ciascuna proprietà in esso definita.

La nuova funzionalità finale del sistema di proprietà è la nozione di proprietà associate. Gli elementi WPF sono basati sul principio del riutilizzo della composizione e del componente. Spesso è possibile che alcuni elementi che contengono (ad esempio un Grid elemento di layout) richiedano dati aggiuntivi sugli elementi figlio per controllarne il comportamento, ad esempio le informazioni riga/colonna. Anziché associare tutte queste proprietà a ciascun elemento, un oggetto può fornire le definizioni delle proprietà per qualsiasi altro oggetto, come avviene per le funzionalità "expando" di JavaScript.

System.Windows.Media.Visual

Dopo avere definito un sistema, è necessario ottenere pixel disegnati sullo schermo. La Visual classe fornisce la compilazione di un albero di oggetti visivi, ognuno dei quali contiene facoltativamente istruzioni di disegno e metadati su come eseguire il rendering di tali istruzioni (ritaglio, trasformazione e così via). Visual è progettato per essere estremamente leggero e flessibile, quindi la maggior parte delle funzionalità non ha esposizione all'API pubblica e si basa principalmente sulle funzioni di callback protette.

Visual è davvero il punto di ingresso al sistema di composizione WPF. Visual è il punto di connessione tra questi due sottosistemi, l'API gestita e il milcore non gestito.

WPF visualizza i dati attraversando le strutture di dati non gestite gestite dal milcore. Queste strutture, denominate nodi di composizione, rappresentano una struttura di visualizzazione gerarchica ad albero con istruzioni di rendering in ciascun nodo. La struttura ad albero, illustrata a destra nella figura riportata di seguito, è accessibile solo mediante un protocollo di messaggistica.

Quando si programma WPF, si creano Visual elementi e tipi derivati, che comunicano internamente all'albero di composizione tramite questo protocollo di messaggistica. Ognuno Visual in WPF può creare uno, nessuno o più nodi di composizione.

The Windows Presentation Foundation Visual Tree.

In questo caso va notato un dettaglio molto importante relativo all'architettura: l'intera struttura ad albero degli elementi visivi e delle istruzioni di disegno è memorizzata nella cache. In termini grafici, WPF usa un sistema di rendering conservato. Ciò fornisce al sistema elevate frequenze di aggiornamento senza che il sistema di composizione si blocchi sui callback al codice utente e consente di impedire la visualizzazione di applicazioni che non rispondono.

Un altro dettaglio importante, non facilmente individuabile nel diagramma, è il modo in cui il sistema esegue effettivamente la composizione.

In User32 e GDI il sistema funziona su un sistema di ritaglio in modalità immediata. Quando è necessario eseguire il rendering di un componente, il sistema stabilisce un limite di ritaglio al di fuori del quale il componente non può toccare i pixel; successivamente, viene chiesto al componente di disegnare i pixel in tale casella. Questo sistema funziona molto bene nei sistemi con memoria limitata poiché quando vengono apportate modifiche basta intervenire solo sul componente interessato. Non sono mai richiesti due componenti per il colore di un singolo pixel.

WPF usa un modello di disegno "algoritmo di pittore". Ciò significa che anziché ritagliare ogni componente, viene richiesto il rendering di ciascun componente dallo sfondo al primo piano della visualizzazione. In tal modo ciascun componente viene disegnato sulla visualizzazione del componente precedente. Il vantaggio di questo modello è la possibilità di avere forme complesse, parzialmente trasparenti. Con l'hardware grafico moderno di oggi, questo modello è relativamente veloce (che non era il caso in cui è stato creato User32/ GDI).

Come accennato in precedenza, una filosofia di base di WPF consiste nel passare a un modello più dichiarativo incentrato sulle proprietà di programmazione. Nel sistema visuale questo è evidente in un paio di punti interessanti.

Innanzitutto, se si pensa al sistema grafico in modalità memorizzata, si nota un deciso allontanamento da un modello imperativo di tipo DrawLine/DrawLine verso un modello orientato ai dati di tipo new Line()/new Line(). Questo spostamento verso il rendering basato sui dati consente operazioni complesse sulle istruzioni di disegno da esprimere utilizzando le proprietà. I tipi derivati da Drawing sono effettivamente il modello a oggetti per il rendering.

In secondo luogo, se si esamina il sistema di animazione appare evidente che è quasi completamente dichiarativo. Anziché richiedere a uno sviluppatore di calcolare la posizione o il colore successivo, è possibile esprimere le animazioni come un set di proprietà su un oggetto di animazione. Le animazioni possono quindi esprimere l'intenzione dello sviluppatore o del progettista (spostare questo pulsante da questa a quella posizione in 5 secondi) e il sistema può stabilire il modo più efficace per raggiungere tale scopo.

System.Windows.UIElement

UIElement definisce i sottosistemi principali, tra cui Layout, Input ed Eventi.

Il layout è un concetto di base in WPF. In molti sistemi è presente un insieme fisso di modelli di layout (HTML supporta tre modelli di layout: flusso, assoluto e tabelle) oppure non è presente alcun modello di layout (User32 supporta effettivamente solo il posizionamento assoluto). WPF ha iniziato con il presupposto che gli sviluppatori e i progettisti volevano un modello di layout flessibile ed estendibile, che potrebbe essere guidato dai valori delle proprietà anziché dalla logica imperativa. A livello, viene introdotto il contratto di base per il UIElement layout, ovvero un modello a due fasi con Measure e Arrange pass.

Measure consente a un componente di determinare la dimensione che si desidera prendere. Si tratta di una fase separata da Arrange perché ci sono molte situazioni in cui un elemento padre chiederà a un elemento figlio di misurare più volte per determinare la posizione e le dimensioni ottimali. Il fatto che gli elementi padre chiedono agli elementi figlio di misurare dimostra un'altra filosofia chiave di WPF, ovvero le dimensioni del contenuto. Tutti i controlli in WPF supportano la possibilità di ridimensionare le dimensioni del contenuto. Tutto ciò semplifica il processo di localizzazione e consente il layout dinamico degli elementi quando gli oggetti vengono ridimensionati. La Arrange fase consente a un padre di posizionare e determinare le dimensioni finali di ogni figlio.

Spesso viene impiegato molto tempo per parlare del lato di output di WPF Visual e degli oggetti correlati. Esiste, tuttavia, una straordinaria quantità di innovazione anche sul lato input. Probabilmente la modifica più importante del modello di input per WPF è la consis modalità tenda l in base alla quale gli eventi di input vengono instradati attraverso il sistema.

L'input come segnale proviene da un driver di dispositivo in modalità kernel e viene indirizzato verso il processo e il thread corretti mediante un complicato processo che interessa il kernel di Windows e User32. Dopo che il messaggio User32 corrispondente all'input viene indirizzato a WPF, viene convertito in un messaggio di input non elaborato WPF e inviato al dispatcher. WPF consente la conversione di eventi di input non elaborati in più eventi effettivi, consentendo l'implementazione di funzionalità come "MouseEnter" a un basso livello del sistema con recapito garantito.

Ciascun evento di input viene convertito in almeno due eventi: un evento in "anteprima" e l'evento effettivo. Tutti gli eventi in WPF hanno una nozione di routing attraverso l'albero degli elementi. Gli eventi vengono detti "bolle" se attraversano da una destinazione verso l'alto l'albero alla radice e vengono detti "tunnel" se iniziano alla radice e attraversano verso il basso fino a una destinazione. Gli eventi di input in anteprima effettuano il tunneling consentendo a qualsiasi elemento della struttura ad albero di filtrare o svolgere un'azione sull'evento. Gli eventi regolari (non in anteprima) vengono propagati dalla destinazione fino alla radice.

Questa suddivisione tra fase di tunneling e fase di bubbling consente di implementare funzionalità, quali i tasti di scelta rapida, in modo coerente in un contesto composito. In User32 i tasti di scelta rapida vengono implementati con una sola tabella globale che contiene tutti i tasti da supportare (mapping di Ctrl+N su "Nuovo"). Nel dispatcher dell'applicazione viene chiamato TranslateAccelerator che rileva la presenza dei messaggi di input in User32 e stabilisce se esiste una corrispondenza con un tasto di scelta rapida registrato. In WPF questo non funzionerebbe perché il sistema è completamente "componibile" - qualsiasi elemento può gestire e usare qualsiasi acceleratore di tastiera. Questo modello a due fasi per l'input consente ai componenti di implementare il proprio "TranslateAccelerator".

Per fare questo passo avanti, UIElement introduce anche la nozione di CommandBindings. Il sistema di comandi WPF consente agli sviluppatori di definire funzionalità in termini di endpoint del comando, un elemento che implementa ICommand. Le associazioni di comandi consentono a un elemento di definire un mapping tra un movimento di input (Ctrl+N) e un comando (Nuovo). I movimenti di input e le definizioni dei comandi sono estendibili e possono essere collegati al momento dell'utilizzo. In tal modo, ad esempio, è possibile consentire a un utente finale di personalizzare le combinazioni di tasti da utilizzare in un'applicazione.

A questo punto dell'argomento, le funzionalità principali di WPF- funzionalità implementate nell'assembly PresentationCore sono state incentrate. Durante la compilazione di WPF, una separazione pulita tra parti fondamentali (ad esempio il contratto per il layout con Measure e Arrange) e parti del framework (come l'implementazione di un layout specifico come Grid) è il risultato desiderato. Lo scopo era quello di fornire un punto di estendibilità basso nello stack per consentire agli sviluppatori esterni di creare i propri framework, se necessario.

System.Windows.FrameworkElement

FrameworkElement può essere esaminato in due modi diversi. Introduce un set di criteri e personalizzazioni nei sottosistemi introdotti in livelli inferiori di WPF. e dall'altro un insieme di nuovi sottosistemi.

Il criterio principale introdotto da FrameworkElement riguarda il layout dell'applicazione. FrameworkElement si basa sul contratto di layout di base introdotto da UIElement e aggiunge la nozione di "slot" di layout che rende più semplice per gli autori di layout avere un set coerente di semantica del layout basato sulle proprietà. Le proprietà come HorizontalAlignment, VerticalAlignmentMinWidth, e Margin (per denominare alcuni) offrono a tutti i componenti derivati da FrameworkElement un comportamento coerente all'interno dei contenitori di layout.

FrameworkElement offre anche un'esposizione api più semplice a molte funzionalità presenti nei livelli principali di WPF. Ad esempio, FrameworkElement fornisce l'accesso diretto all'animazione tramite il BeginStoryboard metodo . Un Storyboard oggetto consente di creare script di più animazioni in base a un set di proprietà.

Le due cose più critiche introdotte FrameworkElement sono data binding e stili.

Il sottosistema di data binding in WPF deve essere relativamente familiare a chiunque abbia usato Windows Form o ASP.NET per la creazione di un'interfaccia utente dell'applicazione. In ciascuno di questi sistemi è possibile effettuare in modo semplice l'associazione di una o più proprietà di un dato elemento a un blocco di dati. WPF offre il supporto completo per l'associazione di proprietà, la trasformazione e l'associazione di elenchi.

Una delle funzionalità più interessanti del data binding in WPF è l'introduzione di modelli di dati. che consentono di specificare in modo dichiarativo come deve essere visualizzato un blocco di dati. Anziché creare un'interfaccia utente personalizzata che può essere associata ai dati, è possibile aggirare il problema e fare in modo che siano i dati a stabilire il tipo di visualizzazione da creare.

L'applicazione degli stili è effettivamente un tipo di associazione dati semplice e il suo utilizzo consente di associare un set di proprietà di una definizione condivisa a una o più istanze di un elemento. Gli stili vengono applicati a un elemento tramite riferimento esplicito (impostando la Style proprietà) o associando in modo implicito uno stile al tipo CLR dell'elemento.

System.Windows.Controls.Control

La funzionalità più significativa dei controlli è l'applicazione di modelli. Considerando il sistema di composizione di WPF come un sistema di rendering in modalità memorizzata, l'applicazione di modelli consente a un controllo di descriverne il rendering in modo dichiarativo e con parametri. Un ControlTemplate oggetto non è in realtà altro che uno script per creare un set di elementi figlio, con associazioni alle proprietà offerte dal controllo.

Control fornisce un set di proprietà stock, Foreground, Background, Padding, per denominare alcuni autori di modelli, che gli autori di modelli possono quindi usare per personalizzare la visualizzazione di un controllo. L'implementazione di un controllo fornisce un modello dati e un modello di interazione. Il modello di interazione definisce un set di comandi (ad esempio, Chiudi per una finestra) e di associazioni a movimenti di input (ad esempio, fare clic sulla X rossa nell'angolo superiore della finestra). Il modello dati fornisce un set di proprietà per la personalizzazione del modello di interazione o della visualizzazione (stabilita dal modello).

Questa suddivisione tra modello dati (proprietà), modello di interazione (comandi ed eventi) e modello di visualizzazione (modelli) consente una personalizzazione completa dell'aspetto e del comportamento di un controllo.

Una caratteristica comune del modello dati dei controlli è il modello di contenuto. Se si esamina un controllo come Button, si noterà che ha una proprietà denominata "Content" di tipo Object. In Windows Form e ASP.NET questa proprietà è in genere una stringa, ma limita il tipo di contenuto che è possibile inserire in un pulsante. Il contenuto di un pulsante può essere una stringa semplice, un oggetto dati complesso o l'intera struttura ad albero di un elemento. Nel caso di un oggetto dati, il modello dati viene utilizzato per costruire una visualizzazione.

Riepilogo

WPF è progettato per consentire di creare sistemi di presentazione dinamici basati sui dati. Ogni parte del sistema è progettata per la creazione di oggetti utilizzando set di proprietà che ne definiscono il comportamento. L'associazione dati è una parte fondamentale del sistema ed è integrata a ogni livello.

Le applicazioni tradizionali creano una visualizzazione e associano successivamente i dati. In WPF, tutto il controllo, ogni aspetto della visualizzazione, viene generato da un tipo di data binding. Il testo che si trova nei pulsanti viene visualizzato creando un controllo composto all'interno del pulsante e associando la relativa visualizzazione alla proprietà del contenuto del pulsante.

Quando si inizia a sviluppare applicazioni basate su WPF, dovrebbe risultare molto familiare. È possibile impostare proprietà, usare oggetti e data bind nello stesso modo in cui è possibile usare Windows Form o ASP.NET. Con un'analisi più approfondita dell'architettura di WPF, si scopre che esiste la possibilità di creare applicazioni molto più avanzate che considerano fondamentalmente i dati come il driver principale dell'applicazione.

Vedi anche