Diversamente da quello che ci si aspetta, la localizzazione di uno UserControl (e quindi della stessa pagina) in Silverlight non segue strettamente la cultura che è stata impostata nel browser. In effetti se provate a effettuare il binding di un TextBlock su di una proprietà di tipo DataTime vi renderete conto immediatamente di uno strano comportamento, soprattutto se la lingua del browser è impostata su una cultura diversa da en-US:

   1: <TextBlock Text="{Binding Today}" />
   2:  
   3: ... si ottiene ...
   4:  
   5: 2/23/2009 8:20:53 PM

La data espressa in questo esempio è palesemente in formato anglosassone. Il motivo di questo comportamento è dovuto ad un valore di default che viene assegnato all'attributo xml:lang (corrispondente alla proprietà Language di FrameworkElement). Se infatti provate a scrivere un IValueConverter custom e a mettere un breakpoint nel metodo Convert() vi renderete rapidamente conto che il valore che arriva al metodo come CultureInfo è "en-US", indipendentemente dalla cultura che valorizza la proprietà CultureInfo.CurrentCulture.

La documentazione in proposito è piuttosto chiara:

The Language property will default to "en-US" if not otherwise set either through the property itself or through processing the xml:lang attribute.

Correggere questo comportamento è piuttosto semplice. E' sufficiente valorizzare la proprietà Language dello UserControl cui fa riferimento la pagina nel costruttore del controllo in questo modo:

   1: this.Language = XmlLanguage.GetLanguage(CultureInfo.CurrentCulture.Name);

In WPF c'è più o meno il medesimo problema che ha una soluzione un po' più elegante, dato che è possibile effettuare l'override dei metadati di una DependencyProperty. Con Silverlight 2.0 invece occorre settare esplicitamente tale valore in ogni UserControl.

 


Stamane, girovagando all'interno del framework come mi capita spesso, in compagnia del fido Reflector, mi sono imbattuto in un paio di classi che non sapevo facessero parte di Silverlight 2.0. Di AsyncOperationManager e AsyncOperation avevo già letto, ma non avevo mai pensato di poterle sfruttare nel plugin per sincronizzare un thread di download e la user interface. Un rapido refactoring mi ha infine confermato che la tecnica funziona e anche bene consentendo il grande vantaggio di non doversi portare in giro un riferimento al Dispatcher o al SynchronizationContext.

Ecco un breve esempio di come usare AsyncOperationManager; Poniamo di dover scaricare un file dalla rete, facendo uso di HttpWebRequest. L'uso di BeginGetResponse() implica una chiamata asincrona e al rientro per notificare l'interfaccia, ad esempio sollevando un evento, saremmo costretti all'operazione di sincronizzazione:

   1: public class DownloadManager
   2: {
   3:     /// <summary>
   4:     /// Enqueues the download.
   5:     /// </summary>
   6:     /// <param name="uri">The URI.</param>
   7:     public void DownloadFile(Uri uri)
   8:     {
   9:         DownloadState state = new DownloadState
  10:         {
  11:             Operation = AsyncOperationManager.CreateOperation(null),
  12:             Request = (HttpWebRequest)WebRequest.Create(uri)
  13:         };
  14:  
  15:         state.Request.Method = "GET";
  16:         state.Request.BeginGetResponse(new AsyncCallback(Download_Completed), state);
  17:     }
  18:  
  19:     /// <summary>
  20:     /// Download_s the completed.
  21:     /// </summary>
  22:     /// <param name="result">The result.</param>
  23:     private void Download_Completed(IAsyncResult result)
  24:     {
  25:         DownloadState state = result.AsyncState as DownloadState;
  26:  
  27:         if (state != null)
  28:         {
  29:             try
  30:             {
  31:                 HttpWebResponse response = (HttpWebResponse)state.Request.EndGetResponse(result);
  32:  
  33:                 if (response.StatusCode == HttpStatusCode.OK)
  34:                     state.Operation.PostOperationCompleted(
  35:                         new SendOrPostCallback(OnDownloadCompleted), response);
  36:                 else if (response.StatusCode == HttpStatusCode.NotFound)
  37:                     state.Operation.PostOperationCompleted(
  38:                         new SendOrPostCallback(OnDownloadFailed), new InvalidOperationException());
  39:             }
  40:             catch (Exception ex)
  41:             {
  42:                 state.Operation.PostOperationCompleted(
  43:                     new SendOrPostCallback(OnDownloadFailed), ex);
  44:             }
  45:         }
  46:     }
  47:  
  48:     /* ... omissis ... */
  49:  
  50:     private class DownloadState
  51:     {
  52:         /// <summary>
  53:         /// Gets or sets the operation.
  54:         /// </summary>
  55:         /// <value>The operation.</value>
  56:         public AsyncOperation Operation { get; set; }
  57:         /// <summary>
  58:         /// Gets or sets the request.
  59:         /// </summary>
  60:         /// <value>The request.</value>
  61:         public HttpWebRequest Request { get; set; }
  62:     }
  63: }

In buona sostanza un attimo prima di avviare il download chiedo al AsyncOperationManager di fornirmi un AsyncOperation. Quest'ultima viene inserita assieme alla Request in una classe DownloadState e al momento giusto viene recuperata per effettuare la sincronizzazione mediante PostOperationCompleted().

In questo modo il componente sarà facilmente riutilzzabile, senza doversi portare dietro una referenza ad un componente di Interfacce...

Technorati Tag: ,

E' di quest'oggi la pubblicazione dei sorgenti dei controlli Silverlight 2.0 RTW presenti in System.Windows.dll. Si tratta di una occasione ottima per imparare meglio come realizzare controlli riusabili che sfruttano appieno i ControlTemplate.

Link:

Technorati Tag: ,

Giusto ieri è diventato pubblico un aggiornamento alla datagrid di Silverlight 2.0 che risolve alcuni malfunzionamenti. Nei post che riporto qui sotto trovate i dettagli dell'aggiornamento, le breaking changes e le istruzioni per installare la patch:

http://blogs.msdn.com/brada/archive/2008/12/19/silverlight-2-datagrid-december-2008-release.aspx

http://silverlight.net/forums/t/59990.aspx

Technorati Tags: ,

Ecco un interessante progetto che sicuramente può trovare delle applicazioni sia ludiche che professionali. Si tratta di un engine fisico sviluppato per Silverlight 2.0, porting di un framework open source.

Codeplex: http://www.codeplex.com/PhysicsHelper

Home: http://www.andybeaulieu.com/Default.aspx?tabid=67&EntryID=128


Stamattina ho trovato la notizia che è stata rilasciata la versione di Dicembre del porting per Silverlight 2.0 del framework Unity. Unity è un framework per la dependency injection già rilasciato per altri ambienti. Interessante il porting per Silverlight che dovrò sicuremente provare.

Download: Unity Application Block 1.2 for Silverlight - December 2008


Interessante sessione seguita nel primo pomeriggio - al ritorno da una mattinata turistica nel centro di Barcellona - a proposito delle cose da non fare nelle applicazioni Silverlight. A parte alcuni consigli che non sempre possono essere seguiti (ad esempio sulla modalità WindowLess) molti altri tip sono sicuramente da considerare:

Install experience: E' utile modificare l'immagine di default (quella che chiede di installare Silverlight) per dare un'idea dell'applicazione cui l'utente sta accedendo. Una buona soluzione è quella di usare una immagine grayed che mostra l'interfaccia dell'applicazione cui si sta accedendo.

Load Experience: Minimizzare il tempo di caricamento. Il consiglio può sembrare ovvio ma naturalmente non è mai ripetuto troppo. Quindi per non mettere elementi eccessivamente pesanti all'interno di un XAP è opportuno usare

    • download on demand
    • cache nello isolated storage

Splash screen: L'uso di uno splash screen consente di migliorare la user experience e  di comunicare all'utente le informazioni di download. Esiste per questo una funzione javascript per tracciare il download.

Isolated Storage: la cache di dll, immagini, risorse, e altro nell'Isolated Storage oltre a consentire di dare migliori performance al download dell'applicazione può consentire di creare applicazioni che si avviano anche se si è offline.

Media: evitare di ridimensionare i video e i font. Questo perchè si tratta in entrambi i casi di operazioni molto onerose. Inutile dire che esistono casi in cui questo non è possibile, ma tenerlo presente è opportuno.

Processori multicore: Attenzione che Silverlight si avvantaggia molto dei processori multi-core. Tenetelo presente mentre sviluppate perchè poi non tutti i client avranno le stesse prestazioni della vostra macchina di sviluppo.

Design: Usare Dati di test per facilitare i designer. A questo scopo esistono due modi per individuare se si è a DesignTime:

    • DesignerProperties.GetIsInDesigneMode(this) - internamente ad UserControls
    • HtmlPage.IsEnabled - da altre classi

Debug settings: usare enableRedrewRegions = true per verificare quanto oneroso è il redraw dell'interfaccia. In questa modalità diagnostica il plugin lo renderà evidente con delle colorazioni particolari.

Elementi nascosti: Usare Visibility.Collapse invece che Opacity="0". Questo perchè gli elementi non vengono disegnati solo con l'attributo Visibility mentre con l'Opacity avviene lo stesso una valutazione dell'elemento una fase di rendering dello stesso.

Elementi di tipo Path: Non usare Width e Height con gli oggetti di tipo Path perchè è molto oneroso.

Memoria: usare GC.GetTotalMemory() per verificare il suo utilizzo.

Un'altro utile consiglio è stato quello di utilizzare il tool XPerf per monitorare iexplore.exe -> agcore.dll. Il tool è disponibile nel Windows Performance Tools Kit, v.4.1.1 (QFE)


Capita di rado, ma talvolta è necessario poter definire il binding di una proprietà da codice anzichè dal markup XAML. In questi casi tradurre la normale sintassi basata su una Markup Extension in codice vero e proprio non è immediato. Io stesso ho dovuto cercare un bel po’ prima di capire quali sono gli oggetti coinvolti e arrivare a tradurre i tutto in poche righe di codice C#.

Ma partiamo da un ipotetico Binding effettuato come di consueto da XAML:

   1: <TextBox x:Name="txtFirstName" Text="{Binding FirstName, Mode=TwoWay}" />

In questa singola riga è racchiuso e semplificato un po’ di codice. Innanzitutto la creazione di un oggetto che si occupa di fare il DataBinding e che incapsula le proprietà che abbiamo valorizzato, in questo caso Source e Mode, ma volendo anche alte come ad esempio il Converter e i suoi parametri. Infine l’assegnazione di questo oggetto alla proprietà che deve essere bindata.

Ecco in soldoni il codice che andrebbe scritto per simulare la Markup Extensione di cui sopra:

   1: // crea l'oggetto che contiene i parametri del binding
   2:  
   3: Binding binding = new Binding("FirstName")
   4: {
   5:     // sorgente del binding (DataContext)
   6:     Source = source,
   7:  
   8:     // modalità di binding
   9:     Mode = mode
  10: };
  11:  
  12: // assegno il binding alla textbox
  13:  
  14: element.SetBinding(TextBox.TextProperty, binding);

Questo non è molto codice, ma scriverlo ripetutamente magari su decine di proprietà è piuttosto laborioso e può aprire la strada a errori e omissioni. E allora perchè non scrivere un metodo che lo incapsuli. Anzi, visto che parliamo di usare il framework 3.5 la soluzione migliore è di scrivere un certo numero di Extension Methods che mettano a disposizione un po’ di overload per i casi più disparati:

   1: public static void Bind(
   2:     this FrameworkElement element, 
   3:     DependencyProperty property, 
   4:     string path, 
   5:     object source, 
   6:     BindingMode mode, 
   7:     IValueConverter converter, 
   8:     object converterParameter, 
   9:     CultureInfo converterCulture)
  10: {
  11:     Binding binding = new Binding(path)
  12:     {
  13:         Source = source,
  14:         Mode = mode
  15:     };
  16:  
  17:     if (converter != null)
  18:     {
  19:         binding.Converter = converter;
  20:         binding.ConverterCulture = converterCulture ?? CultureInfo.CurrentCulture;
  21:         binding.ConverterParameter = converterParameter;
  22:     }
  23:  
  24:     element.SetBinding(property, binding);
  25: }

In questo spezzone di codice viene mostrata la versione più ampia dei metodi in questione, cioè quella che accetta più parametri. Lascio a voi scegliere la combinazione di parametri che più vi aiuta. Se poi volete i miei vi segnalo che questa classe sarà disponibile nella prossima release della mia Silverlight Library 1.0. Ma vediamo come usare i metodi in questione dimostrando una versione con un po’ meno di parametri:

   1: txtFirstName.Bind(TextBox.TextProperty, "FirstName", source, BindingMode.TwoWay);

Questa riga effettua esattamente lo stesso binding dell’esempio scritto in XAML poco sopra. Siamo così riusciti nell’intento di semplificare il lavoro e di ridurre soprattutto la quantità di codice da scrivere.


Silverlight-Toolkit_w E' disponibile per il download il nuovo e appetitoso Silverlight Toolkit. Si tratta di una interessante libreria di controlli per Silverlight 2.0 che aggiungono funzionalità notevoli. Ecco i controlli disponibili

Interessante anche la politica dei rilasci che suddivide i componenti di Quality Bands per discernere quelli più stabili da quelli più nuovi e meno testati.

I più attenti poi avranno notato che l'url del toolkit () corrisponde a quello della mia Silverlight Library. Si tratta di un regalo che ho deciso di fare accogliendo la richiesta del team del toolkit. La mia libreria ora la potete trovare al seguente indirizzo:

Technorati Tag: ,