Introduzione a Windows Presentation Foundation (terza parte)
a cura di Alessandro Del Sole (requisiti: aver letto i primi due articoli della serie)

Premessa
Nel secondo articolo dedicato alla serie introduttiva su Windows Presentation Foundation, ci siamo proposti di realizzare un semplice lettore multimediale e ne abbiamo disegnato la struttura di base, rendendolo capace di riprodurre un file video prestabilito.
In questo articolo, invece, daremo la possibilità all'utente di selezionare un file audio o video a piacere; inoltre renderemo più gradevole l'interfaccia grafica ed aggiungeremo qualche funzionalità.

Prima di procedere, mi preme fare un paio di precisazioni. In primo luogo, in questo articolo introdurremo molti nuovi e fondamentali concetti della programmazione basata su WPF, quindi ho cercato il più possibile di suddividere le novità in parti distinte, in modo che possiate prendetevi il vostro tempo e focalizzare l'attenzione su ogni singolo aspetto. In secondo luogo, l'applicazione a corredo può, in questa sede, essere solo un piccolo esempio; WPF è una tecnologia dalla potenza impressionante e riassumerne tutte le caratteristiche e le funzionalità in una serie introduttiva di articoli è impensabile. Al contrario, anche se i concetti introdotti vi sembreranno tantissimi, ho cercato di proporli in modo tale che vi rimangano come flash da poter approfondire da soli. Nel futuro, ci occuperemo di esaminare nel dettaglio altri aspetti di WPF ma, al momento, l'importante è capire come ci si muove e acquisire una certa "mentalità". Al termine dell'articolo, troverete alcuni spunti di studio per poter migliorare autonomamente il layout e le funzionalità del nostro lettore multimediale.

Anche in questo caso, il codice sorgente completo, sia in Visual Basic 2005 che Visual C#, è disponibile nell'area download di VB T&T, ma è preferibile scaricarlo al termine della lettura dell'articolo, poiché il modo migliore per imparare i nuovi concetti è quello di leggere e provare i singoli frammenti di codice.

Uno sfondo a gradiente per la finestra
Avviate Visual Studio 2005 ed aprite il progetto MyMediaPlayer, iniziato la volta precedente. Il primo passo che vogliamo fare per abbellire il nostro programma è quello di creare uno sfondo a gradiente per la finestra. In WPF è possibile modificare lo sfondo della maggior parte dei controlli assegnando un valore alla proprietà Background di ciascuno di essi. È interessante sapere che è possibile assegnare come sfondo sia un colore a tinta unita, sia il risultato di un oggetto Brush. Brush è una classe astratta, che viene ereditata da altre classi (come, ad esempio, ImageBrush, GradientBrush, LinearGradientBrush, RadialGradientBrush). Ci proponiamo, in questo caso, di fare in modo che la nostra finestra abbia uno sfondo a gradiente lineare. Pertanto, utilizzeremo un oggetto LinearGradientBrush. Dopo il tag iniziale e prima del tag di inizio dell'elemento Grid, digitate il seguente codice XAML:

    <Window.Background>
      <LinearGradientBrush>
        <GradientStop Color="Black" Offset="0.4" />
        <GradientStop Color="Gainsboro" Offset="0.8" />
        <GradientStop Color="White" Offset="1" />
      </LinearGradientBrush>
    </Window.Background>

Il nostro gradiente parte dal nero ed arriva al bianco. Ogni cambio di colore è rappresentato da un elemento GradientStop, i cui attributi Color e Offset rappresentano, rispettivamente, il colore e la posizione. La finestra, quindi, assume il seguente aspetto:

In Windows Forms, o peggio ancora in VB 6, ottenere un risultato del genere sarebbe stato assai complesso. La prossima funzionalità che aggiungeremo all'applicazione è quella che consente all'utente di selezionare il contenuto multimediale da riprodurre.

Un pulsante per selezionare il contenuto da riprodurre.
Aggiungete, quindi, il seguente codice XAML prima della definizione dei precedenti pulsanti:

    <Button Click="Seleziona" Width="75">
      Apri
    </Button>

Come potete osservare, abbiamo specificato un valore per l'attributo Width in modo da fissare la larghezza del pulsante. Per rendere uniforme il layout, aggiungete il medesimo attributo a tutti gli altri pulsanti. Abbiamo poi comunicato a XAML che, quando l'utente fa clic sul pulsante, deve essere richiamato il metodo Seleziona, che consentirà all'utente di selezionare il file da riprodurre mediante un controllo OpenFileDialog. WPF implementa un proprio controllo OpenFileDialog, in maniera analoga a Windows Forms. Tuttavia, questo controllo è un wrapper di quello nativo di Windows (infatti la denominazione della classe è Microsoft.Win32.OpenFileDialog). Al di là dell'aspetto grafico obsoleto di quest'ultimo, può essere conveniente utilizzare quello di Windows Forms anche per capire come far interagire le due tecnologie .NET. Aggiungete un riferimento all'assembly System.Windows.Forms.dll (comando Aggiungi riferimento del menu Progetto). Fatto questo, passate al file di code-behind ed aggiungete il seguente metodo (presuppone l'aggiunta di una direttiva Imports System.Windows.Forms per VB o using System.Windows.Forms; per C#):

  Private sorgente As Uri
  Dim openDialog As New OpenFileDialog

  Private Sub Seleziona(ByVal sender As Object, ByVal e As RoutedEventArgs)

    Try
      With openDialog
        .Title = "Seleziona contenuto multimediale"
        .Filter = "Video|*.asf;*.wmv|Audio|*.mp3;*.wma|Tutti i file|*.*"

        If .ShowDialog = Forms.DialogResult.OK And String.IsNullOrEmpty(.FileName) = False Then
          sorgente = New Uri(.FileName)
        End If
      End With

    Catch ex As Exception
      MessageBox.Show(ex.ToString)
    End Try
  End Sub
    private Uri sorgente;
    OpenFileDialog openDialog = new OpenFileDialog();
    private void Seleziona(object sender, RoutedEventArgs e)
    {
      try
      {
        openDialog.Title = "Seleziona contenuto multimediale";
        openDialog.Filter = "Video|*.asf;*.wmv|Audio|*.mp3;*.wma|Tutti i file|*.*";

        if (openDialog.ShowDialog() == System.Windows.Forms.DialogResult.OK)
        {
          if (String.IsNullOrEmpty(openDialog.FileName) == false)
          {
            sorgente = new Uri(openDialog.FileName);
          }
        }
      }
      catch (Exception ex)
      {
        System.Windows.Forms.MessageBox.Show(ex.ToString());
      }
    }

Il campo Sorgente ci serve per memorizzare il nome del file selezionato dall'utente sotto forma di oggetto URI, il tipo di dato riconosciuto dal controllo MediaElement. Tramite la finestra di dialogo, lasciamo all'utente la decisione sul tipo di file da aprire. È interessante osservare come sia possibile riprodurre file audio .mp3 e .wma senza l'aggiunta di altri componenti. La condizione è che l'utente faccia clic su OK e il nome del file non sia vuoto. In questo caso, il nome del file selezionato viene convertito in URI e assegnato al campo sorgente. Per praticità, ho implementato l'intercettazione di un'eccezione generica, tuttavia in uno scenario reale dovreste prevedere l'intercettazione di specifiche eccezioni.
Ci sono ora da fare un paio di modifiche al controllo MediaElement e al codice che lo gestisce. Passate all'editor XAML e modificate il codice dell'elemento MediaElement come segue:

<MediaElement Name="myMedia" LoadedBehavior="Manual" UnloadedBehavior ="Stop" Margin="10,10,10,10"
              Width="320" Height="240" Volume="5" Stretch="Fill" />

Abbiamo eliminato l'attributo Source, poiché sarà specificato dal nostro codice. Abbiamo poi impostato dei margini per staccare il controllo dai bordi della finestra (attributo Margin). Tramite la proprietà Stretch, poi, stiamo dicendo all'applicazione di ridimensionare il video riprodotto affinché sia contenuto nel controllo. L'aggiunta dell'attributo Volume ci servirà più avanti, al momento sappiate che è possibile impostare il volume della riproduzione su un valore compreso tra zero e dieci. Fatte queste modifiche, l'applicazione si presenta come in figura:

In considerazione dell'aggiunta di un pulsante per la selezione del contenuto da riprodurre, è necessario modificare il codice del metodo Riproduci, implementato la volta precedente, così come segue:

  Private Sub Riproduci(ByVal sender As Object, ByVal e As RoutedEventArgs)
    Try
      myMedia.Source = sorgente
      myMedia.Play()
    Catch ex As Exception

      MessageBox.Show(ex.ToString)
    End Try
  End Sub
    private void Riproduci(object sender, RoutedEventArgs e)
    {
      try
      {
        myMedia.Source = sorgente;
        myMedia.Play();
      }
      catch (Exception ex)
      {
        System.Windows.MessageBox.Show(ex.ToString());
      }
    }

Allo stato attuale, l'applicazione è in grado di riprodurre contenuti multimediali selezionati dall'utente, tuttavia ci sono ancora alcune modifiche da fare prima che, a livello di layout grafico, il tutto sia visualizzato in maniera corretta.
Per il momento, è importante aver acquisito la nozione che in WPF è possibile utilizzare anche i controlli di Windows Forms.

Uno stile per i pulsanti
Windows Presentation Foundation espone una eccezionale funzionalità: gli stili. Uno stile è, in parole povere, un insieme di proprietà che può essere assegnato ad un controllo per influenzarne l'aspetto grafico o il comportamento. È possibile definire uno stile per un determinato tipo di controllo e far sì che tutti i controlli di quel tipo possano conformarsi allo stesso stile. Gli stili vengono memorizzati prevalentemente nelle risorse di un contenitore.
Nel nostro esempio andremo a definire uno stile per i pulsanti, memorizzandolo nelle risorse della finestra. Digitate il seguente codice XAML, appena dopo il frammento che disegna lo sfondo della finestra:

    <Window.Resources>
      <Style x:Key="MyButton" TargetType="Button">
        <Setter Property="Margin" Value="2"/>
        <Setter Property="Background" >
          <Setter.Value>
            <LinearGradientBrush>
              <GradientStop Color="Yellow" Offset="0"/>
              <GradientStop Color="Orange" Offset="0.5"/>
              <GradientStop Color="Red" Offset="1"/>
            </LinearGradientBrush>
          </Setter.Value>
        </Setter>
      </Style>
    </Window.Resources>

L'elemento Window.Resources identifica le risorse interne a quel particolare oggetto. Uno stile è definito dall'elemento Style e può essere identificato tramite il valore dell'attributo x:Key. L'attributo TargetType determina il tipo di controllo a cui lo stile può essere applicato. L'elemento Setter consente di determinare il valore delle varie proprietà del controllo, mediante l'attributo Property. Nel nostro esempio stiamo semplicemente affermando che ogni pulsante dista due pixel dal controllo che lo precede e stiamo definendo uno sfondo a gradiente. Potreste, ad esempio, voler impostare il font per i caratteri e le relative dimensioni. Sarebbe sufficiente impostare, tramite Setter, le proprietà FontFamily e FontSize sui valori desiderati. Una volta definito lo stile, è necessario che i controlli desiderati vi facciano riferimento. Modificate, quindi, il codice XAML relativo al disegno dei pulsanti come segue:

    <Button Style="{StaticResource MyButton}" Click="Seleziona" Width="75">
      Apri
    </Button>
    <Button Style="{StaticResource MyButton}" Name="Play" Width="75" Click="Riproduci">
      Riproduci
    </Button>
    <Separator/>
    <Button Style="{StaticResource MyButton}" Name="Stop" Width="75" Click="Interrompi">
      Interrompi
    </Button>
    <Button Style="{StaticResource MyButton}" Name="Pause" Width="75" Click="Pausa">
      Pausa
    </Button>

Tramite l'attributo Style è possibile assegnare lo stile desiderato al controllo, che viene identificato tramite l'identificatore specificato all'interno dello stile stesso, come risorsa statica (StaticResource). Il risultato a video del codice digitato è il seguente:

La caratteristica degli stili è molto potente e versatile ed è uno degli spunti di studio che potete tenere a mente. Sicuramente ricorderete come, all'inizio dell'articolo, sia stato specificato un attributo Volume per il controllo MediaElement. Implementeremo, ora, la possibilità di modificare il volume del contenuto multimediale da riprodurre, mediante controlli e relativo codice.

Controllare il volume: i controlli Label e Slider
Ogni lettore multimediale che si rispetti permette di controllare il volume della riproduzione. Per fare questo, nel nostro esempio utilizzeremo un controllo Slider, il cui aspetto è simile a quello analogo di Windows Forms. Poiché vogliamo disegnare il controllo Slider al di sotto del controllo MediaElement, corredato da una descrizione mostrata in una Label, dobbiamo racchiudere i tre controlli all'interno di uno StackPanel. All'interno del codice XAML, posizionatevi sotto il tag di chiusura dell'elemento Border e sostituite il codice relativo all'elemento MediaElement con il seguente:

    <StackPanel Orientation="Vertical" Grid.Row="1" VerticalAlignment="Top" >
      <MediaElement Name="myMedia" LoadedBehavior="Manual" UnloadedBehavior ="Stop"
                    Margin="10,10,10,10" Width="320" Height="240" Volume="0" Stretch="Fill" />
      <Label Foreground="Orange" Margin="-1,0,1,10" Height="25"
             VerticalAlignment="Bottom">Volume</Label>
      <Slider Name="volumeSlider" ValueChanged="volumeChanged" Height="25" Margin="1,-10,-1,10"
              VerticalAlignment="Bottom" Minimum="0" Maximum="10" Value="5" />
    </StackPanel>

Le proprietà relative al controllo Label si spiegano da sole. Focalizzate l'attenzione, invece, sulle proprietà del controllo Slider. Minimum, Maximum e Value indicano, rispettivamente, il valore minimo, il valore massimo e il valore corrente che il controllo può assumere. Quando l'utente sposta il selettore, viene generato un evento ValueChanged, che nel nostro caso è gestito dal metodo volumeChanged. Passate al file di code-behind e digitate il codice relativo al gestore dell'evento:

  Private Sub volumeChanged(ByVal sender As Object, _
                            ByVal e As RoutedPropertyChangedEventArgs(Of Double))
    myMedia.Volume = volumeSlider.Value
  End Sub
    private void volumeChanged(object sender, RoutedPropertyChangedEventArgs<System.Double> e)
    {
      myMedia.Volume = volumeSlider.Value;
    }

Al variare della posizione del selettore, viene aggiornata la proprietà Volume del controllo MediaElement. Indipendentemente dalla lunghezza "grafica" del controllo Slider, WPF calcola automaticamente il valore da assegnare alla proprietà Volume grazie a una proporzione. Dopo queste modifiche, l'applicazione si presenta così:

Fate attenzione al fatto che il selettore viene posizionato a metà (valore 5). Questo avviene perché l'evento ValueChanged del controllo Slider viene generato anche in fase di avvio dell'applicazione (se non ci credete, provate a impostare il volume dell'elemento MediaElement su zero :) ). Nel prossimo paragrafo utilizzeremo ancora i controlli Label e Slider, ma per uno scopo diverso: applicare un effetto di trasformazione.

Le trasformazioni
L'effetto di trasformazione influenza l'aspetto grafico di un controllo. È possibile, ad esempio, applicare un effetto di trasformazione per ruotare, inclinare e spostare un controllo rispetto alle assi X e Y originarie. In WPF le trasformazioni vengono applicate mediante oggetti Transform: RotateTransform, SkewTransform e ScaleTransform sono alcuni esempi. Sul mio blog trovate diversi esempi di trasformazione applicati a controlli ListBox, in questo esempio ci proponiamo di consentire all'utente di inclinare il contenuto multimediale mediante l'effetto SkewTransform. Questo effetto inclina lateralmente il controllo creando un effetto tridimensionale. Come vedremo meglio nel prossimo paragrafo, spiegando il concetto di data-binding, utilizzeremo ancora un controllo Slider. Al variare della posizione del selettore, dovrà automaticamente variare l'inclinazione del controllo.

In primo luogo, disegnamo il controllo Slider e la relativa Label descrittiva, posizionando il seguente codice XAML immediatamente dopo quello che disegna lo Slider del volume:

    <Label Foreground="Orange" Margin="-1,-10,1,10" Height="25"
           VerticalAlignment="Bottom">Inclina</Label>
    <Slider Name="skewSlider" Height="30" Margin="1,-10,-1,10" VerticalAlignment="Bottom"
            Minimum="-15" Maximum="15" Value="0" />

Ovviamente, i valori Minimum e Maximum possono essere cambiati. Quelli specificati nell'esempio consentono al controllo di adattarsi ragionevolmente alle dimensioni della finestra. L'applicazione assume l'aspetto riportato in figura (che è quello definitivo):

Ora, è necessario far sì che il controllo MediaElement possa essere inclinato, mediante l'effetto SkewTransform. Sostituite il codice XAML relativo all'elemento MediaElement con il seguente:

    <MediaElement Name="myMedia" LoadedBehavior="Manual" UnloadedBehavior ="Stop"
                  Margin="10,10,10,10" Width="320" Height="240" Volume="5" Stretch="Fill">
      <MediaElement.RenderTransform>
        <SkewTransform CenterX="10" CenterY="10" AngleX="0" />
      </MediaElement.RenderTransform>
    </MediaElement>

Le trasformazioni devono essere contenute in elementi RenderTransform. La SkewTransform consente di specificare le proprietà CenterX, CenterY, AngleX e AngleY. Di particolare interesse sono AngleX e AngleY per la specifica dell'angolo di inclinazione. Per motivi di spazio, in questo esempio ci limitiamo ad inclinare il controllo lungo il solo asse delle X (tenete a mente che il valore della proprietà AngleX assegnato nel codice XAML appena digitato è solo temporaneo e verrà modificato in seguito).
Il passaggio successivo è quello di far sì che il MediaElement sia in grado di ricevere il valore dello Slider che abbiamo predisposto per l'inclinazione. Lo facciamo mediante la tecnica del data-binding.

Utilizzo del data-binding
Il data-binding è un argomento piuttosto complesso. In linea generale, possiamo definire il data-binding come quella tecnica che consente di modificare il comportamento di un controllo al variare dei dati che lo alimentano. Spesso e volentieri è una tecnica utilizzata per cambiare il contenuto di un report (ad esempio tramite ListView) al variare dei dati che lo compongono, nella programmazione di database. In WPF, il data-binding può essere utilizzato per variare anche l'aspetto grafico dei controlli.

Nel nostro caso, il data-binding serve a collegare la proprietà AngleX della trasformazione SkewTransform a un controllo Slider. Modificate, quindi, l'elemento SkewTransform come segue:

      <SkewTransform CenterX="10" CenterY="10" AngleX="{Binding Path=Value,
                     ElementName=skewSlider}" />

Il data-binding si realizza specificando il controllo a cui collegare l'elemento (proprietà ElementName) e il valore da collegare (proprietà Path). Abbiamo così dotato il nostro lettore multimediale di tutte le funzionalità, seppur di base, per poter riprodurre contenuti multimediali. Salvate il lavoro e avviate premendo F5.

My Media Player all'opera
Una volta avviata l'applicazione, fate clic sul pulsante Apri e selezionate un video compatibile col formato Windows Media. Una volta selezionato, fate clic sul pulsante Riproduci. La riproduzione del video viene così avviata regolarmente, come si vede nella seguente figura:

Provate a regolare il volume della riproduzione spostando l'apposito controllo Slider. Se volete vedere il risultato dell'effetto SkewTransform, non vi resta che spostare il selettore dell'apposito Slider, come in figura seguente:

Spunti di studio e conclusioni
Come detto, la conclusione di questo lavoro ha solamente grattato la superficie della potenza di WPF. Potreste, ad esempio, voler migliorare il layout dell'applicazione in modo che i controlli vengano automaticamente ridimensionati al variare delle dimensioni della finestra. A questo proposito, potete studiare i controlli Viewbox, Canvas, StackPanel e DockPanel. Potreste voler controllare il flusso di riproduzione, mediante gli oggetti SpeedRatio e Position del controllo MediaElement. O, ancora, potrebbero interessarvi altri argomenti importanti come i modelli (templates), il disegno geometrico (Geometry), le animazioni (StoryBoard) o la grafica tridimensionale, altro importante aspetto di WPF.

Sebbene l'esempio realizzato sia volutamente semplice, al termine di questa serie di articoli sapete molte cose in più su Windows Presentation Foundation e sicuramente avete imparato a muovervi meglio in questo fantastico mondo, in maniera autonoma. Una volta poste le basi, in futuro ci dedicheremo all'approfondimento di tematiche specifiche.
Come sempre, potete contattarmi al mio indirizzo visitare il mio blog.