You are on page 1of 7

M20.

WINDOWS PRESENTATION FOUNDATION – WPF


Introduzione
WPF è un modo per creare interfacce grafiche un po’ belline. Windows Form usa infatti il
motore GDI che risale ai tempi di “Hanno ucciso l’uomo ragno”: per quanto faccia co-
munque la sua porca figura, ha delle limitazioni legate al modello retrostante. Non è in-
fatti supportato il concetto di semitrasparenza, mentre alcune cose – come le animazioni
– sono implementate introducendo un botto di cicli di CPU.

Windows Presentation Foundation


Microsoft così nel 2006 si è sforzata di cercare una
seconda strada a GDI, utilizzando il motore grafico
delle DirectX, che fa sintetizzare alla GPU un insie-
me di operazioni. Questa strada è Windows Pre-
sentation Foundation, tramite cui è possibile co-
struire interfacce grafiche atte a una pluralità di
applicazioni (documenti, contenuti multimediali,
grafica 2D/3D). Facilita l’integrazione di legacy code, cioè codice preesistente che sfrut-
tava le API base di Windows.

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.

In GDI si usava immediatamente l’algoritmo di Bresenham per “accendere” i vari pixel di


un elemento grafico, andando dunque a utilizzare la grafica raster che non dà buoni ri-
sultati in caso di zoom. WPF invece è interamente basato grafica vettoriale, utilizzando
linee, archi di cerchio/ellisse e curve di Bézier che vengono salvati e passati alla GPU
che sintetizza alla miglior risoluzione possibile.

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.

Modalità immediate vs. modalità retained


GDI (ma anche Java, librerie C++, Qt e compagnia bella) uti-
lizzano, nel disegnare, una modalità immediate: ogni volta
che il sistema capisce che il rettangolo in cui è ospitata la fi-
nestra dell’utente si è “sporcato”, viene mandato un mes-
saggio per ridisegnarla. Questo perché il sistema non ricorda
cosa ciascuna finestra stia visualizzando, quindi ogni finestra
è responsabile di mantenere il suo stato: in Windows questo
sistema è basato sulla coda dei messaggi WM_PAINT che scatenano l’azione di una serie
di routine del programma.

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.

Il grosso vantaggio è che non c’è più bisogno di occuparsi


del ridisegno, enunciando solo “che cosa si vuole essere”.
C’è più efficienza perché si può fare del caching delle operazioni di rendering, sfruttando
al meglio la scheda video.

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

In alcune situazioni è possibile usare un misto dei due linguaggi.

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

public partial class Window1 : Window {


public Window1() {
InitializeComponent();
StackPanel stackPanel = new StackPanel(); /* Layout */
this.Content = stackPanel;
this.Background = new LinearGradientBrush(Colors.Wheat,Colors.White,
new Point(0,0), new Point(1,1));
TextBlock textBlock = new TextBlock();
textBlock.Margin = new Thickness(20);
textBlock.FontSize = 20;
textBlock.Foreground = Brushes.Blue;
textBlock.Text = "Hello World";
stackPanel.Children.Add(textBlock);
Button button = new Button();
button.Margin= new Thickness(10);
button.Height = 25; button.Width = 80;
button.HorizontalAlignment = HorizontalAlignment.Right;
button.Content = "OK";
stackPanel.Children.Add(button);
}
}

La stessa cosa si può scrivere in XAML

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

La coda è incapsulata in un oggetto chiamato dispatcher, conosciuto da tutti gli oggetti


che compongono un’applicazione WPF (perché tutti estendono DispatcherObject). La
coda non è ordinata e riconosce che ci sono
alcune cose più prioritarie.

Sia UIThread che RenderingThread fanno ac-


cesso al dispatcher. Il primo inserisce richie-
ste a fronte del verificarsi di un evento e leg-
ge i messaggi di notifica da parte degli altri
thread, mentre il secondo riceve richieste di inizio di un’attività e inserisce messaggi
sull’esito delle attività svolte.

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

Classi base in WPF


Derivano da Object. A parte VisualTreeHelper che aiuta
a gestire l’albero, tutte le altre classi derivano da Di-
spatcherObject per conoscere l’identità del dispatcher
corrente.

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 Visual si tripartisce in un certo numero di sottoclassi, specializzate nel creare


elementi visivi di qualche tipo.

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

Dipendenze tra proprietà: classe System.Windows.DependencyObject


Ogni componente, come in C# base, può avere delle proprietà. Queste proprietà posso-
no anche essere vincolate alle proprietà di altri oggetti: una volta che due proprietà
sono collegate, il sistema garantisce il forwarding dei valori attraverso l’albero logico
della scena.

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.

Visualizzazione di oggetti: classe System.Windows.Media.Visual


Un oggetto graficamente rappresentabile deve essere istanza di una sottoclasse di Sy-
stem.Windows.Media.Visual, che è la base per implementare nuovi controlli. Introduce
un meccanismo di “caching” delle istruzioni di ridisegno per massimizzare le prestazioni.

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.

Non viene istanziato direttamente, ma acquisito.


*** Inizio appunti dell’AA 2013-2014 ***

/* Crea un oggetto DrawingVisual che conterrà un rettangolo */


private DrawingVisual CreateDrawingVisualRectangle(Point p, Size s) {
DrawingVisual drawingVisual = new DrawingVisual();
/* Si richiede il DrawingContext associato */
DrawingContext drawingContext = drawingVisual.RenderOpen();
/* Crea un rettangolo e lo inserisce nel DrawingContext. */
Rect rect = new Rect(p, s);
drawingContext.DrawRectangle(Brushes.LightBlue, (Pen)null, rect);
/* Affida il contenuto del DrawingContext al sotto-sistema grafico */
drawingContext.Close();
return drawingVisual;
}

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

***

You might also like