Ricerca
 Italiano (Italia) English (United States)
Register
Login
 
Author: Sabrina Cosolo
Published: 2007/11/22

Starting from Scratch with WPF (Part 3)
Rows, deities, brushes and refrigerators (first part)

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 seconda parte si trova in calce al testo.

Introduzione

In questo terzo gradino della nostra scalata verso WPF, proseguiamo da dove avevamo terminato la scorsa puntata, ovvero avevamo colorato la Window del nostro programma di Rosso porpora usando  un pennello (Brush) a noi virtualmente sconosciuto, proveremo ad usarne le versioni più semplici in questo articolo. Prima di parlare di brushes però ho indicato un litigio e quindi vado a spiegare le motivazioni, il suo svolgimento e le conclusioni.

Un match fra un'Icona tradizionale ed un'Icona WPF

Una delle cose che faccio sempre nei miei programmi, anche quelli didattici, è quella di cambiare l'icona della form da quella standard di Visual Studio ad un'icona personalizzata.

Nelle Windows Forms, questo si ottiene inserendo l'icona desiderata nelle Resources ed assegnandola ad ogni form. In WPF quindi pretendevo di fare lo stesso, solo che... non è proprio così che funziona.

Infatti, se in una Window WPF provate a scrivere il seguente codice:

this.Icon = Properties.Resources.btn_001;

 

Me.Icon = My.Resources.btn_001

Ottenete il seguente errore di compilazione:

Error 1 Cannot implicitly convert type 'System.Drawing.Bitmap' to 'System.Windows.Media.ImageSource'

Ovviamente la domanda è, ma come si converte un Bitmap in una ImageSource? Tentando un cast nulla di fatto, ma cercando su internet ho trovato varie soluzioni che andrò a mostrare:

Generare l'ImageSource da un Bitmap memorizzato nel file Resources.resx

public static BitmapSource BitmapSourceFromResource(System.Drawing.Image pImg)
        {
            System.IO.MemoryStream memStream = new System.IO.MemoryStream();
            //Salva l'immagine come Png sul MemoryStream
            pImg.Save(memStream, System.Drawing.Imaging.ImageFormat.Png);
            //Genera un decoder per il tipo di stream
            System.Windows.Media.Imaging.PngBitmapDecoder decoder = 
                new System.Windows.Media.Imaging.PngBitmapDecoder(memStream, 
                System.Windows.Media.Imaging.BitmapCreateOptions.PreservePixelFormat, 
                System.Windows.Media.Imaging.BitmapCacheOption.Default);
            //Torna il primo (e unico) frame generato dall'immagine via Decoder.return decoder.Frames[0];
        }

 

Public Shared Function BitmapSourceFromResource(ByVal pImg As _System.Drawing.Image) As BitmapSource 
    Dim memStream As New System.IO.MemoryStream() 
    'Salva l'immagine come Png sul MemoryStream 
    pImg.Save(memStream, System.Drawing.Imaging.ImageFormat.Png) 
   'Genera un decoder per il tipo di stream Dim decoder As New System.Windows.Media.Imaging.PngBitmapDecoder _
    (memStream, System.Windows.Media.Imaging.BitmapCreateOptions.PreservePixelFormat, _
System.Windows.Media.Imaging.BitmapCacheOption.[Default]) 'Torna il primo (e unico) frame generato dall'immagine via Decoder. Return decoder.Frames(0) End Function

Questo è il metodo per la conversione di un immagine, in questo caso in formato Png, ma potrebbe essere un Jpg o un Gif o un Bmp ecc., esistono i decoder per i vari tipi di immagine. Come possiamo vedere non si tratta di un'operazione proprio semplice, infatti le operazioni eseguite sono le seguenti:

  • Salvare l'immagine su un memory stream;
  • Generare un PngBitmapDecoder a partire dallo stream;
  • Restituire il Frame 0 prodotto dal decoder;

Scomodiamo quindi la libreria Media, nonché i Codec per ottenere un'icona, perché mai dobbiamo complicarci la vita così?

Generare l'ImageSource da un Icona memorizzata nel file Resources.resx

public static BitmapSource BitmapSourceFromResource(System.Drawing.Icon pIco)
        {
            System.IO.MemoryStream iconStream = new System.IO.MemoryStream();
            pIco.Save(iconStream);
            return( IconBitmapDecoder.Create(iconStream, BitmapCreateOptions.PreservePixelFormat,
                System.Windows.Media.Imaging.BitmapCacheOption.Default)).Frames[0];

        }

 

Public Shared Function BitmapSourceFromResource(ByVal pIco _
    As System.Drawing.Icon) As BitmapSource 
    Dim iconStream As New System.IO.MemoryStream() 
    pIco.Save(iconStream) 
    Return (IconBitmapDecoder.Create(iconStream, _
        BitmapCreateOptions.PreservePixelFormat, _
        System.Windows.Media.Imaging.BitmapCacheOption.[Default])).Frames(0)    
End Function

Questo metodo converte un'icona invece di convertire un bitmap. Sono 3 righe di codice, eppure su di esso ho perso 3 ore di tempo perché una volta generata l'icona, ricevevo un'eccezione quando la form veniva visualizzata, mi diceva che il formato dell'icona era errato.

Non capivo per quale motivo, fino a che, per caso, ho usato un'icona diversa da quella che avevo utilizzato fino a quel momento ed ho scoperto che non c'era un errore nel codice ma era l'icona che era errata.

Ho indagato sulle differenze fra l'icona che funzionava e quella che invece dava eccezione, ed ho scoperto che il problema è dovuto al fatto che in un raptus di ottimizzazione, quando ho generato il file .ico ho indicato  al programma di utilizzare la compressione png per le icone in formato Windows Vista. Questa compressione non è gestita da parte di WPF o meglio, da parte delle Windows.

La conversione, simile a quella per il bitmap precedente viene effettuata con le seguenti azioni:

  • Generare un Memory Stream;
  • Salvare l'icona sul Memory stream;
  • Generare un Frame dal Decoder esattamente come per la Bitmap.

Ma questi due metodi sono in realtà un ripiego, che ho cercato perché non riuscivo a comprendere come riuscire a generare un'immagine nel modo che sembrava più semplice.

Generazione di Una ImageSource utilizzando una Risorsa Embedded ed un URI

Infatti, il primo metodo per generare una bitmap che ho trovato  guardando il costruttore della ImageSource, ovvero il BitmapFrame, richiedeva un solo parametro, di tipo URI.

this.Icon = BitmapFrame.Create( Uri );

Se cercate su Wikipedia, troverete varie definizioni, la prima dice che le URI sono le Vergini del paradiso islamico, la seconda che URI era il dio della caccia dei Longobardi, probabilmente l'Herne dei Sassoni e il Pan dei Greci, gli Uri erano anche un tipo di Bovini estintisi nel primo Medioevo. Infine, L'immancabile acronimo URI: Unique Resource Identifier, Identificatore Univoco di Risorsa, comprensibile, ma che cos'è? Non bastava un semplice Path? Il nome della risorsa?

Strap

Che diamine, il Framework 2.0 ci aveva finalmente dato il Properties.Resources, o My.Resources e adesso mi cambiano tutto? Non bastasse ciò, quando sono andata a cercare di capire come è fatto un URI, mi sono imbattuta in una definizione: Pack URIs in Windows Presentation Foundation, che non ha fatto che aumentare la mia confusione mentale, la definizione era associata a questo esempio: pack://application:,,/ResourceFile.xaml, una cosa interessante, ma le spiegazioni trovate non riuscivano a farmi comprendere di cosa si trattasse, forse perché io non sto utilizzando dei files XAML ma sto scrivendo codice, quindi sono andata a cercare aiuto da Charles Petzold ovvero ho cercato URI sull'indice analitico del suo primo libro dedicato a WPF e per fortuna la sua spiegazione era un po' più comprensibile.

Appurato che un'URI non è una delle Vergini del paradiso islamico, e non è neppure la divinità Longobarda,  le Pack URI sono degli identificatori univoci che permettono di identificare una Risorsa agglomerata in un progetto ovvero una risorsa Embedded. Bisogna solo scoprire che cos'è una risorsa Embedded per un progetto WPF. Visto l'esempio che ho riportato qui sopra, pensavo fosse stato modificato il nome del file risorse, da un .resx a un .xaml, ma non è così. Con Risorsa Embedded, in un progetto WPF si intende un qualsiasi file, nel mio caso un file grafico di tipo JPG, PNG o ICO, che viene aggregato utilizzando Add>Existing File> ad un progetto e la cui property BuildAction è indicata come Resource. Questo fa in modo che il compilatore lo inserisca nell'exe o nella dll compilati e che siano accessibili tramite la famosa Pack URI.

La Pack URI, è generata da un path simile ad un URL nella forma: pack://application:,,/NomeDelFile. Per inciso, se facciamo una sottocartella Images per inserire tutte le immagini, scriviamo pack://application:,,/Images/NomeDelFile. Probabilmente gli esperti di WPF sorrideranno per la mia scoperta, ma dal mio punto di vista, si tratta di un primo chiodo importante nella scalata alla parete nord del WPF.

Scoperto come generare una risorsa embedded, e aggiunta la mia icona al progetto, il codice da scrivere diviene il seguente:

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

 

Me.Icon = BitmapFrame.Create( _
    New Uri("pack://application:,,/btn_dnw_001.ico"))

Direi che torniamo alla ragionevolezza, anche se mi chiedo "Perchè quella cosa davanti al nome del file di risorsa?" non sarebbe stato più semplice usare solo il nome del file, ma al momento non mi metto fretta, sono certa che lo scopriremo e sarà un altro chiodo di sostegno per le nostre corde da arrampicata.

Terminata questa prima parte estemporanea, passiamo al vero soggetto dell'articolo, ovvero i pennelli di WPF.

Le classi di tipo Brush

Pennello (Brush) è decisamente un nome riduttivo per queste classi, infatti per chi come me a scuola faceva i disegni con gli acquerelli, oppure con i carioca, il concetto di pennello è semplice, si tratta di un attrezzo che è in grado di spargere un colore su un foglio, in questo caso, i pennelli mono colore sono solo uno dei tipi di pennello che possono essere forniti dalla classe Brush, nella forma dei SolidBrush, ma già i pennelli monocolore non sono così banali, perché oltre al colore, hanno anche una trasparenza che pemette loro di stendere il colore in modo un pochino più complesso di quanto facessimo noi con gli acquerelli. Vediamo dove si trovano i pennelli nell'albero degli oggetti di WPF.

Object

DispatcherObject (abstract)

DependencyObject

Freezable (abstract)

Animatable (abstract)

Brush (abstract)

GradientBrush (abstract)

LinearGradientBrush

RadialGradientBrush

SolidColorBrush

TileBrush(abstract)

DrawingBrush

ImageBrush

VisualBrush

Ho collegato le pagine di MSDN relative alle classi che è opportuno leggere se intendete lavorare con WPF, ci mostrano alcune delle cose che possono fare i nostri pennelli, e già guardando il DrawingBrush possiamo vedere come il pennello è in grado di tracciare delle forme, o ripetere delle forme per generare dei pattern utilizzando degli oggetti di tipo Drawing. L'ImageBrush è invece un pennello in grado di disegnare usando come base un immagine che può riprodurre o ripetere su una superficie. Il VisualBrush è invece in grado di utilizzare un oggetto Visual per riprodurlo, oppure disegnarlo. In questo caso il Visual è ancora immerso nella nebbia che avvolge la maggior parte del WPF, pertanto vi invito a leggerne la descrizione di MSDN per saperne di più. Per questo primo tour limitiamoci ai tre pennelli più semplici, Solidbrush, LinearGradientBrush, RadialGradientBrush.

La classe WinSolid

Il SolidBrush fornisce un pennello in grado di riempire una superficie con un colore, il Framework fornisce di default una collezione di SolidBrush che rappresentano i colori nominati come White, Red, Green eccetera. Pertanto se ricordate la nostra puntata precedente, abbiamo colorato il background di una form utilizzando la seguente riga di codice:

this.Background = Brushes.White;

 

Me.Background = Brushes White

La classe statica Brushes ci permette di utilizzare direttamente i colori più comuni, se non erro sono quelli chiamati colori Web fra cui sono Lime, Plum, Chocolate ed altri colori esotici. Questa classe però ha a che vedere con i frigoriferi e vediamo come.

Se riguardate l'albero dell'ereditarietà delle classi Brush, noterete come c'è una classe Freezable, ovvero Congelabile. Se proviamo a eseguire il seguente codice:

SolidColorBrush myBrush = Brushes.White;
myBrush.Color = Color.FromRgb( 125, 0, 45);

 

SolidColorBrush myBrush = Brushes.White
myBrush.Color = Color.FromRgb( 125, 0, 45)

Otteniamo la seguente eccezione:

Impossibile impostare una proprietà per l'oggetto '#FFFFFFFF' perché è in stato di sola lettura.

Questo può darci un indizio relativo a cosa sono gli oggetti Freezable. L'appartenenza dell'oggetto ai discendenti della classe Freezable, permette loro di avere due stati, NonCongelato e Congelato, nel momento in cui un oggetto viene Congelato, non è più possibile modificarlo in alcun modo, mentre quando non è congelato, l'oggetto può essere modificato. Questo lo rende utile nei casi in cui sia necessario lavorare con più thread contemporaneamente, vi invito a leggere la Panoramica sugli oggetti congelabili di WPF che potrà probabilmente evitarci più di qualche mal di testa in merito.

Siccome vogliamo provare a modificare il colore del nostro pennello, ci basta modificare la sua generazione per ottenere un pennello scongelato e quindi malleabile.

La prima classe di test ci serve per vedere come è fatto un SolidColorBrush, questo tipo di pennello non ha molte caratteristiche per giocare salvo il proprio colore e la possibilità di divenire trasparente. Pertanto, anche per andare a curiosare in alcune delle cose che dovremo studiare nelle prossime puntate di questa serie, introdurremo alcuni oggetti che ci permetteranno di vedere alcune delle potenzialità di WPF.

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

    class WinSolid: 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.Animation 
Imports System.Windows.Input 
Imports System.Windows.Media.Imaging 

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

La struttura base della classe è simile alle Window che abbiamo già implementato, le cose nuove che appaiono sono alcuni Namespaces, System.Windows.Media e System.Window.Media.Imaging sono i namespace in cui abbiamo trovato come creare l'icona della form.  System.Windows.Input fornisce gli oggetti per utilizzare gli eventi del mouse, System.Windows.Media.Animation contiene gli oggetti necessari ad animare le property del nostro pennello.

Utilizziamo inoltre una costante per definire un Nome per il pennello animato.

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

            this.ResizeMode = System.Windows.ResizeMode.NoResize;
            //this.Background = Brushes.White;this.Background = new SolidColorBrush(Colors.White);
            BuildAnAnimation();
            
        }

 

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 
    'this.Background = Brushes.White; Me.Background = New SolidColorBrush(Colors.White) 
        
    BuildAnAnimation() 
End Sub

Nel costruttore, predisponiamo la finestra e dipingiamo di bianco il background, per poi chiamare il metodo che costruisce l'animazione.

private void BuildAnAnimation()
        {

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

            SolidColorBrush myAnimatedBrush = new SolidColorBrush();
            myAnimatedBrush.Color = Colors.Orange;
            Background = myAnimatedBrush;

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

            // Animiamo il pennello per farlo cambiare colore al mouse enter
            ColorAnimation mouseEnterColorAnimation = new ColorAnimation();
            mouseEnterColorAnimation.To = Colors.Lime;
            mouseEnterColorAnimation.Duration = TimeSpan.FromSeconds(3);
            Storyboard.SetTargetName(mouseEnterColorAnimation, ID_PennelloAnimato);
            Storyboard.SetTargetProperty(
                mouseEnterColorAnimation, new PropertyPath(SolidColorBrush.ColorProperty));
            Storyboard mouseEnterStoryboard = new Storyboard();
            mouseEnterStoryboard.Children.Add(mouseEnterColorAnimation);
            this.MouseEnter += delegate(object sender, MouseEventArgs e)
            {
                mouseEnterStoryboard.Begin(this);
            };
            
            //// Animiamo il pennello quando il mouse lascia il rettangolo
            ColorAnimation mouseLeaveColorAnimation = new ColorAnimation();
            mouseLeaveColorAnimation.To = Colors.Indigo;
            mouseLeaveColorAnimation.Duration = TimeSpan.FromSeconds(3);
            Storyboard.SetTargetName(mouseLeaveColorAnimation, ID_PennelloAnimato);
            Storyboard.SetTargetProperty(
                mouseLeaveColorAnimation, new PropertyPath(SolidColorBrush.ColorProperty));
            Storyboard mouseLeaveStoryboard = new Storyboard();
            mouseLeaveStoryboard.Children.Add(mouseLeaveColorAnimation);
            this.MouseLeave += delegate(object sender, MouseEventArgs e)
            {
                mouseLeaveStoryboard.Begin(this);
            };

            //// Animiamo l'opacità del pennello quando premiamo il bottone sinistro//
            DoubleAnimation opacityAnimation = new DoubleAnimation();
            opacityAnimation.To = 0.0;
            opacityAnimation.Duration = TimeSpan.FromSeconds(0.5);
            opacityAnimation.AutoReverse = true;
            Storyboard.SetTargetName(opacityAnimation, ID_PennelloAnimato);
            Storyboard.SetTargetProperty(
                opacityAnimation, new PropertyPath(SolidColorBrush.OpacityProperty));
            Storyboard mouseLeftButtonDownStoryboard = new Storyboard();
            mouseLeftButtonDownStoryboard.Children.Add(opacityAnimation);
            this.MouseLeftButtonDown += delegate(object sender, MouseButtonEventArgs e)
            {
                mouseLeftButtonDownStoryboard.Begin(this);
            };



        }

 

Private Sub BuildAnAnimation() 
    
    ' Creiamo un Namescope per usare gli storyboards 
    NameScope.SetNameScope(Me, New NameScope()) 
    
    Dim myAnimatedBrush As New SolidColorBrush() 
    myAnimatedBrush.Color = Colors.Orange 
    Background = myAnimatedBrush 
    
    ' Registriamo il pennello animato per poterlo ' usare con gli storyboard Me.RegisterName(ID_PennelloAnimato, myAnimatedBrush) 
    
    ' Animiamo il pennello per farlo cambiare colore al mouse enter Dim mouseEnterColorAnimation As New ColorAnimation() 
    mouseEnterColorAnimation.[To] = Colors.Lime 
    mouseEnterColorAnimation.Duration = TimeSpan.FromSeconds(3) 
    Storyboard.SetTargetName(mouseEnterColorAnimation, ID_PennelloAnimato) 
    Storyboard.SetTargetProperty(mouseEnterColorAnimation, _
New PropertyPath(SolidColorBrush.ColorProperty)) Dim mouseEnterStoryboard As New Storyboard() mouseEnterStoryboard.Children.Add(mouseEnterColorAnimation) AddHandler Me.MouseEnter, AddressOf ConvertedAnonymousMethod1 ' ' Animiamo il pennello quando il mouse lascia il rettangolo Dim mouseLeaveColorAnimation As New ColorAnimation() mouseLeaveColorAnimation.[To] = Colors.Indigo mouseLeaveColorAnimation.Duration = TimeSpan.FromSeconds(3) Storyboard.SetTargetName(mouseLeaveColorAnimation, ID_PennelloAnimato) Storyboard.SetTargetProperty(mouseLeaveColorAnimation, _
New PropertyPath(SolidColorBrush.ColorProperty)) Dim mouseLeaveStoryboard As New Storyboard() mouseLeaveStoryboard.Children.Add(mouseLeaveColorAnimation) AddHandler Me.MouseLeave, AddressOf ConvertedAnonymousMethod1 ' ' Animiamo l'opacità del pennello quando premiamo il bottone sinistro ' Dim opacityAnimation As New DoubleAnimation() opacityAnimation.[To] = 0 opacityAnimation.Duration = TimeSpan.FromSeconds(0.5) opacityAnimation.AutoReverse = True Storyboard.SetTargetName(opacityAnimation, ID_PennelloAnimato) Storyboard.SetTargetProperty(opacityAnimation, _
New PropertyPath(SolidColorBrush.OpacityProperty)) Dim mouseLeftButtonDownStoryboard As New Storyboard() mouseLeftButtonDownStoryboard.Children.Add(opacityAnimation) AddHandler Me.MouseLeftButtonDown, AddressOf ConvertedAnonymousMethod1 End Sub

Questo metodo è il cuore di tutta la nostra classe, iniziamo generando un NameScope, la prima classe nuova, per sapere cos'è un Namescope, vi invito a leggere WPF Namescopes su MSDN,  se (spero) ho compreso correttamente quanto letto, il Namescope è un oggetto che permette di rendere univoci i nomi di tutti gli oggetti all'interno di una Window o una Pagina XAML, ogni oggetto radice (root) dell'albero degli elementi di WPF da origine ad un Namescope, che permette di trovare oggetti al suo interno utilizzando un nome univoco e che controlla che non sia possibile generare due oggetti con lo stesso nome all'interno di uno stesso scopo (di qui il suo nome, Scopo dei nomi), sono certa che ci imbatteremo spesso nel Namescope in futuro. Infatti è collegato all'uso degli Storyboards, che servono per realizzare animazioni.

Generato il Namescope, creiamo un pennello arancione, e gli assegnamo un Nome univoco all'interno del Namescope.

Proseguiamo, generando una ColorAnimation, il secondo oggetto nuovo, color animation anima una Property di tipo color per passare da un colore ad un altro utilizzando l'interpolazione lineare per un periodo specificato.

Un'Animazione, modifica il valore di una property nell'arco di un periodo di tempo. L'animazione può avere una forma minuta, come ad esempio spostare un oggetto di alcuni pixel oppure essere complessa, come ad esempio ingrandire un oggetto di 200 volte facendolo ruotare e cambiare colore. Per creare una animazione in WPF si associa una classe di tipo animazione ad una property di un oggetto.

Alla color animation assegnamo un colore di arrivo, la Property To e una durata in secondi 3.

A questo punto, entra in gioco un oggetto nuovo, l'oggetto Storyboard, un contenitore per una Timeline (linea temporale) che fornisce informazioni relative agli oggetti ed alle proprietà a cui sono destinate le sue animazioni figlie.

Infatti, nel nostro codice indichiamo l'animazione che abbiamo generato ed il Nome assegnato al controllo nel Namescope utilizzando il metodo statico SetTargetName, indichiamo poi la property da animare utilizzando il metodo statico SetTargetProperty in cui utilizziamo un altro oggetto nuovo, PropertyPath, questa classe rappresenta il percorso  (path) che descrive una proprietà come Path sottostante ad una property o ad un tipo di dato che la possiede, è utilizzato sia per il data binding, sia per le animazioni. Nel nostro caso, la property path indica all'animazione la property di quale classe è interessata dall'animazione. Per capirne di più vi invito a leggere  la traduzione della Panoramica delle Proprietà subordinate (Dependency Properties) ed il loro funzionamento.

La PropertyPath che generiamo, indica che utilizzeremo SolidColorBrush.ColorProperty come target dell'animazione.

Per completare la generazione dell'animazione, generiamo una classe Storyboard, aggiungiamo alla sua Collection Children l'animazione che abbiamo generato, ed assegnamo all'evento MouseEnter della nostra form un event handler che in C# è generato tramite un metodo anonimo, che esegue il metodo Begin dell'animazione, in VB non so come consegnare all'eventhandler la variabile locale, presumo che sarà necessario implementare un Field a livello di classe per generare correttamente l'event handler.

Abbiamo finito, però visto che ci siamo lanciati, generiamo altre due animazioni, una che cambia di nuovo colore al mouse leave, e un'ultima che Utilizza una nuova classe, una DoubleAnimation per animare l'opacità, il risultato di questa animazione non è perfetto perché vista la nostra ignoranza (non conosciamo nulla salvo la finestra) animiamo il Background della stessa e vedrete che l'effetto è un po' banale, però, ripeteremo l'esperimento con un oggetto più consono quanto prima.

Se leggete che cos'è una DoubleAnimation, scoprirete che è un'animazione che modifica una property di tipo double (il tipo numerico floating point a doppia precisione) fra due valori utilizzando una interpolazione lineare per una durata specifica. Nel nostro esempio, molto semplice, facciamo passare l'opacità del pennello da 1.0 ( opaco, il valore di default per un SolidBrush) a 0.0 (trasparente). Due valori sono inoltre modificati per la nostra animazione, la property OpacityProperty e la property AutoReverse dell'animazione che viene messa a True, mentre il valore To dell'animazione è 0.0 anziché un colore.

Per lanciare il nostro programma, impostiamo nella classe DnwApp del progetto (derivata da Application e identica a quella dei progetti nei precedenti articoli) il seguente codice:

protected override void OnStartup(StartupEventArgs e)
{
            base.OnStartup(e);
            WinSolid win1 = new WinSolid();
            win1.Title = "Test Solid brush C#";
            win1.Height = 400;
            win1.Width = 340;
            win1.Show();
}

 

Protected Overloads Overrides Sub OnStartup(ByVal e As StartupEventArgs) 
    MyBase.OnStartup(e)
    Dim win1 As New WinSolid() 
    win1.Title = "Test Solid brush C#" 
    win1.Height = 400 
    win1.Width = 340 
    win1.Show()
End Sub

Facendo partire il progetto otterremo una form che si apre con sfondo arancione, all'ingresso del mouse diviene verde e all'uscita del mouse diviene Indaco, al doppio click, dal colore che ha diviene nera (perché il suo background diviene trasparente) e poi torna al colore precedente.

Non è un progetto che risolve un problema reale, però apre una serie di scatole che contengono delle cose interessanti per proseguire la nostra esplorazione di WPF.

Forma iniziale della finestra con pennello arancione
Form all'apertura dell'applicazione
Formato Mouse over verde lime
Form al mouse enter
Forma mouseout pennello da verde a indaco
Form al mouse leave
Formato al click del mouse trasparenza
Form dopo il click

Mi spiece non poter mostrare un filmato ma ovviamente il codice del progetto sarà pubblicato e quindi potrete provarlo sul vostro PC.

Vai alla seconda parte >>

You need to logon to download the code attached to this article


       
Articoli|Webcast|Risorse|Utility
© 2007-2010 by DotNetWork .:. Terms Of Use .:. Privacy Statement .:. Login .:.