in

dotNet Umbria

Il primo User Group in Umbria sul mondo .Net

Articoli

Articoli vari degli iscritti a DotNetUmbria

April 2008 - Posts

  • Timeline ed interpolazione con WPF

    Come la maggior parte di coloro che per la prima volta si avvicinano a WPF mi sono trovato a gestire le animazioni attraverso le Timeline.

    Un animazione gradevole e didatticamente interessante consiste nel far ingrandire un oggetto qualsiasi (button, image, o qualsiasi altro) al passaggio del mouse.

    Ottenere quest’effetto è molto semplice, basta andare a modificare i valori dei due moltiplicatori X e Y (scale orizzontale e scale verticale). Di default entrambi i valori sono impostati a 1, che è come dire 100%

    Cosa significa questo: che se il nostro button (ad esempio) è alto 10 pixel, con il valore di scale Y a 1, diventa 10 x 1 = 10 pixel quindi dimensione originale. Modificando il moltiplicatore e impostandolo a 2 (200%), è banalmente ovvio che il nostro button diventerà alto 20.

    Torniamo a noi. Volendo creare un animazione, che al passaggio del mouse ingrandisca il nostro oggetto raddoppiandone le dimensioni, ci basta agganciarne l’evento OnMouseEnter e, impostando nella timeline un arco di tempo a piacere (diciamo mezzo secondo) e successivamente cambiare i valori, da 1 a 2, dei campi X e Y nel campo scale del LayoutTransform .

    Volendo fare l’operazione opposta, e cioè agganciando all’oggetto l’evento OnMouseLeave per far si che all’uscita del mouse, il nostro oggetto torni alle dimensioni originali, ho incontrato un problema. Ho ovviamente creato una nuova timeline, impostato lo scale a 2 del LayoutTransform per indicare, ovviamente, i valori di inizio interpolazione, e poi, a mezzo secondo di distanza nella timeline, ho impostato di nuovo i valori dello scale a 1.

    [code language="xml"]

    <DoubleAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetName="prova" Storyboard.TargetProperty="(FrameworkElement.LayoutTransform).(TransformGroup.Children)[0].(ScaleTransform.ScaleX)">
        <SplineDoubleKeyFrame KeyTime="00:00:00" Value="2"/>
        <SplineDoubleKeyFrame KeyTime="00:00:00.5000000" Value="1"/>
    </DoubleAnimationUsingKeyFrames>
    <DoubleAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetName="prova" Storyboard.TargetProperty="(FrameworkElement.LayoutTransform).(TransformGroup.Children)[0].(ScaleTransform.ScaleY)">
        <SplineDoubleKeyFrame KeyTime="00:00:00" Value="2"/>
        <SplineDoubleKeyFrame KeyTime="00:00:00.5000000" Value="1"/>
    </DoubleAnimationUsingKeyFrames>

    [/code]

    Mandando in esecuzione l’applicazione, dopo pochissime prove, ho notato un insolito e alquanto poco gradito comportamento: facendo uscire il mouse dal mio oggetto prima del termine dell’animazione (in pratica scatenando l’evento OnMouseLeave quando il mio oggetto non era ancora completamente ingrandito), questo “scattava” alla dimensione raddoppiata per poi proseguire correttamente con la seconda animazione e quindi tornando fluidamente alle dimensioni originali.

    Facendo un paio di prove ho scoperto che la cosa è di una semplicità disarmante ma, soprattutto per chi come me ha avuto già a che fare in precedenza con animazioni, timeline e interpolatori vari, non molto ovvia.

    L’errore da me commesso è stato di settare lo scale a 2 per indicare i valori di inizio interpolazione per l’animazione di “restringimento”. Non impostandoli, ma impostando solo i valori di fine interpolazione, WPF fa partire l’interpolazione dai valori attuali dello scale, qualsiasi essi siano.

    [code language="xml"] 

    <DoubleAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetName="prova" Storyboard.TargetProperty="(FrameworkElement.LayoutTransform).(TransformGroup.Children)[0].(ScaleTransform.ScaleX)">
        <SplineDoubleKeyFrame KeyTime="00:00:00.5000000" Value="1"/>
    </DoubleAnimationUsingKeyFrames>
    <DoubleAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetName="prova" Storyboard.TargetProperty="(FrameworkElement.LayoutTransform).(TransformGroup.Children)[0].(ScaleTransform.ScaleY)">
        <SplineDoubleKeyFrame KeyTime="00:00:00.5000000" Value="1"/>
    </DoubleAnimationUsingKeyFrames>

    [/code] 

    Lanciando la nostra applicazione, possiamo vedere che, l’effetto voluto è gradevole e fluido.

    Sperando di avervi risparmiato quei 5 minuti di prove, saluto tutti.

  • Inserisci il tuo articolo !!

    Ogni utente registrato può inserire liberamente i propri articoli nella sezione "Articoli".
    Potete scrivere l'articolo direttamente dal sito oppure utilizzare Windows Live Writer indicando il seguente url come url del blog:

    http://dotnetumbria.org/blogs/articoli/

    Aspettiamo numerosi vostri contenuti.

  • WPF Invalidate Binding

    Il binding di WPF è molto reattivo grazie a tutta la struttura di DependencyObjects e DependencyProperties a cui si appoggia. Per questo motivo lavorando con WPF molto spesso non ci dobbiamo preoccupare di fare un refresh dell'interfaccia utente per visualizzare i dati aggiornati.

    Può capitare tuttavia di dover mettere in binding alcune sorgenti dati che non utilizzano DependencyProperties e che nella loro struttura non implementano l'interfaccia INotifyPropertyChanged . In questi rari casi accade che l'interfaccia utente non è aggiornata con i dati perchè non è in grado di capire che i dati sono stati modificati in qualche modo. Dobbiamo essere quindi noi a forzare il refresh.

    Vediamo quali strumetni abbiamo a disposizione. Viene sicuramente in nostro aiuto la classe statica BindingOperations, la quale ci permette di interrogare la struttura ad oggetti dell'interfaccia utente e capire se le proprietà degli oggetti stessi sono in binding oppure no.

    image

     

    BindingOperations ci restituisce oggetti di tipo BindingExpression che sono di fatto il collegamento tra il target e la sorgente dati.

    image

    Possiamo utilizzare il metodo UpdateTarget per forzare la lettura dei dati ed un refresh dell'interfaccia utente.


    Come facciamo tuttavia da codice a utilizzare questo metodo senza vincolare la grafica a delle strutture fisse di binding ?
    Mi spiego meglio: Se utilizziamo questo metodo, dobbiamo conoscere esattamente quali sono i campi bindati per poter sollevare il binding e quindi vincoliamo lo sviluppo della parte grafica impedendo la modifica di template e di datatemplate.

    Ci viene in aiuto un'altra classe molto importante: VisualTreeHelper.

    image

    Questa classe ci permette di navigare all'interno della VisualTree, ovvero all'interno degli oggetti renderizzati nell'interfaccia utente. Con un po' di reflection , e con l'aiuto di BidingOperations possiamo estrarre dalla VisualTree tutte le BindingExpressions ed invocare il metodo UpdateTarget.

    Ecco il codice :

    [code language="c#"]

     

    private static void InvalidateBinding(DependencyObject objectbase)
    {

      // Per ogni oggetto all'interno del ramo di visualtree
      for (int x = 0; x < VisualTreeHelper.GetChildrenCount(objectbase); x++)
      {

        // Leggo l'oggetto nodo
        DependencyObject obj = VisualTreeHelper.GetChild(ww, x) as DependencyObject;

        // Se è un DependencyObject
        if (obj != null)
        {

          // Cerco tutti i fields di
          foreach (FieldInfo finfo in obj.GetType().GetFields())
          {

            // Se il field trovato è una DependencyProperty allora può essere sottoposta a binding
            if (finfo.FieldType.Equals(typeof(DependencyProperty)))
            {

              // Verifico se è sottoposta a binding
              DependencyProperty dpvalue = (DependencyProperty)finfo.GetValue(obj);
              if (BindingOperations.IsDataBound(obj, dpvalue))
              {

                // Estraggo la BindingExpression
                BindingExpression expr = BindingOperations.GetBindingExpression(obj, dpvalue);
               

                // Sollevo il metodo di aggiornamento del binding.
                expr.UpdateTarget();
              }

              // Se il contenuto della DependencyProperty è un DependencyObject allora devo controllare anche tutte DP dell'oggetto contenuto nella DP corrente
              DependencyObject subdpobject = obj.GetValue(dpvalue) as DependencyObject;
              if (subdpobject != null)
                if (!(subdpobject is Visual))
                {
                  foreach (FieldInfo subfinfo in subdpobject.GetType().GetFields())
                  {
                    DependencyProperty subdpvalue = (DependencyProperty)subfinfo.GetValue(subdpobject);
                    if (BindingOperations.IsDataBound(subdpobject, subdpvalue))
                    {
                      BindingExpression subexpr = BindingOperations.GetBindingExpression(subdpobject, subdpvalue);
                      subexpr.UpdateTarget();
                    }
                  }
                }
            }
          }

          // Rendo la funzione ricorsiva in modo da esplorare tutta la VisualTree
          InvalidateBinding(obj);
        }
      }
    }

     

     

    [/code]

dotNet Umbria 2007-2008
Powered by Community Server (Commercial Edition), by Telligent Systems