Technology Experience

.NET World

Programmazione, libri, snippet di codice, articoli tecnici

.NET World

Gestire lo swipe nelle quattro direzioni su un qualsiasi elemento della UI

Vi piacerebbe poter gestire lo swipe verso sinistra/destra/alto/basso, su qualsiasi elemento della UI, e sfruttando ovviamente Model-View-ViewModel, per gli amici MVVM? Ovvero, poter scrivere un blocco di XAML così?

<Border Background="Cyan" Width="500" Height="200"
        helper:SwipeCommandHelper.Up="{Binding IncrementCommand}"
        helper:SwipeCommandHelper.Down="{Binding DecrementCommand}"
        helper:SwipeCommandHelper.Right="{Binding MultiplyCommand}"
        helper:SwipeCommandHelper.Left="{Binding DivideCommand}" />

Al Border qui sopra ho dato la possibilità di eseguire un command differente in base a quale tipo di swipe l’utente fa sopra il Border stesso. Se scorro il dito verso l’alto, eseguo l’IncrementCommand. Se lo scorro verso destra, eseguo il MultiplyCommand. E così via. La cosa bella è che la classe SwipeCommandHelper può agganciarsi a qualsiasi oggetto FrameworkElement.

Il codice è abbastanza semplice.

public static class SwipeCommandHelper
{
    public static ICommand GetUp(DependencyObject obj)
    {
        return (ICommand)obj.GetValue(UpProperty);
    }

    public static void SetUp(DependencyObject obj,
        ICommand value)
    {
        obj.SetValue(UpProperty, value);
    }

    public static readonly DependencyProperty UpProperty =
        DependencyProperty.RegisterAttached("Up",
        typeof(ICommand),
        typeof(SwipeCommandHelper),
        new PropertyMetadata(null,
        new PropertyChangedCallback(Setup)));
}

Il codice qui sopra è una semplificazione, e riporta solo la dependency property per gestire lo swipe verso l’alto. La dependency property è UpProperty, ed è di tipo ICommand. Il valore predefinito è null, e quando il suo valore viene modificato viene invocato il metodo Setup(). Questo metodo fa un po’ di code, ma il succo del discorso è che si aggancia all’evento ManipulationStarted del controllo, in modo tale che quando l’utente farà qualche gesture noi potremo intercettare questo evento ed eseguire il command associato. Il codice del metodo Setup() è il seguente:

private static void Setup(DependencyObject obj,
            DependencyPropertyChangedEventArgs e)
{
    FrameworkElement ctl = obj as FrameworkElement;

    if (ctl != null)
    {
        ICommand oldValue = (ICommand)e.OldValue;
        ICommand newValue = (ICommand)e.NewValue;

        if (oldValue == null && newValue != null)
        {
            {
                if (GetAttached(ctl) == false)
                {
                    ctl.ManipulationMode = ManipulationModes.TranslateX |
                        ManipulationModes.TranslateY;
                    SetAttached(ctl, true);
                    ctl.ManipulationCompleted += ctl_ManipulationCompleted;
                }
            }
        }
        else if (oldValue != null && newValue == null)
        {
            ctl.ManipulationMode = ManipulationModes.None;
            ctl.SetValue(AttachedProperty, false);
            ctl.ManipulationCompleted -= ctl_ManipulationCompleted;
        }
    }
}

Aggiungo due note importanti: per default, in Windows 8 su un oggetto sulla UI non si possono intercettare gesture. Con Windows Phone, per esempio, è sufficiente sottoscrivere l’evento ManipulationCompleted ed il gioco è fatto. Con Windows 8, invece, prima dovete impostare il ManipulationMode, poi sottoscrivete l’evento. Per default, ManipulationMode è None, e quindi anche se impazzite a fare gesture sopra l’oggetto, nel codice non accadrà nulla. Ma non abbiate paura: ci pensa il mio codice a fare tutto ciò: non appena associate un command ad uno dei quattro tipi di swipe, vado automaticamente ad impostare il ManipulationMode su TranslateX | TranslateY.

A questo punto non ci resta che scrivere il codice dell’evento ManipulationCompleted:

static void ctl_ManipulationCompleted(object sender,
            ManipulationCompletedRoutedEventArgs e)
{
    var element = sender as FrameworkElement;
    ICommand command = null;

    // x < 0 --> verso sinistra
    // x > 0 --> verso destra
    var x = e.Cumulative.Translation.X;

    // y < 0 --> verso l'alto
    // y > 0 --> verso il basso
    var y = e.Cumulative.Translation.Y;

    if (y < 0 && Math.Abs(y) > Math.Abs(x))
    {
        command = GetUp(element);
    }
    else if (y > 0 && Math.Abs(y) > Math.Abs(x))
    {
        command = GetDown(element);
    }
    else if (x > 0 && Math.Abs(x) > Math.Abs(y))
    {
        command = GetRight(element);
    }
    else if (x < 0 && Math.Abs(x) > Math.Abs(y))
    {
        command = GetLeft(element);
    }

    if (command != null)
    {
        if (command.CanExecute(null))
        {
            command.Execute(null);
        }
    }
}

All’interno dell’evento posso capire quale tipo di gesture ha fatto l’utente sul controllo. Avendo impostato il ManipulationMode solamente alle traslazioni X e Y, sappiamo per forza di cose che arriveremo qui solo in presenza di questa gesture. Capisco la direzione del movimento seguendo due fattori:

  • il valore di x e y, ovviamente
  • quale dei due valori x e y ha più rilevanza l’uno con l’altro (spostando il dito verso l’alto, probabilmente lo sposterete anche a sinistra o a destra – grazie a Math.Abs() capisco quale asse ha avuto più rilevanza nella gesture)

I base alla direzione, ottengo l’ICommand associato. Ovviamente può anche essere null, nel caso in cui non abbia bindato alcun command. Se invece c’è, invoco il CanExecute, ed in caso di esito positivo, eseguo finalmente il comando associato.

Si potrebbe pensare di aggiungere il CommandParameter per ciascuna delle quattro direzioni, ma a me non serve, per cui lo lascio fare voi!

Il codice completo della classe pronta all’uso è qui. E’ stata scritta in circa 30 minuti, ci sarà sicuramente qualche bug: avvisatemi e correggiamola/miglioriamola insieme!

Send to Kindle
.NET World

WinRT, Xaml, TextBox e selezione del testo

Ho cercato di riassumere lo scopo del post nel titolo, ma lo riassumo qui per maggiore chiarezza. Lo scopo è quello di scatenare il ‘seleziona tutto il testo’ quando una TextBox prende il focus. Chiaramente in ottica MVVM, quindi senza sporcare il code-behind della pagina. E’ uno dei comportamenti di default che più mi mancano, anche perchè sarebbe bastato aggiungere una proprietà AutoSelectAll nella TextBox e parecchi developer sarebbero stati più contenti.

Fortunatamente è piuttosto semplice scrivere una classe che raggiungere lo stesso risultato.

public static class TextBoxHelper
{
    public static bool GetAutoSelectAll(DependencyObject obj)
    {
        return (bool)obj.GetValue(AutoSelectAllProperty);
    }

    public static void SetAutoSelectAll(DependencyObject obj,
        bool value)
    {
        obj.SetValue(AutoSelectAllProperty, value);
    }

    public static readonly DependencyProperty AutoSelectAllProperty =
        DependencyProperty.RegisterAttached("AutoSelectAll",
        typeof(bool),
        typeof(TextBoxHelper),
        new PropertyMetadata(null,
        new PropertyChangedCallback(Setup)));

    private static void Setup(DependencyObject obj,
        DependencyPropertyChangedEventArgs e)
    {
        TextBox tb = obj as TextBox;

        if (tb != null)
        {
            bool oldValue = (bool)e.OldValue;
            bool newValue = (bool)e.NewValue;

            if (oldValue == false && newValue == true)
            {
                tb.GotFocus += tb_GotFocus;
            }
            else if (oldValue == true && newValue == false)
            {
                tb.GotFocus -= tb_GotFocus;
            }
        }
    }

    static void tb_GotFocus(object sender, RoutedEventArgs e)
    {
        TextBox tb = sender as TextBox;

        if (tb != null)
        {
            tb.SelectAll();
        }
    }
}

Questa classe va ovviamente utilizzata nello XAML:

<TextBox common:TextBoxHelper.AutoSelectAll="True" />

Quando questa TextBox prende il focus, il testo in esso contenuto viene automaticamente selezionato grazie al behavior che gli abbiamo agganciato. Tutto questo direttamente nello XAML, senza code-behind, in perfetta sintonia con MVVM.

Send to Kindle
.NET World

Windows App Cert Kit, rapporto tra colore di sfondo e colore del testo

Prima di poter tentare il submit di un’app Windows 8 per lo Store, è necessario far girare localmente sul vostro PC il tool in oggetto, il Windows App Cert Kit. Non ve ne potete scordare, perchè vi viene automaticamente proposto da Visual Studio appena terminate di creare il package da uploadare nella dashboard. Questo tool fa un sacco di controlli in locale, e vi segnala eventuali problemi che causerebbero la bocciatura dell’app stessa, senza però farvi perdere tempo (qualche giorno) prima di avere un feedback da parte di Microsoft.

Ieri sera, facendolo girare a seguito della creazione di un nuovo package, mi ha bocciato l’app con la seguente unica motivazione:

Il rapporto del contrasto tra colore di sfondo e colore del testo in primo piano è 1,16. Questo rapporto deve essere maggiore o uguale a 1,5.

La morale, ben spiegata dal testo, è che non c’è abbastanza contrasto tra il colore di sfondo ed il colore del testo. Un valore 1 significa che in pratica avete lo stesso colore, il che è assurdo. Prima che vi mettiate a ripensare un altro colore di sfondo, a rifare i loghi, a ridisegnare le icone, etc. etc., aprite il file di manifest dell’app (Package.appxmanifest) e date un’occhiata alle seguenti impostazioni:

  • Foreground text
  • Background color

Il Windows App Cert Kit non misura quel rapporto guardando veramente i colori delle vostre Page, ma guarda questi due parametri. Nel mio caso, avevo impostato un colore di sfondo #F2EEDC, mentre il colore del testo era impostato a Light. In pratica, un colore molto simile al bianco, con il testo bianco. Direi poco contrasto, come riportato dal tool! La realtà quindi è che la mia app era perfettamente visibile ed usabile, perchè nei vari elementi della UI non usavo quel valore, ma avevo io impostato “Black” tramite una mia risorsa, senza usare le risorse di sistema.

E’ bastato mettere il ‘Foreground text’ a Dark e l’app ha passato il test con successo.

Send to Kindle
.NET World

L’AdControl contiene una WebView che si ruba il focus

Ok ok, il titolo di questo post è un po’ assurdo. Se siete sviluppatori di app Windows 8, probabilmente vi è già capitato. Oppure no. Nel qual caso, prendete appunti. Immaginate una pagina di un’app Windows 8 strutturata – in soldoni – da una Grid con tre righe, il cui contenuto è:

  • prima riga: intestazione, logo, quello che volete, nulla di importante
  • seconda riga: ScrollViewer a scorrimento orizzontale, che contiene UI più complessa, che rappresenta il contenuto vero e proprio dell’app
  • terza riga: controllo AdControl per mostrare un po’ di pubblicità

Ora, ieri sera mi è accaduta una cosa strana. Dal punto di vista utente, voglio che i miei utenti possano utilizzare la rotella del mouse per scorrere lo ScrollViewer indicato prima. Ricordiamocelo: le app Windows 8 non sono solamente touch, ma devono funzionare anche con tastiera & mouse. Appena l’app viene avviata, l’utente deve fin da subito poter rotellare usare la rotella del mouse per scorrere avanti ed indietro i contenuti. A me questo meccanismo non funzionava, sebbene avessi impostato nello XAML le proprietà IsTabStop=”true” e TabIndex=”"0” sullo ScrollViewer. Se invece andavo cliccare manualmente all’interno dello ScrollViewer, ecco che la rotella aveva effetto.

Vi riassumo in due righe circa 2 ore di litigate col codice per capire il problema. Sgrunt.

In pratica ho scoperto che il controllo AdControl per l’advertising contiene al suo interno un controllo WebView che – guarda caso – si appropriava del focus. Quindi all’avvio dell’app lo ScrollViewer perdeva automaticamente il focus, e la rotella smetteva di funzionare.

Come ho risolto? Così:

<adv:AdControl IsEnabled="False"
ApplicationId="xyz"
AdUnitId="xyz" />

.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, “Courier New”, courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }

Impostando la proprietà IsEnabled a false, l’AdControl, e la WebView al suo interno, non si prendono più il focus e tutto funziona egregiamente. Sulla UI non c’è alcuna differenza: il banner appare come deve apparire, è cliccabile, per cui non cambia nulla: ma essendo disabilitato non può prendersi il focus.

Ultima nota: non so se questo comportamento accade sempre o magari nel mio caso avevo una situazione più complessa o fuorviante. Ho risolto nel modo descritto: se dovesse capitare anche a voi, sapete quale potrebbe essere una possibile causa.

Send to Kindle
.NET World

Layout generale di un’app Windows 8

Sebbene condividano lo stesso approccio, c’è una enorme grande differenza tra costruire un’app per Windows Phone 7 ed un’app per Windows 8. Quest’ultima deve adattarsi alla risoluzione del device su cui sta girando (notebook, ultrabook, tablet, etc.) e soprattutto deve essere in grado di adattarsi a tre modalità di visualizzazione, ovvero:

  • landscape (quando tenete il pc in orizzontale)
  • portrait (quando tenete il pc in verticale)
  • snapped (quando andate ad agganciare l’app sul bordo sinistro o destro dello schermo)

Sappiamo tutti che il modo migliore è lasciare che il runtime .NET o WinRT faccia tutto il lavoro di intercettazione del cambiamento della vista: tramite il Visual State Manager possiamo andare a modificare qualsiasi proprietà della UI per fare in modo che la vista si adatti. In pratica, possiamo, possiamo per esempio rendere visibili/invisibili dei controlli, possiamo stringere/allargare colonne e via dicendo. Questo implica una cosa piuttosto importante: ad ogni controllo su cui vogliamo interagire tramite Visual State Manager dobbiamo assegnare un nome.

Io dal canto mio ho trovato un modo che mi piace di più, riassunto del codice XAML qui sotto.

<Page>
    <Page.Resources>
    </Page.Resources>
    <Grid>
        <”Panel” x:Name="FullScreenMainGrid">
        </”Panel”>
        <Panel x:Name="PortraitMainGrid">
        </”Panel”>
        <Panel x:Name="SnappedMainGrid">
        </”Panel”>
    </Grid>
</Page>

.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, “Courier New”, courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }

In pratica avete una Grid esterna che non fa altro che contenere altri tre Panel al suo interno (tali Panel possono essere Canvas, altre Grid, StackPanel, qualsiasi pannello previsto da WinRT – io ho messo Panel giusto per rendere l’idea). In modo alla visualizzazione corrente, solo uno di questi Panel sarà visibile in un dato momento. C’è il Panel che contiene il layout specifico per il landscape, poi c’è quello per il portrait, ed infine c’è quello dedicato alla visualizzazione snapped. Può sembrare una cosa prolissa, eppure io mi trovo bene così. So che toccando una certa porzione di XAML non vado a modificare le altre viste. E se lavorate con MVVM, basta fare binding e siete a posto: il codice del viewmodel che governa la view è sempre lo stesso.

Lo parte di XAML relativa al Visual State Manager è la seguente:

<VisualStateManager.VisualStateGroups>
    <VisualStateGroup x:Name="ApplicationViewStates">

        <VisualState x:Name="FullScreenLandscape">
            <Storyboard>
                <ObjectAnimationUsingKeyFrames
                    Storyboard.TargetName="FullScreenMainGrid"
                    Storyboard.TargetProperty="Visibility">
                    <DiscreteObjectKeyFrame KeyTime="0" Value="Visible"/>
                </ObjectAnimationUsingKeyFrames>
                <ObjectAnimationUsingKeyFrames
                    Storyboard.TargetName="SnappedMainGrid"
                    Storyboard.TargetProperty="Visibility">
                    <DiscreteObjectKeyFrame KeyTime="0" Value="Collapsed"/>
                </ObjectAnimationUsingKeyFrames>
                <ObjectAnimationUsingKeyFrames
                    Storyboard.TargetName="PortraitMainGrid"
                    Storyboard.TargetProperty="Visibility">
                    <DiscreteObjectKeyFrame KeyTime="0" Value="Collapsed"/>
                </ObjectAnimationUsingKeyFrames>
            </Storyboard>
        </VisualState>

        <VisualState x:Name="Filled">

        </VisualState>

        <VisualState x:Name="FullScreenPortrait">
            <Storyboard>
                <ObjectAnimationUsingKeyFrames
                    Storyboard.TargetName="FullScreenMainGrid"
                    Storyboard.TargetProperty="Visibility">
                    <DiscreteObjectKeyFrame KeyTime="0" Value="Collapsed"/>
                </ObjectAnimationUsingKeyFrames>
                <ObjectAnimationUsingKeyFrames
                    Storyboard.TargetName="SnappedMainGrid"
                    Storyboard.TargetProperty="Visibility">
                    <DiscreteObjectKeyFrame KeyTime="0" Value="Collapsed"/>
                </ObjectAnimationUsingKeyFrames>
                <ObjectAnimationUsingKeyFrames
                    Storyboard.TargetName="PortraitMainGrid"
                    Storyboard.TargetProperty="Visibility">
                    <DiscreteObjectKeyFrame KeyTime="0" Value="Visible"/>
                </ObjectAnimationUsingKeyFrames>
            </Storyboard>
        </VisualState>

        <VisualState x:Name="Snapped">
            <Storyboard>
                <ObjectAnimationUsingKeyFrames
                    Storyboard.TargetName="FullScreenMainGrid"
                    Storyboard.TargetProperty="Visibility">
                    <DiscreteObjectKeyFrame KeyTime="0" Value="Collapsed"/>
                </ObjectAnimationUsingKeyFrames>
                <ObjectAnimationUsingKeyFrames
                    Storyboard.TargetName="SnappedMainGrid"
                    Storyboard.TargetProperty="Visibility">
                    <DiscreteObjectKeyFrame KeyTime="0" Value="Visible"/>
                </ObjectAnimationUsingKeyFrames>
                <ObjectAnimationUsingKeyFrames
                    Storyboard.TargetName="PortraitMainGrid"
                    Storyboard.TargetProperty="Visibility">
                    <DiscreteObjectKeyFrame KeyTime="0" Value="Collapsed"/>
                </ObjectAnimationUsingKeyFrames>
            </Storyboard>
        </VisualState>
    </VisualStateGroup>
</VisualStateManager.VisualStateGroups>

.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, “Courier New”, courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }

Il concetto è molto semplice: per ogni stato (FullScreenLandscape, FullScreenPortrait, Snapped) rendete invisibili due Panel e rendete visibile quello previsto per quello stato.

In questo modo evitate di impazzire assegnando nomi ai controlli qua e là, e siete assolutamente sicuri di come viene renderizzata una vista in un certo stato. Ogni vista è indipendente dalle altre. Prima cercavo di stringere/allargare colonne e righe di una Grid, oppure di rimpicciolire il FontSize delle TextBlock e degli altri controlli, ma finivo sempre per impazzire. Alla fine penso che la strada più lineare, semplice e comoda da gestire sia questa.

Che ne dite?

Send to Kindle
.NET World

Advertising delle app Windows Phone

Mi sono scontrato con due problemi, secondo me piuttosto oscuri, mentre lavoravo sull’advertising di una mia app per Windows Phone. Piuttosto oscuri perchè su Internet ho trovato ben poco.

Parto dal presupposto che sappiate già di cosa parlo: controllo AdControl, registrazione su PubCenter, qualche concetto di XAML, capabilities di un’app Windows Phone, etc.

Il primo problema è il seguente: nonostante voi mettiate il controllo AdControl sulla vostra pagina XAML, quando mandate in esecuzione l’app esso improvvisamente scompare. Questo vale sia all’interno del simulatore, sia su un device vero e proprio. E continua a valere anche se impostate i valori delle seguenti proprietà:

  • proprietà ApplicationId a “test_client”
  • proprietà AdUnitId a “Image480_80”

Questo due valori permettono di testare il motore di advertising, senza realmente accedere ai banner pubblicitari veri e propri. A design-time vedete il vostro bel controllo, ma se mandate in esecuzione sparisce tutto nel giro di un decimo di secondo. Attenzione: non sto dicendo che non vedete alcun banner: è proprio il controllo che sparisce dalla Page. Beh, se questa cosa capita anche a voi, fate una bella cosa: sottoscrivete l’evento ErrorOccured nello XAML e nel code-behind della pagina potete semplicemente scrivere (vado a memoria):

MessageBox.Show(e.Error.ToString())

Nel mio caso, mancavano alcune capabilities necessarie per far funzionare l’advertising, ovvero:

  • ID_CAP_PHONEDIALER
  • ID_CAP_IDENTITY_USER
  • ID_CAP_MEDIALIB
  • ID_CAP_WEBBROWSERCOMPONENT

Se non mettete queste capabilities nella vostra app, l’advertising non andrà mai.

L’altro problema, invece, è ancora più oscuro, almeno fintantochè non trovate e capite la soluzione. Sempre grazie all’evento ErrorOccured citato prima, potreste ottenere una AdException con pressapoco un errore simile a “An unexpected error occured during response processing (Name: ECN)”. Non state a sbatterci la testa: impostate la lingua del telefono su inglese e riprovate. Se funziona, significa che le categorie impostate sulla unità di annunci non hanno banner per la lingua italiana. Quindi o tornare su PubCenter e provate a cambiare categorie, oppure boh! L’importante è che avete capito è che non avete un errore nel codice o nello XAML della vostra app, ma è semplicemente qualcosa che riguarda la configurazione del vostro advertising server-side. A meno che, chiaramente, non dobbiate cambiare ApplicationId o AdUnitId nello XAML.

Send to Kindle
.NET World

App Windows 8, suspending e resuming dello stato

Una delle cose che ho trovato più complicate da capire quando si sviluppa un’app per Windows 8 Metro-style è la gestione del suspending e del resuming. Non perchè sia di per sè complicata, quanto perchè se aprite Visual Studio 2012 e iniziate con un qualsiasi progetto per il Windows Store, vi viene già fornita un set di classi che – sotto sotto – fanno il lavoro sporco di salvataggio e di recupero dei dati. Quindi secondo me non si capisce esattamente cosa faccia davvero il sistema operativo di base, e cosa invece viene fatto dal template classico di un’app Windows Store.

Andiamo con calma. Supponiamo di dover fare una semplice app Metro che chiede all’utente tre informazioni: cognome, nome e data di nascita. Il tutto avviene in modo molto semplice, con tre TextBox. Aprite Visual Studio 2012, cominciate con un progetto scegliendo il template “Blank App (XAML)”, che si trova all’interno dei folder Visual C# –> Windows Store. Editiamo la MainPage.xaml ed incollate questo:

<Grid Background="{StaticResource ApplicationPageBackgroundThemeBrush}">
    <Grid.ColumnDefinitions>
        <ColumnDefinition />
        <ColumnDefinition />
    </Grid.ColumnDefinitions>
    <Grid.RowDefinitions>
        <RowDefinition Height="Auto" />
        <RowDefinition Height="Auto" />
        <RowDefinition Height="Auto" />
    </Grid.RowDefinitions>
        
    <TextBlock Grid.Row="0" Grid.Column="0"
                Text="Cognome" FontSize="34" VerticalAlignment="Center" Margin="4" />
    <TextBlock Grid.Row="1" Grid.Column="0"
                Text="Nome" FontSize="34" VerticalAlignment="Center" Margin="4" />
    <TextBlock Grid.Row="2" Grid.Column="0"
                Text="Data di Nascita" FontSize="34" VerticalAlignment="Center" Margin="4" />

    <TextBox Grid.Row="0" Grid.Column="1" Text="Damiani"
                x:Name="Cognome" FontSize="32" VerticalAlignment="Center"
                Foreground="Black" Margin="4" />
    <TextBox Grid.Row="1" Grid.Column="1" Text="Igor"
                x:Name="Nome" FontSize="32" VerticalAlignment="Center"
                Foreground="Black" Margin="4" />
    <TextBox Grid.Row="2" Grid.Column="1" Text="01/01/1900"
                x:Name="DataNascita" FontSize="32" VerticalAlignment="Center"
                Foreground="Black" Margin="4" />

</Grid>

 

Nulla di insolito. Per ora evitiamo MVVM. Abbiamo messo tre TextBox, ciascuna con il suo nome. Adesso, facciamola girare ed osserviamone il comportamento.

  1. All’avvio vi vengono ovviamente proposti i valori di default cablati nello XAML (il mio nome, cognome, e la mia data di nascita falsa)
  2. Provate a cambiare tali valori, scrivendoci i vostri, o quello che volete
  3. Tornate in Visual Studio a suon di Alt+Tab e – utilizzando la toolbar Debug Location cliccate sull’opzione Suspend and Shutdown: questa opzione simula il fatto che Windows termina la vostra app perchè ha bisogno di memoria, o per qualsiasi altro motivo. Ad esempio, se passate da un’app Metro all’altra, quella che finisce in background viene sospesa, per non consumare memoria, batteria, etc. etc.
  4. Se avete fatto correttamente il passo (3), l’app viene terminata: Visual Studio ed il suo debugger terminano ovviamente la sua esecuzione
  5. Se rilanciate l’app, DEVE accadere una cosa molto importante: l’app parte ma NON DEVE riproporre i valori di default, ma deve recuperare il precedente stato!!

A questo punto è necessario soffermarsi sul punto (5). Per chi viene da un avanzato sviluppo di app Windows Phone come me, troverà una notevole differenza. Quando toccate una tile su Windows Phone, l’app parte SEMPRE da zero, senza alcun bisogno di restorare il precedente stato. Questo NON E’ VERO con Windows 8. Il motivo è presto detto: con Windows Phone non potete chiudere manualmente l’app, mentre con Windows 8 sì. Con Windows 8, fin tanto che non la chiudete, essa deve SEMPRE recuperare lo stato che l’utente aveva. In breve: con il nostro piccolo test di prima l’app è stata sospesa (ovvero: non l’abbiamo volutamente chiusa), per cui se la rilanciamo (dalla tile sulla Start Page oppure prendendola dalle app aperte, usando lo charm sulla sinistra dello schermo) essa deve recuperare lo stato, proponendoci non i valori di default, ma quelli che avevamo modificato. Questo adesso ovviamente non accade, perchè in questo momento il meccanismo di suspending & resuming nella nostra piccola app di prova non esiste, perchè non è stato ancora implementato. Ma è stato importante capire, secondo me, cosa succede by default, e cosa invece dobbiamo implementare.

Andiamo avanti.

Suspending, salvataggio delle informazioni

Diamo un’occhiata al costruttore della classe App, contenuto nel file App.xaml.cs. Esso sottoscrive l’evento Suspending:

this.Suspending += OnSuspending;

 

L’handler di questo evento fa una cosa semplice:

private async void OnSuspending(object sender, SuspendingEventArgs e)
{
    var deferral = e.SuspendingOperation.GetDeferral();
    await this.SaveCurrentState();
    deferral.Complete();
}

 

La parte più interessante è il this.SaveCurrentState(), che fa una cosa di cui magari parleremo in un altro post. Questo metodo viene eseguito ogni qualvolta il sistema operativo manda in sospensione la nostra app: lo scopo quindi è quello di salvare lo stato, con tutto ciò che comporta. Caselle di testo, contenuto di ListBox, viewmodel, etc. etc.: tutto ciò che serve per dare l’illusione che l’app non si sia mai chiusa, insomma. Ed il tutto deve avvenire – se ben ricordo – nell’arco di 5 secondi: trascorso questo tempo, Windows killa la nostra app e tutto è perduto.

Resuming, recupero delle informazioni

Ovviamente deve esistere anche l’operazione inversa. Quando l’app viene lanciata potrebbe essere necessario recuperare le informazioni e riadattare la view dell’utente secondo quanto salvato. Diamo un’occhiata a:

protected async override void OnLaunched(LaunchActivatedEventArgs args) { }

 

Ad un certo punto troverete una riga di codice simile alla seguente:

if (args.PreviousExecutionState == ApplicationExecutionState.Terminated)
{
    await this.RestoreLastState(rootFrame);
}

 

La proprietà PreviousExecutionState ci dice la modalità con cui è stata chiusa l’app: nel caso fosse Terminated (ovvero chiusa dal sistema operativo) allora dobbiamo recuperare lo stato. Negli altri casi (ClosedByUser, NotRunning, Running, Suspended) no. Nel mio caso ho implementato un metodo RestoreLastState che fa tutto il lavoro.

Conclusioni

Quello che mi ha confuso per un po’ di tempo è il fatto che se usate un altro template per il Windows Store (Grid App, Split App, etc.) vi viene già fornita una classe SuspensionManager che fa buona parte del lavoro, con il fatto però che non ho mai capito cosa facesse il sistema operativo nativamente, e quanto invece facesse l’app e la parte di codice già implementata per noi. Per questo ho preferito partire da una Blank App e vedere cosa accadesse. Magari in un altro post parlerò di ciò che fanno i metodi SaveCurrentState() e RestoreLastState(). In breve, vanno a recuperare la view corrente e tramite un’interfaccia ISuspendInfo chiedono alla view le informazioni che devono essere salvate o recuperate.

Send to Kindle
.NET World

Utilizzare servizi WCF con callback al chiamante da app Windows 8

Introduzione
Utilizzare un servizio WCF mi è diventato così istintivo in tutte le cose che faccio che francamente non riesco più a farne a meno. Non sto qui ad elencare i vantaggi e a discutere le varie tecniche, perchè prima di tutto non ne ho le competenze (rischierei di usare termini non corretti), per cui vado diretto al succo di questo post.

Generalmente, dal punto di vista client (quindi un’applicazione WPF, un’app Windows Forms o Windows Phone, un’applicazione ASP.NET MVC, etc. etc.) un servizio WCF è un servizio al quale forniamo dati, compie elaborazioni e restituisce qualcosa. Questo avviene con chiamate a metodi esposte dal servizio tramite un contratto (un’interfaccia IQualcosa), grazie al quale noi sappiamo come comunicare, cosa passare e cosa ci aspettiamo come valori di ritorno. Questo tipo di approccio non presenta nessun tipo di problema (in realtà ce ne sono una valanga, sicurezza in primis, ma sorvoliamo): una volta che il servizio è stato esposto via http, è sufficiente aggiungerlo al nostro progetto corrente tramite il comando “Add Service Reference”. Sul nostro client viene creata una classe proxy da utilizzare per effettuare le chiamate.

Quello che succede in uno scenario di questo tipo è che il client, indipendentemente dal tipo, invoca il servizio WCF. Quest’ultimo lavora un po’ e poi restituisce i dati al client. Semplice, almeno concettualmente.

Quello su cui ho lavorato per una giornata intera è invece lo scenario seguente:

  • applicazione Windows 8 Metro-style, scritta in C#
  • servizio WCF in ascolto su http://localhost:1976
  • il servizio WCF non solo espone dei metodi che vengono invocati dall’applicazione, ma è in grado – tramite callback ed un canale di comunicazione duplex, di invocare un metodo definito sul client

Il servizio WCF, quindi, ha diverse particolarità:

  • si deve memorizzare da qualche parte il “chiamante”
  • deve definire non solo un contratto col quale dialogare con il client, ma anche un contratto di callback. Quest’ultimo definisce il/i metodi da invocare sul client

Arriviamo al sodo, vediamo un po’ di codice.

Codice Server Side
Implementiamo un servizio che prende in input un ExecuteRequest e restituisce, prima o poi, un ExecuteResponse.

[DataContract]
public class ExecuteRequest
{
    [DataMember]
    public int Value { get; set; }
}

[DataContract]
public class ExecuteResponse
{
    [DataMember]
    public int Value { get; set; }
}

 

Ho scritto “prima o poi” perchè supponiamo che l’esecuzione server-side sia piuttosto lunga; quindi, NON facciamo un metodo simile a questo:

[ServiceContract]
public interface IExecuteService
{
    [OperationContract]
    ExecuteResponse Execute(ExecuteRequest value);
}

 

bensì facciamo come segue:

[ServiceContract(CallbackContract = typeof(IExecuteServiceCallback))]
public interface IExecuteService
{
    [OperationContract]
    void Execute(ExecuteRequest value);
}

 

Invochiamo il metodo Execute passandogli un ExecuteRequest, che restituisce void. Il servizio WCF lavora, lavora, lavora, e prima o poi chiamerà la callback per informarci del risultato. La funzione di callback è indicato all’interno dell’attributo ServiceContract, ed è IExecuteServiceCallback.

public interface IExecuteServiceCallback
{
    [OperationContract(IsOneWay = true)]
    void ExecuteTerminated(ExecuteResponse response);
}

 

L’implementazione del servizio è piuttosto semplice:

[ServiceBehavior(InstanceContextMode = InstanceContextMode.PerCall)]
public class ExecuteService : IExecuteService
{
    private ExecuteRequest Request;
    private ExecuteResponse Response = new ExecuteResponse();
    private IExecuteServiceCallback Callback;

    public void Execute(ExecuteRequest value)
    {
        this.Callback = OperationContext.Current.GetCallbackChannel<IExecuteServiceCallback>();
        this.Request = value;

        Timer timer = new Timer(10000);
        timer.Elapsed += (s, e) =>
            {
                timer.Stop();

                this.Response.Value = this.Request.Value * 3;
                this.Callback.ExecuteTerminated(this.Response);
            };
        timer.Start();
    }
}

 

Quando il cliente invoca il metodo Execute esposto dal servizio, accade che:

  • il servizio si memorizza chi è il chiamante e predispone la callback
  • al client restituisce un void

A solo scopo di test, parte un timer che fa aspettare il client 10 secondi, al termine dei quali viene invocata la callback sul client con il risultato dell’operazione. Praticamente, ciò che accade è che il client invoca il metodo passando un numero intero, ed il servizio restituisce il doppio di tale numero. Ripeto: è solo a scopo di test, si presuppone che un processore moderno si metta un po’ meno a calcolare il doppio di un numero!

Sorriso

Un pezzetto di web.config

C’è qualche accorgimento da apportare anche nel web.config. L’endpoint deve essere di tipo netHttpBinding. Come recita MSDN all’indirizzo http://msdn.microsoft.com/en-us/library/hh674273.aspx, infatti:

NetHttpBinding is a binding designed for consuming HTTP or WebSocket services and uses binary encoding by default. NetHttpBinding will detect whether it is used with a request-reply contract or duplex contract and change its behavior to match – it will use HTTP for request-reply contracts and WebSockets for duplex contracts.

Riporto qui sotto la parte più significativa del web.config:

<system.serviceModel>
    <services>
      <service name="WcfCallbackSample.ExecuteService"
            behaviorConfiguration="bev1">
        <host>
          <baseAddresses>
            <add baseAddress="http://localhost:1000/Service" />
          </baseAddresses>
        </host>
        <endpoint name="duplexendpoint" address=""
            binding="netHttpBinding"
            contract="WcfCallbackSample.IExecuteService" />
        <endpoint name="MetaDataTcpEndpoint" address="mex"
        binding="mexHttpBinding" contract="IMetadataExchange" />
      </service>
    </services>
    <behaviors>
      <serviceBehaviors>
        <behavior name="bev1">
          <serviceMetadata httpGetEnabled="true" />
          <serviceDebug includeExceptionDetailInFaults="True" />
        </behavior>
      </serviceBehaviors>
    </behaviors>
    <protocolMapping>
      <add binding="basicHttpsBinding" scheme="https" />
    </protocolMapping>
    <serviceHostingEnvironment aspNetCompatibilityEnabled="true"
    multipleSiteBindingsEnabled="true" />
</system.serviceModel>

 

Ovviamente alcuni valori, attributi e nodi XML dipendono dal vostro servizio.

Client Side con un’applicazione Metro per Windows 8

Ok, abbiamo quasi finito. Adesso arriva il bello, ovvero usare il nostro servizio dal client. Dopo averlo aggiunto nei service reference, ci verrebbe istintivo utilizzare la classe client proxy che ci è stata auto-generata. No. Falso. Nada. Fermi tutti.

Innanzitutto con Visual Studio 2012 creiamo una nuova Blank app, sotto la categoria Windows Store.

Dentro troveremo un’unica Page, nel cui costruttore metteremo:

EndpointAddress address = new EndpointAddress("http://localhost:1924/ExecuteService.svc");
var binding = new NetHttpBinding();
var cf = new DuplexChannelFactory<IExecuteService>(new InstanceContext(this), binding, address);
service = cf.CreateChannel();

 

L’oggetto service, definito come private della Page, è di tipo IExecuteService, ed è grazie a questo che potremo utilizzare il nostro servizio.

A questo punto aggiungiamo un Button nello Xaml, gestiamo l’evento Click, e nel code-behind inseriamo:

service.ExecuteAsync(new ExecuteRequest() { Value = 4 });

 

Proviamo ad eseguire l’app: è Metro-style, per cui come sappiamo che gira in full-screen. Clicchiamo sul Button. Non accade nulla. Ed è perfettamente normale, pensiamoci bene. La chiamata a Execute (che avviene in async) restituisce void. Cosa manca? Non abbiamo la callback, ovvero quel meccanismo che il server adotta per avvisarci quando lui ha terminato il calcolo. Morale: la Page deve implementare l’interfaccia di callback.

public sealed partial class MainPage : Page, IExecuteServiceCallback
{
    public async void ExecuteTerminated(ExecuteResponse response)
    {
        MessageDialog dialog = new MessageDialog(response.Value.ToString());
        await dialog.ShowAsync();
    }
}

 

Il timer, istanziato ed in esecuzione lato server, invoca la callback chiamando il metodo ExecuteTerminated sul client, e quindi apparirà la nostra MessageDialog con il risultato dell’operazione.

In ottica Model-View-ViewModel

In ottica MVVM, ovviamente, non sarà direttamente la Page ad implementare l’interfaccia IExecuteServiceCallback, ma la classe View Model che governa quella view. Il View Model gestisce la callback, lavorerà sulle proprie proprietà pubbliche per aggiornare la view.

Send to Kindle
.NET World

Creare MediaElement da codice con WinRT

Una delle piacevoli novità che ho visto introdotte con WinRT è la possibilità di definire e poter utilizzare oggetti di tipo MediaElement direttamente in codice WinRT, senza quindi passare dalla loro istanziazione in pagine Xaml. Questo comporta due vantaggi concreti:

– evita, come già detto, di dover definire tanti MediaElement nello Xaml, ciascuno con il suo nome. Dico “tanti” perchè se in un pagina dovevate far sentire più suoni (contemporanei o no) dovevate per forza mettere più MediaElement (a meno di non usarne uno solo switchando da un source all’altro)
– miglior utilizzo del MVVM, conseguenza del punto precedente. Nel ViewModel associato ad una pagina posso mandare in play un suono senza aver a che fare con la view corrente

Detto questo, mi sono potuto fare una serie di classi helper:

public static class MediaElementGenerator
{
    public async static Task<MediaElement> Get(string source)
    {
        MediaElement result = new MediaElement();
        var package = Windows.ApplicationModel.Package.Current;
        var installedLocation = package.InstalledLocation;
        var storageFile = await installedLocation.GetFileAsync(source);
        
        if (storageFile != null)
        {
            var stream = await storageFile.OpenAsync(Windows.Storage.FileAccessMode.Read);
            result.SetSource(stream, storageFile.ContentType);
        }

        return result;
    }
}

 

Questa riportata qui sopra è una classe statica che lavora in modo asincrono (async/await) e che restituisce un MediaElement associato ad un source specifico. Il source ovviamente deve essere un qualsiasi Uri, che punta ad un file wav/mp3 incluso come “Content” all’interno della nostra solution.

All’interno dei miei ViewModel utilizzo ciò che mi viene esposto dalla classe ApplicationContext:

private async Task Play(string source)
{
    if (ApplicationContext.Instance.Sound)
    {
        if (!this.MediaElements.ContainsKey(source))
        {
            this.MediaElements.Add(source, await MediaElementGenerator.Get(source));
        }

        this.MediaElements[source].Play();
    }
}

public async Task PlayClickButton()
{
    await this.Play("Media\ClickButton.mp3");
}

public async Task PlayReady()
{
    await this.Play("Media\Ready.mp3");
}

 

Gli ultimi due metodi pubblici (PlayClickButton e PlayReady) si appoggiano su Play che invece è privato. Notare che nel metodo Play controllo anche che il suono all’interno dell’applicazione sia attivo: ricordiamoci infatti che è buona norma permettere all’utente di attivare/disattivare i suoni nelle nostre app Windows 8, tramite il pannellino dei settings che va gestito correttamente. Ma questo è forse un tema da affrontare in un altro post.

Send to Kindle
.NET World

Ottenere la versione di un Assembly con WinRT

Con le applicazioni Windows, compreso Silverlight, che quindi comprende a sua volta anche il mondo Windows Phone, potevamo scrivere il codice seguente:

string version = System.Reflection.Assembly.GetExecutingAssembly().FullName.Split(',')[1];
string[] fullversion = version.Split('=')[1].Split('.');
return fullversion[0] + "." + fullversion[1];

 

Questo mi permette di estrarre dal FullName di un Assembly il numero di versione dell’assembly stesso, molto comodo per essere visualizzato su una Page di Silverlight.

Il codice da scrivere con WinRT, e quindi con Windows 8, è leggermente diverso.

string version = typeof(<qualsiasi_classe_usata_nel_codice>).GetTypeInfo().Assembly.FullName.Split(',')[1];

 

La differenza è tutta nella prima riga di codice. In pratica si tratta di avere un’istanza di Type relativa ad una qualsiasi classe del proprio codice, chiamare il metodo GetTypeInfo(), per arrivare all’Assembly, etc. etc. Notare che GetTypeInfo() è un’extension method, per cui bisogna includere il namespace System.Reflection.

Tutto qua, il resto del codice è analogo al precedente. Con qualche split si ottiene il numero di versione dell’assembly.

Send to Kindle