Professional Documents
Culture Documents
Windows Presentation Foundation (WPF)
Windows Presentation Foundation (WPF)
Contiene al suo interno sia una porzione applicativa visibile all’utente (PresentationFra-
mework e PresentationCore) e la milcore, ovvero una parte nativa Win32 che vive sotto
il CLR e fa da bridge tra il CLR stesso e le DirectX.
Per funzionare, WPF mette insieme due cose: la logica applicativa (“quando premo que-
sto bottone succede questo”, scritta in C# o in quello che vogliamo) e l’interfaccia grafi-
ca (che con Windows Forms diventava codice da eseguire, mentre qui è esplicitata con
una descrizione dichiarativa scritta in un dialetto XML). Questo facilita molto la separa-
zione dei ruoli tra grafico e sviluppatore.
Architettura WPF
Internamente, WPF è fatto di tanti blocchi: alla base di
tutto c’è la capacità di disegnare le primitive visuali
(linee, triangoli, ellissi, curve di Bèzier...) e la cosa è
demandata alla scheda video.
Sopra questo strato c’è uno strato di animazione. Fornisce un meccanismo dichiarativo
per dire cosa dovrà succedere nei prossimi n secondi, basandosi su un “coreografo”. Sul-
lo strato di animazione si appoggiano gli elementi necessari per costruire le primitive
percepite dall’utente (grafica 2D/3D, testo, video, audio, effetti per “storpiare” primitive
esistenti, immagini).
Mentre tutto quello che abbiamo presentato si occupa di far apparire qualcosa sullo
schermo, la colonna laterale si occupa della raccolta degli eventi. Sulla base di questi
due blocchi fondanti si costruiscono i servizi con cui ci interfacciamo: le librerie dei con-
trolli (bottoni e quant’altro), il data binding per legare valori di variabili a caratteristiche
degli elementi grafici, e così via.
Visual rendering
WPF introduce innovazioni dal punto di vista del rendering di oggetti grafici.
WPF invece usa l’approccio retained: ogni oggetto grafico descrive come vuole apparire
in una modalità dichiarativa. Ciascun oggetto quindi contiene dati sul proprio rendering,
che possono essere dati vettoriali, immagini, glifi (testo) e video, piazzando tutto in una
struttura dati serializzata in modo opportuno.
Il sistema grafico mantiene in una struttura ad albero i dati sul rendering degli oggetti
che compongono la scena da visualizzare. Le strutture dati sono costruite in modo da
essere ottimizzate nella costruzione di primitive di disegno da parte della GPU: quando
la finestra diventa non valida e invia il messaggio di
WM_PAINT, il motore di WPF non va più a scomodare i sin-
goli componenti perché sa già cosa deve fare declinando-
la opportunamente in base ai parametri correnti e facen-
dola eseguire alla CPU.
Grafica vettoriale
Consente di descrivere un elemento grafico in base ad una serie di primitive grafiche ed
è una soluzione più sofisticata della tradizionale grafica “bitmap”. Un’immagine ridimen-
sionata viene ridisegnata e non perde qualità rispetto all’originale, perché viene “scala-
ta” anziché “stirata.
Unità di misura
Due fattori determinano la dimensione fisica del testo e oggetti grafici sullo schermo
• risoluzione dello schermo (numero di pixel visualizzati)
• densità dei pixel DPI: dimensione di un pollice ideale espressa in pixel
WPF supporta schermi con differenti densità e usa come unità di misura primaria i “devi-
ce independent pixel” anziché i pixel hardware e consente di riadattare automaticamen-
te testo ed elementi grafici a diverse risoluzioni e DPI mantenendo costante la dimensio-
ne.
Sviluppo in WPF
Come facciamo a sviluppare applicazioni in WPF? Due possibilità:
• siccome tutto ciò che è fatto in WPF è istanza di una classe, posso creare pro-
grammaticamente le mie cose
• uso l’approccio dichiarativo utilizzando un linguaggio descrittivo basato su XML
XAML
Usato in WPF per creare e inizializzare oggetti secondo una data gerarchia. Tutte le clas-
si in WPF hanno solo un costruttore privo di argomenti: si utilizzano le proprietà per con-
figurare il comportamento e l’aspetto degli oggetti, facilitando l’integrazione con un lin-
guaggio descrittivo.
Il codice C# rimane così compatto e il codice XML generato è leggibile facilmente anche
dai non programmatori. Se volessi disegnare una finestra e fare tutto via software dovrei
scrivere una cosa del genere
<Window x:Class="hello.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window1" Height="145" Width="298" >
<Window.Background>
<LinearGradientBrush StartPoint="0 0" EndPoint="1 1" >
<GradientStop Offset="0" Color="Wheat"/>
<GradientStop Offset="1" Color="White"/>
</LinearGradientBrush>
</Window.Background>
<StackPanel>
<TextBlock Margin="20" FontSize="20" Foreground="Blue">Hello World </TextBlock>
<Button Margin="10" HorizontalAlignment="Right" Height="25" Width="80">OK</Button>
</StackPanel>
</Window>
Reattività
Quando creiamo un programma basato su WPF ci sono sempre (almeno) due thread. Noi
interagiamo col thread principale chiamato UI thread, che parte quando l’applicazione
viene lanciata e riceve gli eventi dell’utente, gestendo tutto ciò che è l’interazione base.
Può creare – anche se non è il massimo della vita – uno o più thread secondari per ese-
guire compiti in base all’input, il che è utile in tutte quelle situazioni in cui i compiti da
fare sono particolarmente spessi e bloccherebbero l’interfaccia.
Nel momento in cui l’applicazione si accorge di dover usare WPF crea comunque un th-
read secondario chiamato rendering thread che prende l’albero costruito nel thread prin-
cipale e “fa i disegni” a seconda di quello che gli chiede il thread principale.
Dispatcher
Internamente, tutte le applicazioni WPF hanno l’equivalente di una coda produttore-con-
sumatore, già sincronizzata. Quando vogliamo che succeda qualcosa basta scrivere dei
messaggi all’interno in modo che il thread di rendering li macini.
Lo UIThread può inserire messaggi nella coda con i metodi Invoke() (“chiedo di fare
questo mestiere e mi blocco finché non è stato fatto”) e BeginInvoke() (“chiedo di fare
questa roba e a limite passo un delegato che viene chiamato quando il mestiere è
fatto”).
La maggior parte, inoltre, sono anche DependencyObject: le proprietà degli oggetti sono
collegate tra loro in modo particolarmente efficiente. Posso ad esempio dire che il botto-
ne X sia sempre 10 pixel più a destra del bottone Y. Un DependencyObject è anche un Vi-
sual, nel senso che hanno un’apparenza grafica in quanto sanno generare un insieme di
primitive.
La classe System.Threading.DispatcherObject
Vive nel namespace System.Threading perché è un oggetto di sincronizzazione. Dentro
di sé contiene il riferimento al dispatcher e garantisce che tutte le operazioni effettuate
dalle proprie istanze avvengano nel contesto del thread responsabile della gestione del
Dispatcher cui è associato
Il valore della proprietà non viene duplicato finché non cambia, anche se appartiene a
due oggetti diversi di una certa classe: si ha così una minore occupazione di memoria.
Devo allocare memoria solo se la proprietà è “indipendente” e diversa da quella di de-
fault.
Proprietà aggiunte
<Canvas>
<Button Canvas.Top="20" Canvas.Left="20" Content="Ok"/>
</Canvas>
Quando costruiamo l’albero grafico del WPF bisogna fare attenzione al fatto che nel sin-
golo tag ci sono attributi propri del tag (il bottone contiene la scritta Ok) e attributi di un
oggetto contenitore (il bottone ha una certa posizione nel Canvas), che riconosciamo
perché hanno il punto in mezzo.
Offre supporto per output su display, trasformazioni (es.: scritte ruotate), clipping (tagli),
hit test (ho fatto un click col mouse, sono dentro l’oggetto Visual?) e calcolo dei margi-
ni. Non offre nessun supporto interattivo: sebbene sappia rispondere alla domanda
dell’hit test, non ha meccanismi per reagire. Inoltre non gestisce stili, layout e data bin-
ding.
I Visual sono combinabili tra loro in una gerarchia di elementi grafici, in cui è bene di-
stinguere i VisualTree dai LogicalTree. Quando scriviamo uno XAML produciamo un Lo-
gicalTree, ma alla scheda grafica comunichiamo un albero molto più ciccione, ovvero il
VisualTree. In certe situazioni dobbiamo sapere com’è fatto il VisualTree, al fine di ap-
plicare effetti speciali.
La classe Object.VisualTreeHelper
Utilizzata per passare dal LogicalTree al VisualTree, camminarci e scoprire una serie di
elementi al suo interno per poterli manipolare.
La classe DrawingContext
Ogni Visual ha un DrawingContext, che permette di popolare un Visual con un contenu-
to grafico vero e proprio, offrendo dei metodi che in apparenza disegnano qualcosa, ma
in realtà memorizza un insieme di informazioni sul disegno utilizzate in seguito.
La classe FrameworkElement
Aggiunge dei vincoli affinché un controllo sia un “bravo cittadino” nel mondo di WPF.
All’interno si trovano strumenti per la gestione delle animazioni, per il data binding e per
gli stili.
Layout
Una delle difficoltà iniziali nella creazione di interfacce con WPF è data dal comprendere
come sono posizionati gli elementi. Con Windows Forms il posizionamento è assoluto e
si vive bene, mentre qui c’è lo sforzo di non rendere le dimensioni assolute in modo da
costruire interfacce fluide.
Ci sono dunque tanti elementi del framework per gestire lo spazio. Il framework infatti
presuppone che ciascun componente operi con lo spazio circostante per capire dove an-
darsi a collocare: in una prima fase di measure ciascun elemento del LogicalTree viene
interrogato per sapere quanto vorrebbe essere grande, mentre in una seconda fase di
arrange il contenitore ricalcola la posizione e la dimensione degli elementi contenuti se-
condo i propri criteri.
Con questo meccanismo di misurazione posso ad esempio mettere immagini nei bottoni.
Input ed eventi
L’input si origina sempre nel kernel del sistema operativo, dove il driver responsabile di
una certa periferica intercetta un evento essenziale, propagato alla coda principale del
WindowManager posseduta dal sottosistema User32 e inviato al thread che ha creato
l’interfaccia grafica.
Routing degli eventi
Supponiamo di aver cliccato alle coordinate (100, 100). In Windows Form, se alle coordi-
nate (100, 100) c’è il rettangolo di un bottone, questo si sarebbe preso l’evento del
click.
WPF invece fa una cosa più sofisticata: il bottone fa parte di uno StackPanel che fa parte
di una Window. Comincio a dire alla Window che alle coordinate (100, 100) è avvenuto un
click: se questa è interessata a stopparlo bene, altrimenti si prosegue a catena fino al
bottone (e se ha un listener fa delle cose). Se l’evento è arrivato fino al bottone, risale
nuovamente la gerarchia fino alla Window.
Eventi preview
Si parte dall’elemento root scendendo fino all’elemento target. Quest’operazione è detta
tunnel e consente a tutti gli elementi intermedi dell’albero di filtrare o reagire all’evento.
Eventi effettivi
Se – come accade il più delle volte – il preview non fa niente, l’evento arriva sull’elemen-
to che lo ha generato e da questo viene inoltrato fino all’elemento root. L’operazione av-
viene dopo la ricezione di un evento preview ed è detta bubble.
Templating
Nella classe System.Windows.Controls.Control (che contiene tutto ciò che ha una pre-
sentazione grafica ed è interattivo) troviamo la definizione di alcuni template di stili, che
permettono la resa grafica in maniera facile. E’ possibile cambiare completamente
l’aspetto e il comportamento dei nostri controlli.
Tra i controlli ci sono alcuni contenitori, che sono dei blocchi logici che usano il proprio
spazio per ripartirlo tra i figli (tipo lo StackPanel o la Canvas).
***