Ricerca 
it-ITen-US
Registrazione
Accedi
 
Autore:  Sabrina Cosolo
Pubblicato il:  2007/11/22

Iniziare da zero con WPF (Parte 3)
Litigi, Divinità, Pennelli e Frigoriferi. (seconda parte)

Un articolo che parte da un litigio per ottenere una cosa semplicissima che in WPF sembra complicatissima, per proseguire studiando alcune delle caratteristiche delle classi Brush base di WPF, le classi che ci permettono di disegnare su una finestra. I pennelli, ci permetteranno di introdurre gli oggetti Freezable di WPF. Proveremo a generare ed usare un pennello per colorare lo sfondo di una finestra e vedremo gli oggetti già pronti forniti dal framework. Da qui potemo scoprire cos'è un oggetto congelabile e cos'è un oggetto congelato, come riconoscerlo e come utilizzarlo.
Trovate strano il connubio fra Windows e Frigoriferi? Concordo, ma in america il verbo Freeze è molto usato, soprattutto nei telefilm polizieschi quindi i programmatori di WPF hanno pensato bene di usare un concetto noto per una funzionalità comprensibile immediatamente agli anglofoni, un po' meno al resto del pianeta. Infatti se negli Stati Uniti urlate a qualcuno "Freeze." Con voce stentorea, vedrete che si ferma a mani in alto, se in italiano urlate a qualcuno "Congelati!", Questo prende il cellulare e chiama il 118 chiedendo l'intervento della neuro. Il congelamento di oggetti è però una cosa importante, e ci aiuta ad avviarci verso l'inizio dello studio dell'animabilità delle property degli oggetti e dell'animazione su evento.

L'articolo è stato diviso in due metà per questioni tecniche, il link alla prima parte si trova in testa e in calce al testo.

La classe WinLinearGradient

Questa seconda classe di test, esplora il secondo tipo di base fra i pennelli, che permette di costruire un gradiente lineare con due o più colori.

using System;
using System.Collections.Generic;
using System.Text;
using System.Windows;
using System.Windows.Media;
using System.ComponentModel;
using System.Windows.Media.Imaging;
using System.Windows.Media.Animation;
using System.Windows.Input;

namespace Dnw
{
    class WinLinearGradient: Window
    {
        private const string ID_PennelloAnimato = "PennelloAnimato";

    }
}

 

Imports System 
Imports System.Collections.Generic 
Imports System.Text 
Imports System.Windows 
Imports System.Windows.Media 
Imports System.ComponentModel 
Imports System.Windows.Media.Imaging 
Imports System.Windows.Media.Animation 
Imports System.Windows.Input 

Namespace Dnw 
    Class WinLinearGradient 
        Inherits Window 
        Private Const ID_PennelloAnimato As String = "PennelloAnimato" 
        
    End Class 
End Namespace

La struttura base è la stessa della precedente classe, al suo interno troverete anche i due metodi per generare l'icona della form da una risorsa di tipo bitmap oppure da un'icona di cui abbiamo parlato all'inizio di questo articolo.

public WinLinearGradient()
        {
            //this.Icon = BitmapSourceFromResource(Properties.Resources.btn_dnw_001);
            this.Icon = BitmapSourceFromResource((System.Drawing.Image)Properties.Resources.btn_dnw_032_004);
            this.WindowStyle = WindowStyle.ThreeDBorderWindow;
            this.ResizeMode = System.Windows.ResizeMode.NoResize;
            BuildAnimation();
        }

nel costruttore, abbiamo il test delle due funzioni per l'icona, poi, visto che ci siamo lanciati verso le potenzialità delle animazioni di WPF proviamo ad animare anche questo tipo di pennello per dare una particolare forma al nostro Background usando ancora una volta un metodo BuildAnimation che illustreremo immediatamente.

private void BuildAnimation()
        {
            // Creiamo un Namescope per usare gli storyboards
            NameScope.SetNameScope(this, new NameScope());

            GradientStopCollection colorStop;

            colorStop = new GradientStopCollection();
            colorStop.Add(new GradientStop(Colors.DarkGreen, 0.0));
            colorStop.Add(new GradientStop(Colors.White, 0.50));
            colorStop.Add(new GradientStop(Colors.Red, 1.0));


            LinearGradientBrush myAnimatedBrush = new LinearGradientBrush(colorStop,0);

            Background = myAnimatedBrush;

            // Registriamo il pennello animato per poterlo
            // usare con gli storyboard
            this.RegisterName(ID_PennelloAnimato, myAnimatedBrush);

            //
            // Animiamo lo startpoint e l'endpoint del LinearGradientBrush
            //
            PointAnimation gradientAni = new PointAnimation();
            gradientAni.From = new Point(0,0);
            gradientAni.To = new Point(1,1);
            gradientAni.Duration = TimeSpan.FromSeconds(3);
            gradientAni.AutoReverse = true;

            Storyboard.SetTargetName(gradientAni, ID_PennelloAnimato);

            Storyboard.SetTargetProperty(
                gradientAni, new PropertyPath(LinearGradientBrush.EndPointProperty));
            Storyboard mouseLeftButtonDownStoryboard = new Storyboard();
            mouseLeftButtonDownStoryboard.Children.Add(gradientAni);

            Storyboard.SetTargetName(gradientAni, ID_PennelloAnimato);
            Storyboard.SetTargetProperty(
                gradientAni, new PropertyPath(LinearGradientBrush.StartPointProperty));

            mouseLeftButtonDownStoryboard.Children.Add(gradientAni);

            this.MouseLeftButtonDown += delegate(object sender, MouseButtonEventArgs e)
            {
                mouseLeftButtonDownStoryboard.Begin(this);
            };

        }

 

Private Sub BuildAnimation() 
    ' Creiamo un Namescope per usare gli storyboards 
    NameScope.SetNameScope(Me, New NameScope()) 
    
    Dim colorStop As GradientStopCollection 
    
    colorStop = New GradientStopCollection() 
    colorStop.Add(New GradientStop(Colors.DarkGreen, 0)) 
    colorStop.Add(New GradientStop(Colors.White, 0.5)) 
    colorStop.Add(New GradientStop(Colors.Red, 1)) 
    
    
    Dim myAnimatedBrush As New LinearGradientBrush(colorStop, 0) 
    
    Background = myAnimatedBrush 
    
    ' Registriamo il pennello animato per poterlo 
    ' usare con gli storyboard 
    Me.RegisterName(ID_PennelloAnimato, myAnimatedBrush) 
    
    ' 
    ' Animiamo lo startpoint e l'endpoint del LinearGradientBrush 
    ' 
    Dim gradientAni As New PointAnimation() 
    gradientAni.From = New Point(0, 0) 
    gradientAni.[To] = New Point(1, 1) 
    gradientAni.Duration = TimeSpan.FromSeconds(3) 
    gradientAni.AutoReverse = True 
    
    Storyboard.SetTargetName(gradientAni, ID_PennelloAnimato) 
    
    Storyboard.SetTargetProperty(gradientAni, New PropertyPath(LinearGradientBrush.EndPointProperty)) 
    Dim mouseLeftButtonDownStoryboard As New Storyboard() 
    mouseLeftButtonDownStoryboard.Children.Add(gradientAni) 
    
    Storyboard.SetTargetName(gradientAni, ID_PennelloAnimato) 
    Storyboard.SetTargetProperty(gradientAni, New PropertyPath(LinearGradientBrush.StartPointProperty)) 
    
    mouseLeftButtonDownStoryboard.Children.Add(gradientAni) 
    AddHandler Me.MouseLeftButtonDown, AddressOf ConvertedAnonymousMethod1 
      
End Sub

 

Un LinearGradientBrush, genera il proprio gradiente fra due linee parallele che ne segnano inizio e fine, la colorazione è perpendicolare alle linee di inizio e fine del gradiente. A differenza di un SolidColorBrush, ha bisogno di più colori per la sua costruzione, pertanto, il suo costruttore accetta una collezione di oggetti di tipo GradientStop, un GradientStop, descrive la posizione ed il colore di un punto di transizione in un gradiente. Ogni gradiente può contenere un numero arbitrario di Gradient Stop. Nel nostro caso, facciamo un gradiente patriottico, e costruiamo tre gradient stop, uno verde, che parte dal punto 0 del gradiente, uno bianco che parte dal punto 0.5 e uno rosso che parte, o meglio arriva ad 1.0. Quando facciamo partire la finestra, modificando il DnwApp con queste 5 righe di codice:

WinLinearGradient win2 = new WinLinearGradient();
            win2.Title = "Test Linear gradient brush C#";
            win2.Height = 400;
            win2.Width = 340;
            win2.Show();

 

Dim win2 As New WinLinearGradient() 
win2.Title = "Test Linear gradient brush C#" 
win2.Height = 400 
win2.Width = 340 
win2.Show()

 Con questo codice, aggiungiamo una seconda finestra alla precedente che quando si apre è fatta così:

Finestra iniziale con gradiente patriottico

L'animazione che abbiamo inserito all'interno del metodo BuildAnimation di questa finestra è una variazione derivata dalle animazioni precedenti.

Il linear gradient brush, viene disegnato fra due punti lungo due righe parallele. la sua posizione rispetto all'asse della form può ruotare di 360° e per farlo utilizza i due punti di inizio e fine del nostro gradiente, che sono due Point, esattamente le proprietà StartPoint ed Endpoint, ciascuno dei 2 punti permette di percorrere le linee del rettangolo della form partendo da 0.0 angolo in alto a sinistra a 1.1 angolo in basso a destra.

La prima cosa che facciamo è creare un gradiente lineare patriottico, usando tre gradient stop verde, bianco e rosso posizionati a 0, 0.5, 1 lungo la linea ideale che compone il gradiente. Assegnamo il gradiente al Background e otteniamo la finestra che vediamo qui sopra.

Per animare il nostro gradiente, vogliamo provare a spostare le due proprietà StartPoint ed EndPoint, che non sono Ne un Color da animare con una Color Animation, ne un Double da animare con una DoubleAnimation, però a naso, proviamo a vedere se esiste una PointAnimation e guarda caso c'è anche lei. Questo tipo di animazione serve per spostare due valori (quindi X e Y del punto) da un Valore ad un altro usando le property From e To oppure tramite un terzo valore chiamato By, visto che abbiamo appena iniziato l'esplorazione di WPF ci limiteremo alle cose semplici, quindi iniziamo animando l'EndPoint in modo che From sia 0.0 e To 1.1, però, visto che stiamo facendo esperimenti aggiungiamo un'po' di brio e proviamo ad assegnare l'animazione anche allo StartPoint del gradiente.

Agganciamo l'animazione ad uno Storyboard generato per lavorare sul Mouse Click del bottone sinistro, e, visto che lo Storyboard possiede una collezione di Children animations, aggiungiamo a quella collezione entrambe le animazioni ed invochiamo il metodo Begin all'interno del metodo anonimo di gestione del Click del bottone sinistro del Mouse.

Facendo partire l'applicazione, otterremo due form, quella con il solid brush e la seconda, con il gradiente patriottico.

Se facciamo click sul Background della finestra con il gradiente, otteniamo una animazione che ruota il Gradiente e poi lo riporta indietro alla forma iniziale. Ho preso tre screenshot, per mostrare l'effetto:

Inizio animazione gradiente A metà animazione del gradiente Alla fine della rotazione prima di tornare indietro.

Il gradiente da orizzontale diviene verticale e poi ritorna indietro, il Ritorna indietro è gestito dalla property Autoreverse dell'animazione che abbiamo attivato.

Credo si possa notare come anche con le nostre scarse conoscenze, WPF è piuttosto portato per la grafica e l'animazione.

La classe WinRadialGradientBrush

Questa terza finestra che disegnamo, utilizza il terzo fra i gradienti di base, il RadialGradientBrush, che ci permette di creare un gradiente circolare, anche in questo caso, utilizzando il GradientStop per definire il colore e la sua posizione.

using System;
using System.Collections.Generic;
using System.Text;
using System.Windows;
using System.Windows.Media;
using System.ComponentModel;
using System.Windows.Media.Imaging;
using System.Windows.Media.Animation;
using System.Windows.Input;

namespace Dnw
{
    class WinRadialGradient: Window
    {
        private const string ID_PennelloAnimato = "PennelloAnimato";
    }
}

 

Imports System 
Imports System.Collections.Generic 
Imports System.Text 
Imports System.Windows 
Imports System.Windows.Media 
Imports System.ComponentModel 
Imports System.Windows.Media.Imaging 
Imports System.Windows.Media.Animation 
Imports System.Windows.Input 

Namespace Dnw 
    Class WinRadialGradient 
        Private Const ID_PennelloAnimato As String = "PennelloAnimato" 
    End Class 
End Namespace

Anche la struttura di questa terza classe è identica alle precedenti.

public WinRadialGradient()
        {

            this.Icon = BitmapFrame.Create(new Uri("pack://application:,,/btn_dnw_001.ico"));
            this.WindowStyle = WindowStyle.ThreeDBorderWindow;

            this.ResizeMode = System.Windows.ResizeMode.NoResize;
            BuildAnimation();
        }

 

Public Sub New() 
    
    Me.Icon = BitmapFrame.Create(New Uri("pack://application:,,/btn_dnw_001.ico")) 
    Me.WindowStyle = WindowStyle.ThreeDBorderWindow 
    
    Me.ResizeMode = System.Windows.ResizeMode.NoResize 
    BuildAnimation() 
End Sub

Anche qui, nel costruttore configuriamo la finestra e poi chiamiamo la costruzione dell'animazione.

/// 
        /// Costruisce l'animazione...
        /// 
        private void BuildAnimation()
        {
            // Creiamo un Namescope per usare gli storyboards
            NameScope.SetNameScope(this, new NameScope());

            /// 
            /// Composizione del gradiente
            /// 
            GradientStopCollection colorStop;

            colorStop = new GradientStopCollection();
            colorStop.Add(new GradientStop(Colors.Purple, 0.0));
            colorStop.Add(new GradientStop(Colors.Red, 0.17));
            colorStop.Add(new GradientStop(Colors.Orange, 0.33));
            colorStop.Add(new GradientStop(Colors.Yellow, 0.5));
            colorStop.Add(new GradientStop(Colors.Green, 0.67));
            colorStop.Add(new GradientStop(Colors.Blue, 0.84));
            colorStop.Add(new GradientStop(Colors.Indigo, 1.0));


            RadialGradientBrush myAnimatedBrush = new RadialGradientBrush(colorStop);

            Background = myAnimatedBrush;

            // Registriamo il pennello animato per poterlo
            // usare con gli storyboard
            this.RegisterName(ID_PennelloAnimato, myAnimatedBrush);

            //
            // Animiamo lo startpoint e l'endpoint del LinearGradientBrush
            //
            DoubleAnimation gradientAni = new DoubleAnimation();
            gradientAni.From = -1.0;
            gradientAni.To = 1.0;
            gradientAni.Duration = TimeSpan.FromSeconds(3);
            gradientAni.AutoReverse = true;

            Storyboard.SetTargetName(gradientAni, ID_PennelloAnimato);

            Storyboard.SetTargetProperty(
                gradientAni, new PropertyPath(RadialGradientBrush.RadiusXProperty));
            Storyboard mouseLeftButtonDownStoryboard = new Storyboard();
            mouseLeftButtonDownStoryboard.Children.Add(gradientAni);

            this.MouseLeftButtonDown += delegate(object sender, MouseButtonEventArgs e)
            {
                mouseLeftButtonDownStoryboard.Begin(this);
            };

        }

 

Private Sub BuildAnimation() 
'''  
''' Costruisce l'animazione... 
'''  
    ' Creiamo un Namescope per usare gli storyboards 
    NameScope.SetNameScope(Me, New NameScope()) 
    
    '''  
    ''' Composizione del gradiente 
    '''  
    Dim colorStop As GradientStopCollection 
    
    colorStop = New GradientStopCollection() 
    colorStop.Add(New GradientStop(Colors.Purple, 0)) 
    colorStop.Add(New GradientStop(Colors.Red, 0.17)) 
    colorStop.Add(New GradientStop(Colors.Orange, 0.33)) 
    colorStop.Add(New GradientStop(Colors.Yellow, 0.5)) 
    colorStop.Add(New GradientStop(Colors.Green, 0.67)) 
    colorStop.Add(New GradientStop(Colors.Blue, 0.84)) 
    colorStop.Add(New GradientStop(Colors.Indigo, 1)) 
    
    
    Dim myAnimatedBrush As New RadialGradientBrush(colorStop) 
    
    Background = myAnimatedBrush 
    
    ' Registriamo il pennello animato per poterlo 
    ' usare con gli storyboard 
    Me.RegisterName(ID_PennelloAnimato, myAnimatedBrush) 
    
    ' 
    ' Animiamo lo startpoint e l'endpoint del LinearGradientBrush 
    ' 
    Dim gradientAni As New DoubleAnimation() 
    gradientAni.From = -1 
    gradientAni.[To] = 1 
    gradientAni.Duration = TimeSpan.FromSeconds(3) 
    gradientAni.AutoReverse = True 
    
    Storyboard.SetTargetName(gradientAni, ID_PennelloAnimato) 
    
    Storyboard.SetTargetProperty(gradientAni, New PropertyPath(RadialGradientBrush.RadiusXProperty)) 
    Dim mouseLeftButtonDownStoryboard As New Storyboard() 
    mouseLeftButtonDownStoryboard.Children.Add(gradientAni) 
    AddHandler Me.MouseLeftButtonDown, AddressOf ConvertedAnonymousMethod1 
    
    
End Sub

Per animare questa finestra, Creiamo il NameScope, poi generiamo il RadialGradientBrush e stavolta utilizziamo i sette colori dell'arcobaleno distribuiti lungo la circonferenza utilizzando i GradientStop. La finestra iniziale che otterremo sarà la seguente:

RadialGradient Finestra iniziale

Per l'animazione, scegliamo di modificare la property RadiusX del gradiente, ovvero il raggio orizzontale del cerchio, questo valore è un Double (floating point a doppia precisione) pertanto usiamo una DoubleAnimation, anche in questo caso, con autoreverse, andiamo da -1 ad 1 come valori assunti dalla property e osserviamo quello che accade.

Inizio animazione Metà animazione Fine Animazione

Per quanto ancora piuttosto grezzo, l'effetto che otterremo sarà far sembrare che il cerchio ruoti su se stesso sull'asse verticale in un senso e poi tornando indietro.

Conclusioni

In questo articolo abbiamo scoperto le nuove resources embedded, come convertire un icona o una bitmap in un immagine WPF, come iniziare a giocare con le animazioni, magari per poi ottenere qualche effetto simpatico sulle form WPF che svilupperemo, e ovviamente abbiamo aggiunto un paio di chiodi alla nostra scalata verso la vetta.

Il progetto utilizzato per il codice dimostrativo può essere scaricato in Area Risorse.
Per qualsiasi Feedback, Ulteriore domanda, chiarimento, oppure se trovate qualche errore usate direttamente il form dei commenti in calce a questo articolo oppure scrivete all'autrice .

Nota per gli sviluppatori VB

Visual basic, al contrario di C# non possiede la funzionalità cosiddetta dei metodi anonimi, che permette, come potete osservare all'interno del codice, di definire un event handler al volo e di utilizzare al suo interno le variabili locali del metodo in cui è definito.

Per ovviare a questo problema, anche se nel codice di questo articolo non si vede, sono stati generati degli event handler normali e gli oggetti Storyboard sono stati spostati a livello di classe, in modo che gli event handler li possano vedere.

 

 

E'necessario essere un utente registrato per il download del codice allegato all'articolo


       
Articoli|Webcast|Risorse|Utility
© 2007-2010 by DotNetWork  .:.  Condizioni d'uso  .:.  Privacy  .:.  Accedi  .:.