Introduzione a Windows Presentation Foundation

Di Corrado Cavalli - Microsoft MVP

In questa pagina

Introduzione Introduzione
Unire le varie tecnologie Unire le varie tecnologie
Dove trovo WPF? Dove trovo WPF?
Hello World? Hello WPF! Hello World? Hello WPF!
Professione “Grafico” Professione “Grafico”
Il linguaggio XAML Il linguaggio XAML
 Navigation Applications, Xbap Applications e WPF/E  Navigation Applications, Xbap Applications e WPF/E
 Stili  Stili
DataBinding DataBinding
Animazioni Animazioni
Interoperabilità Windows Forms e Windows Presentation Foundation Interoperabilità Windows Forms e Windows Presentation Foundation
Tools di sviluppo Tools di sviluppo
Conclusioni Conclusioni

Introduzione

E’ ormai innegabile il fatto che il design sia un componente essenziale nel successo di un prodotto, quotidianamente i nostri acquisti sono, almeno inizialmente, diretti verso quei prodotti che esteticamente ci appagano di più e basta guardarsi attorno per capire come il design sia ormai parte integrante della nostra quotidianità.
Se cosi è per la vita di tutti i giorni, non possiamo dire altrettanto per un mondo che ci riguarda da vicino: Le applicazioni Windows.

Mettendo a confronto un’applicazione sviluppata per Windows 3.1 con una sviluppata recentemente, è facile scoprire che esteticamente, a parte qualche piccolo dettaglio, poco è cambiato e il tutto è tutt’ora basato sulla tecnologia GDI (Graphics Device Interface) progettata 20 anni fa e incapace di sfruttare nativamente tutte le potenzialità messe a disposizione dall’hardware grafico di ultima generazione. Dopo anni di onorato servizio è perciò giunto il momento di passare al motore grafico delle applicazioni di domani, capace di cambiare il modo di pensare e rappresentare un’interfaccia utente Windows e soprattutto in grado di sfruttare l’hardware di oggi e di domani, in poche parole è giunto il momento di approcciare Windows Presentation Foundation (WPF), tecnologia in precedenza conosciuta come Avalon.

 

Unire le varie tecnologie

Creare un’applicazione in grado di interagire con documenti, grafica 3D e componenti multimediali richiede l’utilizzo di tecnologie quali Acrobat Reader per la gestione della documentazione, Direct3D per la parte tridimensionale, Windows Media Player per l’area multimediale e la gestione di tutte le problematiche legate all’interazione di tecnologie così diverse tra loro. Windows Presentation Foundation si propone di eliminare questo tipo di problemi fornendo una piattaforma unica per la realizzazione di applicazioni di “impatto” eliminando la necessità di utilizzo di tecnologie e/o componenti esterni.

 

Dove trovo WPF?

Windows Presentation Foundation insieme a WCF (Windows Communication Foundation) , WF (Workflow Foundation) e Windows CardSpace rappresentano le novità introdotte nella versione 3.0 di .NET Framework il quale è automaticamente installato su Windows Vista™ ed è disponibile come installazione separata per Windows XP Sp2 e Windows Server 2003.
Come esempio di applicazione sviluppata con WPF, se avete .NET Framework 3.0 installato potete scaricare ed eseguire l’applicazione HealthCare Prototype che trovate qui.

 

Hello World? Hello WPF!

Ecco un esempio di “Hello World” realizzato con WPF:

using System.Windows;
[STAThread()]
static void Main ()
{
Application app = new Application();
Window win = new Window();
win.Title = "Hello WPF";
app.Run(win);
}

FakePre-feed5a41ec684b38b86c3b5added04e2-0fde2b7c059e44c193ca9ac4485db517

Come vedete il tutto ruota attorno alle classi Application e Window contenute in System.Windows, la prima costituisce l’applicazione da eseguire la seconda invece rappresenta una finestra nel mondo WPF. Il metodo Run fa partire l’applicazione utilizzando come form principale la finestra ‘win’ il cui titolo è impostato a “Hello WPF”.
A prima vista nulla di particolarmente impegnativo e, soprattutto, non molto diverso da quanto andrebbe scritto se avessimo fatto la stessa cosa con un’applicazione Windows Forms. In realtà le cose non sono mai così semplici anche perché va affrontato un argomento che a prima vista potrebbe sembrare ‘inusuale’ nell’ambito Windows client, ovvero il ruolo del “Grafico” nel ciclo di vita di un' applicazione WPF.

 

Professione “Grafico”

Realizzare applicazioni d’impatto capaci di rappresentare le informazioni in modo moderno, gradevole e allo stesso tempo facili nell’utilizzo, non è cosa da poco, ecco perché spesso quest’attività è delegata a un ruolo già presente nel campo delle applicazioni WEB: il Grafico, cioè colui che si occupa dell’aspetto estetico dell’applicazione e che spesso non è un programmatore.

Pur rimanendo una figura ‘opzionale’, è chiaro che volendo affidare a un grafico il compito di costruire l’interfaccia del nostro programma è impensabile chiedere che il tutto avvenga usando del codice, questo implica la reale necessità di disaccoppiamento dei due ruoli. Da una parte il programmatore che mediante codice .NET si occupa delle funzionalità applicative, dall’altra l’eventuale grafico il quale usando un linguaggio (o strumento) ‘alternativo’ costruisce l’interfaccia utente, il tutto senza che i due ruoli si interferiscano. Tutto questo porta a un’inevitabile separazione tra interfaccia e funzionalità e all’utilizzo di un linguaggio che meglio del codice possa descrivere interfacce grafiche: il linguaggio in questione si chiama XAML.

 

Il linguaggio XAML

XAML (Extensible Application Markup Language) è un linguaggio di derivazione XML il cui compito è quello di descrivere mediante un approccio dichiarativo gerarchie, anche complesse, di oggetti. Pur essendo nato con WPF, in realtà XAML può essere impiegato in qualsiasi occasione si abbia la necessità di descrivere, attraverso un linguaggio xml “like”, grafi di oggetti. Per maggiori approfondimenti sull’utilizzo alternativo di XAML vi rimando a questo link (informazioni in lingua Inglese).

Nell’ambito di questa semplice introduzione, è sufficiente sapere che quanto descritto con XAML viene poi trasformato nella rispettiva gerarchia di classi WPF a dimostrazione del fatto che è comunque possibile scrivere intere applicazioni WPF senza l’ausilio di XAML sebbene il suo utilizzo semplifichi notevolmente la descrizione della parte grafica.
Riprendendo l’esempio mostrato in precedenza, l’equivalente XAML è il seguente:

<Window x:Class="HelloWPF.Window1"
    xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml"
    Title="HelloWPF">    
</Window>

I vari xml-namespaces che vedete riportati sono utilizzati dal parser XAML per la generazione dei rispettivi oggetti WPF. L’esempio che segue mostra invece com’è possibile far interagire XAML con il rispettivo codice applicativo:

<Window x:Class="HelloWPF.Window1"
    xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml"
    Title="HelloWPF" MouseDoubleClick="DoubleClickHandler" Name="win1"
    Width="300" Height="200">    
</Window>

FakePre-dba17fdc8b704239b42f21242d5519f6-d6043f0acefa4e75a492047525493396 FakePre-f04615b8236e49f182b5e75a176f2b8d-3d48a4dcf65248258bf6c52526edd335

Come potete notare, l’associazione delle proprietà e l’indicazione del gestore di un particolare evento avviene mediante attributi xml, mentre valorizzando la proprietà Name è possibile accedere da codice all’oggetto descritto in XAML.

Le due parti (XAML + codice) costituiscono le due parti della classe parziale Window1 (figura 1)

*

Figura 1

Una nota particolare la meritano le proprietà Width e Height, perché i valori indicati non sono, come si potrebbe inizialmente ipotizzare pixels, ma un’unità di misura conosciuta come DIP (device independent pixels) la cui singola unità corrisponde a 1/96 di pollice. L’utilizzo dei DIP fa sì che le applicazioni possano auto dimensionarsi in base al valore DPI (dot-per-inches) impostato nel pannello di controllo di Windows ottenendo, di fatto, delle applicazioni che possono scalare in base alla risoluzione dello schermo, anche grazie al fatto che WPF è interamente basato su grafica vettoriale.

 

*

Figura 2

Ciò che rende le Navigation Applications particolarmente interessanti è che possono essere facilmente trasformate in applicazioni Xbap (Express Browser Application), ovvero applicazioni a pagina singola ospitate all’interno di Internet Explorer (versione 6 o superiore)Tecnicamente sono delle applicazioni distribuite mediante ClickOnce in modalità Online, ciò significa che girano in un contesto di sicurezza limitato e quindi, oltre a non poter utilizzare tutte le feature di WPF, sono soggette a tutte le limitazioni che la sandbox che le ospita impone. La figura 3 mostra la stessa Navigation Application mostrata in precedenza migrata in applicazione XBApp.

*

Figura 3

Se avete .NET Framework 3.0 installato potete provare voi stessi un’applicazione Xbap semplicemente cliccando qui. Quanto mostrato finora è chiaramente legato a .NET Framework 3.0 e di conseguenza alla piattaforma Windows, volendo però utilizzare WPF all’interno di applicazioni ASP.NET è possibile utilizzare WPF/E (/Everywhere).
WPF/E è sottoinsieme di WPF non vincolato a Internet Explorer e in grado, attualmente, di girare su piattaforma Windows e Macintosh grazie ad un plug-in gratuito scaricabile dal sito Microsoft.

 

Stili

Il concetto di stile, da sempre presente nel mondo web, diventa ora parte integrante delle applicazioni Windows. Attraverso gli stili è possibile definire in un unico punto le caratteristiche grafiche dei controlli presenti in una finestra.
Questo esempio:

<Window x:Class="Styles.Window1"
    xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml"
    Title="Styles" Height="135" Width="299" Name="win1">

<Window.Resources>
<Style TargetType="{x:Type Button}">
<Setter Property="Control.Background" Value="Yellow" />
<Setter Property="Control.BorderBrush" Value="Red" />
</Style>
<Style x:Key="MyStyle">
<Setter Property="Control.Background" Value="Cyan" />
<Setter Property="Control.BorderBrush" Value="Red" />
</Style>
</Window.Resources>
<StackPanel>
<Button Width="100" Margin="5" Name="b1">Hello</Button>
<Button Width="100" Margin="5" Style="{StaticResource MyStyle}"
 Name="b2">from</Button>
<Button Width="100" Margin="5" Name="b3">WPF</Button>
</StackPanel></Window>

Ha come risultato la finestra di figura 4

*

Figura 4

All’interno delle risorse associate alla finestra (Windows.Resources) sono stati definti due stili, il primo è uno stile di tipo che quindi agisce su tutti i controlli di tipo Button contenuti nella finestra, il secondo è uno stile nominativo utilizzato dal pulsante denominato b2 attraverso l’associazione alla proprietà Style che i tutti controlli espongono.
Com’è possibile notare, uno stile non fa altro che modificare le proprietà del controllo al quale è stato applicato.
Ciò che differenzia gli stili di WPF dai fogli di stile del mondo web è la possibilità di avere dei trigger associati ovvero delle condizioni che regolano l’applicazione dello stile.L’esempio che segue mostra un esempio di trigger (Property trigger) condizionato dalla proprietà IsMouseOver del controllo Button, quando la proprietà è vera lo stile verrà applicato riportando automaticamente le proprietà Background e BorderBrush al valore precedente non appena IsMouseOver=False.

<Style TargetType="{x:Type Button}">
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Control.Background" Value="Yellow" />
<Setter Property="Control.BorderBrush" Value="Red" />
</Trigger>
</Style.Triggers>
</Style>

I trigger possono essere associati anche allo scatenarsi di eventi (Event trigger) oppure alla valorizzazione di una proprietà associata al controllo mediante Databinding (Data trigger) così com’è possibile realizzare dei trigger basati su combinazioni di più proprietà realizzando, in questo caso, dei MultiTrigger.

 

DataBinding

In WPF il databinding è parte integrante della piattaforma, questo significa che non è solamente un aspetto da utilizzare quando s’interagisce con dei dati ma che è, a tutti gli effetti, il meccanismo che WPF utilizza per legare tra loro i vari elementi presenti in un’interfaccia utente.

...
<StackPanel>
       <Slider Minimum="0" Maximum="20" LargeChange="5" SmallChange="1" Value="0"
               Name="slider" />          
          <Image Source="images/monte.jpg">
             <Image.BitmapEffect>
                <BlurBitmapEffect Radius="{Binding ElementName=slider,Path=Value}"  />
             </Image.BitmapEffect>
          </Image>          
       </StackPanel>
...

Il frammento di XAML sopra riportato mostra come attraverso DataBinding, in maniera dichiarativa, è possibile connettere la proprietà Radius dell’oggetto BlurBitmapEffect applicato all’oggetto Image alla proprietà Value dell’elemento identificato come “slider”, ottenendo il risultato in figura 5.

*

Figura 5

Ciò che rende il Databinding di WPF diverso da quello presente nelle applicazioni Windows Forms è il fatto di poter, finalmente, descrivere come visivamente deve apparire un determinato tipo di dato.

<Window x:Class="D09.DataBinding.ComplexBinding"
    xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="clr-namespace:D09.DataBinding"
    Title="List DataBinding" Height="450" Width="400"
    >
   <Window.Resources>
      <DataTemplate DataType="{x:Type d:Car}">
         <Border BorderBrush="Green" 
BorderThickness="3" CornerRadius="10" Margin="10,10,10,10" 
Width="200" Background="LightYellow">
            <StackPanel Orientation="Vertical" Margin="5,0" >
               <TextBlock Text="{Binding Path=Model}" Foreground="Red" FontSize="25" FontWeight="Bold"/>
               <TextBlock Text="{Binding Path=Price}" Foreground="Black" FontSize="14" />
            </StackPanel>
         </Border>
      </DataTemplate>
   </Window.Resources>   
    <StackPanel>
       <ListBox Height="150" Background="Beige" Margin="10,3" 
                Name="carsList" 
                ItemsSource="{Binding}"     
                >
       </ListBox>
    </StackPanel>
</Window>

FakePre-c5f47906356746fab86f8a51d9535c2f-e512485df869474c82cddacc1b64e841 FakePre-48296cc560f944db8153a7ec3cb0927b-f5b88850925a41389772290dc95459ba

L’esempio indicato mostra una listbox la cui proprietà ItemSource è collegata a un insieme di classi Car attraverso la proprietà ItemSource associata al valore "{Binding}”,questa particolare espressione fa si che WPF, partendo dalla listbox stessa, scorra tutti i controlli parent della listbox alla ricerca di un controllo che abbia la proprietà DataContext valorizzata. Nel nostro caso la ricerca terminerà raggiunto l’oggetto Window in quanto la rispettiva proprietà DataContext è stata associata da codice all’insieme AvailableCars all’interno del metodo InitializeComponent().A questo punto la listbox, dovendo rappresentare visivamente un insieme di oggetti Car verificherà la presenza di un DataTemplate associato al tipo Car e, se presente, lo utilizzerà come template per renderizzare il relativo item.Nel nostro caso il DataTemplate è definito nell’area risorse della finestra ed è associato al tipo Car attraverso la proprietà DataType dell’oggetto DataTemplate.L’esempio mostra anche come in XAML, attraverso la definizione di un opportuno xml-namespace (xmlns:d="clr-namespace:D09.DataBinding") sia possibile utilizzare qualsiasi tipo oltre a quelli già presenti nel namespace WPF.Il risultato è visibile in figura 6.

*

Figura 6

 

Animazioni

Il concetto di animazione non è di per sé nuovo, WPF però ci permette di poterle definire direttamente in XAML in maniera dichiarativa.

In WPF il ruolo di un’animazione è quello di modificare dinamicamente il valore della proprietà del controllo (TargetProperty) al quale è applicata (TargetName), a seconda del tipo di proprietà da modificare esiste il relativo tipo di animazione.

Prendiamo ad esempio il codice che segue:

<Window x:Class="Animations.Window1"
    xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml"
    Title="Animations" Height="300" Width="350">
<Window.Triggers>
<EventTrigger RoutedEvent="Window.Loaded">
<EventTrigger.Actions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimation Storyboard.TargetName="MyEllipse" 
 Storyboard.TargetProperty="(Ellipse.Width)" 
From="10" To="300" Duration="0:0:3" 
RepeatBehavior="Forever" AutoReverse="True" />
</Storyboard>
</BeginStoryboard>
</EventTrigger.Actions>
</EventTrigger>
</Window.Triggers>
<Grid>
<Ellipse x:Name="MyEllipse" Fill="Orange" Width="50" />
</Grid>
</Window>

Al caricamento della finestra (evento Loaded) viene attivata una DoubleAnimation, ovvero un' animazione che può agire su proprietà di tipo double, la quale modifica il valore della proprietà Width dell’ellisse denominato “MyEllipse” portandolo in 3 secondi dal valore 10 al valore 300. Essendo la proprietà AutoReverse della DoubleAnimation impostata a True il valore verrà poi riportato, nello stesso tempo, al valore iniziale e vista la presenza dell’attributo RepeatBehaviour =”Forever” l’animazione verrà poi ripetuta all’infinito.

L’oggetto StoryBoard funge da contenitore di animazioni, quindi può contenere al proprio interno diversi tipi di animazioni che possono anche agire contemporaneamente sullo stesso controllo come mostrato nell’esempio seguente.

...
<BeginStoryboard>

<Storyboard Storyboard.TargetName="MyEllipse" >
<DoubleAnimation From="10" To="300" Duration="0:0:3" Storyboard.TargetProperty="(Ellipse.Width)"
RepeatBehavior="Forever" AutoReverse="True"/>
<ColorAnimation From="Orange" To="Blue" Duration="0:0:5"
 AutoReverse="True" RepeatBehavior="2x" 
Storyboard.TargetProperty="(Ellipse.Fill).Color" />
</Storyboard>
</BeginStoryboard>
...

 

Interoperabilità Windows Forms e Windows Presentation Foundation

Vista la miriade di applicazioni Windows Forms è ovvio che le due tecnologie devono poter interoperare tra loro, anche perché, in alcuni casi, non esiste la controparte WPF di alcuni importanti controlli Windows forms come ad esempio la DatagridView.

L’interoperabilità tra i due mondi è garantita attraverso due controlli: WindowsFormsHost e ElementHost.

WindowsFormsHost è un controllo in grado di ospitare al suo interno un controllo Windows Forms permettendone l’utilizzo in interfaccia WPF.

Esempio (richiede un riferimento a System.Windows.Forms.dll)

<Window x:Class="Window1"
    xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:wf="clr-namespace:System.Windows.Forms;assembly=System.Windows.Forms"    
    Title="Interoperability">

   <StackPanel>
      <WindowsFormsHost Margin="20,10" Height="100">
         <wf:DataGridView x:Name="winDatagrid" CellClick="HandleCellClick"/>
      </WindowsFormsHost>       
       <WindowsFormsHost Margin="20,5" Height="200">
          <wf:MonthCalendar x:Name="calendar"/>
       </WindowsFormsHost>
    </StackPanel>
</Window>

La finestra Window1 contiene una DataGridView e un controllo MonthCalendar, il tutto è ottenuto importando in XAML il namespace System.Windows.Forms, associandolo al relativo prefisso xml (wf) e ospitando i controlli Winforms all’interno di un controllo WindowsFormsHost. Ovviamente tutte le caratteristiche tipiche dei controlli WPF (scaling, rotazione, opacity...) non sono applicabili a controlli ospitati in WindowsFormsHost.

ElementHost è invece un controllo Windows Forms in grado di ospitare controlli WPF come mostrato nell’esempio seguente:

using System.Windows.Forms.Integration;
using System.Windows.Controls;
using System.Windows.Media.Effects;

   public partial class WinForm : Form
   {
      System.Windows.Controls.Button _wpfButton;
      ElementHost _host = new ElementHost();

      public WinForm()
      {
         InitializeComponent();
         //Aggiungo un pulsante WPF...
         _wpfButton = new System.Windows.Controls.Button();
         _wpfButton.Width = 50;
         _wpfButton.Height = 10;
         _wpfButton.Content = "WPF Button!";
         _wpfButton.LayoutTransform = 
            new System.Windows.Media.RotateTransform(45, _host.Width / 2.0, _host.Height / 2.0);
         _wpfButton.Background = 
new System.Windows.Media.LinearGradientBrush(System.Windows.Media.Colors.Red,
 System.Windows.Media.Colors.Orange, 
new System.Windows.Point(0, 0), new System.Windows.Point(0, 1));         
         //Inserisco il controllo WPF nell'host
         _host.Left = 20;
         _host.Dock = DockStyle.Fill;
         _host.Child = _wpfButton;

         //Aggiungo all collezione di controlli
         panel1.Controls.Add(_host);
      }

FakePre-e59726c414754074bb90d18a4775e8d2-82efb0ffa7664498ad7ff13207ab4987

In questo caso l’applicazione Windows Forms 2.0 richiede un riferimento alle assemblies WPF: PresentationCore.dll, PresentationFramework.dll, UIAutomationProvider.dll, UIAutomationTypes.dll, WindowsBase.dll e WindowsFormsIntegration.dll.

Il risultato è visibile nelle figure 7 e 8

*

Figura 7

*

Figura 8

 

Tools di sviluppo

Come potete facilmente immaginare descrivere una semplice interfaccia utente richiede una notevole quantità di righe di XAML e la nota dolente è che, pur essendo disponibili, gli strumenti di sviluppo d’interfacce WPF non sono ancora disponibili in versione RTM.
Gli strumenti attualmente disponibili sono:

  • Cider
    Il designer integrato nella prossima versione di Visual Studio (Orcas) e che è attualmente disponibile come add-on separato per Visual Studio 2005. Le prossime CTP (Community Technology Preview) saranno disponibili esclusivamente all’interno delle CTP di Visual Studio “Orcas”.

  • Expression Blend
    Tool esterno, parte della famiglia Expression, diretto principalmente ai non-sviluppatori.

  • Aurora
    Tools per il disegno d’interfacce grafiche alternativo a Expression Blend, prodotto da Mobiform.

  • ZAM3D
    Tool specializzato per la creazione d’interfacce XAML 3D sviluppato da Electric Rain.

 

Conclusioni

Anche se le applicazioni Windows Forms continueranno a vivere a lungo, la nuova era delle interfacce utente è iniziata. E’ quindi giunto il momento di iniziare a esplorare questa nuova tecnologia e, sopratutto, è giunta l’ora di pensare a una futura migrazione verso WPF delle applicazioni Windows Forms di oggi.Un consiglio per facilitare questo compito? Organizzare la propria applicazione affinché interfaccia grafica e implementazione siano totalmente indipendenti; un aiuto in questa direzione si può avere applicando il pattern Model View Presenter.

Riferimenti (informazioni in lingua Inglese)