Technology Experience

.NET World

Programmazione, libri, snippet di codice, articoli tecnici

.NET World

[UWP] Concetti importanti delle AppService

Un’app service è quel meccanismo di UWP che permette ad un’app di invocarne un’altra, di potergli passare determinati valori, per ottenere un risultato di qualche tipo. C’è quindi un legame tra “app client” ed “app service”, anche se tecnicamente si tratta di due app UWP che risiedono sullo stesso PC. Per poter dialogare, l’app client deve conoscere un po’ di informazioni:

  • L’AppServiceName
  • Il PackageFamilyName
  • Di quali informazioni l’AppService necessita per poter svolgere il suo lavoro. Tra app client ed app service viaggia un’istanza di ValueSet, che può essere tranquillamente visto come un Dictionary<string, object>.

L’AppService deve essere implementato tramite un BackgroundTask, il quale però non ha bisogno di essere registrato come accadrebbe con un BackgroundTask tradizionale. Quindi, per riassumere:

  • Abbiamo un’app UWP contenuta in una solution (l’AppService deve comunque sia essere hostato all’interno di un’app UWP tradizionale)
  • All’interno di questa solution aggiungiamo un progetto di tipo “Windows Runtime Component”
  • All’interno di questo progetto aggiungiamo una classe che implementa l’interfaccia IBackgroundTask, esattamente come un normalissimo BackgroundTask

Nel metodo Run previsto dall’interfaccia IBackgroundTask dobbiamo fare quanto segue:

  • Dall’istanza IBackgroundTaskInstance che arriva in ingresso al metodo, dobbiamo sfruttare la proprietà TriggerDetails e farne il casting verso AppServiceTriggerDetails
  • Sottoscriviamo l’evento RequestReceveid esposto dall’oggetto AppServiceConnection (ovvero, vedere la proprietà AppServiceConnection)
  • All’interno dell’evento RequestReceveid, dobbiamo prelevare i valori che il client ci ha eventualmente passato. Questa cosa la si ottiene con:
    var message = args.Request.Message;
    int number = (int)message[“param1”];
    string strValue = message[“param2”].ToString();
  • All’interno dell’evento RequestReceveid, prima o poi dovremo restituire qualcosa al client. Questo si ottiene con la seguente riga:
  • ValueSet returnValue = new ValueSet();
    returnValue["ReturnValue"] = result;
    await args.Request.SendResponseAsync(returnValue);

    Come si vede, “returnValue” è un’istanza di ValueSet al cui interno abbiamo inserito una o più chiavi con il corrispondente valore.

Questo delegate verrà invocato quando l’app client invocherà l’AppService. A proposito di client, come si attiva il dialogo tra client ed il nostro AppService? Il legame è molto loose coupled.

service = new AppServiceConnection();
service.AppServiceName = "randomNumberService";
service.PackageFamilyName = "bcbc4d41-504f-4d99-8a93-43f5b9d4c404_xe5ny6wrzrhn8";

var status = await this.service.OpenAsync();

if (status != AppServiceConnectionStatus.Success)
{
    return;
}

La proprietà AppServiceName deve coincidere con quanto stabilito nel file di manifest dell’app UWP che hosta l’AppService. Idem per il PackageFamilyName. La chiamata al metodo OpenAsync() della classe AppServiceConnection ci restituisce un valore tratto dall’enum AppServiceConnectionStatus. Se qualcosa è andato storto, allora interrompiamo immediatamente l’esecuzione.

Altrimenti:

var message = new ValueSet();
message.Add("Operation", "Random");
message.Add("NumberA", 1);
message.Add("NumberB", 100);
AppServiceResponse response = await this.service.SendMessageAsync(message);

if (response.Status == AppServiceResponseStatus.Success)
{
    int result = (int)response.Message["ReturnValue"];
}

Nel frammento di codice qui sopra, preparo un oggetto ValueSet, con alcune coppie di chiave/valore, ed invoco l’AppService. Notare che la chiamata a SendMessageAsync passa all’AppService il nostro ValueSet. Otteniamo un valore di risposta; se è Success possiamo prelevare il valore di ritorno.

Notare che all’interno di ValueSet possiamo inserire oggetti serializzabili: numeri, stringhe ma anche ovviamente classi complesse.

L’AppService va dichiarato nella sezione “Declarations” del file di manifest dell’app UWP che hosta il servizio. E’ necessario quantomeno specificare il Name e l’entrypoint. Quest’ultimo, come accade con i BackgroundTask tradizionali, deve contenere il nome della classe (compreso di namespace) che implementa l’AppService stesso. Ricordo ancora una volta che questo BackgroundTask non va registrato come accadrebbe con un BackgroundTask tradizionale.

Send to Kindle
.NET World

[UWP] Utilizzo di ThreadPoolTimer

La classe ThreadPoolTimer ci fornisce due metodi statici che creano e danno lo Start a due tipi differenti di Timer:

  • CreateTimer: crea un’istanza di ThreadPoolTimer che esegue un metodo dopo X secondi. Poi il timer si ferma.
  • CreatePeriodicTimer: crea un’istanza di ThreadPoolTimer che esegue un metodo ogni X secondi (timer periodico)

In entrambi i casi il metodo da eseguire è specificato tramite il delegate TimerElapsedHandler.

In entrambi i casi il secondo parametro è un’istanza di TimeSpan. Nel primo caso rappresenta dopo quanto tempo il metodo deve essere eseguito. Nel secondo caso rappresenta l’intervallo di tempo che scatena periodicamente l’esecuzione del timer.

Entrambi i metodi prevedono un terzo parametro, un delegate TimerDestroyedHandler, che è il metodo che viene eseguito quando il timer termina la propria esecuzione.

Nel caso di CreateTimer, questo metodo viene eseguito in due momenti:

  • quando il timer raggiunge il TimeSpan: prima viene eseguito il metodo TimerElapsedHandler, e poi TimerDestroyedHandler
  • quando il timer viene (eventualmente) annullato dall’utente, ad esempio cliccando su un bottone

Nel caso di CreatePeriodicTimer, il metodo TimerDestroyedHandler viene eseguito solo quando l’utente eventualmente annulla e ferma il timer. Trattandosi di un timer periodico, teoricamente potrebbe proseguire all’infinito.

Entrambi i metodi restituiscono un’istanza di ThreadPoolTimer, istanza che dobbiamo salvarci da qualche parte, ad esempio per poterne invocare il metodo Cancel(). L’oggetto ThreadPoolTimer, oltre a questo metodo, espone anche tre property read-only:

  • Delay, che è l’oggetto TimeSpan utilizzato dal Timer
  • Period, che è l’intervallo di tempo previsto dal Timer periodico

Nel caso di CreateTimer, Delay contiene l’intervallo di tempo, mentre Period è zero.

Nel caso di CreatePeriodTimer, le due property equivalgono. Un timer periodico attende X secondi prima di eseguire TimerElapsedHandler, e poi ripete ogni X secondi. Delay e Period coincidono.

All’interno di TimerElapsedHandler o TimerDestroyedHandler non abbiamo ovviamente accesso al thread della UI, per cui dobbiamo passare dal Dispatcher della Window e chiamarne il metodo RunAsync se dobbiamo aggiornare qualcosa sull’interfaccia utente dell’applicazione.

Send to Kindle
.NET World

[UWP] Accesso alla SD da un’app Windows 10

In giro per il web troverete tonnellate di documentazione relativa all’accesso alla SD da un’applicazione UWP. Ci sono certe cose che non mi entrano in testa, per cui mi appunto qui un po’ di cosucce interessanti.

  • La chiamata a:
    var folder = KnownFolders.RemovableDevices;
    è la porta di accesso per ottenere un riferimento alla SD
  • E’ necessario attivare la capability “Removable Storage”, altrimenti la riga sui sopra vi spara una bella System.UnauthorizedAccessException
  • Se tutto è andato bene, otterrete un oggetto simile al seguente:image
  • Come si può notare, non è la vera root della vostra SD. Per ottenere la root dell’SD dovete nuovamente richiedere l’elenco dei folder:
    var root = await folder.GetFoldersAsync().FirstOrDefault();
  • L’oggetto root potrebbe valere null nel caso in cui non sia presente alcuna SD nel dispositivo Windows 10. L’oggetto folder invece è sempre valorizzato, indipendentemente dal fatto che la SD sia presente oppure no
  • Su un PC Desktop, i dischi USB ed un eventuale lettore CD/DVD vengono visti come “Removable Storage”. In questo caso, la proprietà Name di StorageFolder vi dà la lettera del drive (esempio: “E:”).
  • Da notare che potreste avere più root SD, semplicemente per il fatto che avete più dischi removibili presenti nel sistema, e quindi avete una root per ciascun drive.
  • E’ necessario andare nel file di manifest, nella sezione “Declarations”, ed aggiungere tante voci “File Type Associations”, una per ciascun tipo di file che volete leggere/scrivere dalla scheda SD. Volete leggere i file .txt? Dovete specificarlo! Volete leggere i file .pdf? Idem come sopra!
  • Se non indicate alcun “File Type Associations” nel file di manifest, qualsiasi tentativo di lettura/scrittura vi spara una System.UnauthorizedAccessException (esempio: GetFileAsync, GetFilesAsync, CreateFileAsync)
  • Se fra le “File Type Associations” indicate i file JPG (è solo un esempio), la chiamata a GetFilesAsync() vi restituirà solamente quel particolare tipo di file, anche se sul disco ci sono altri tipi di file
  • Le operazioni sui folder (creazione, lettura, etc.) non richiedono ovviamente alcuna voce definita fra le “File Type Associations”
  • Parliamo un attimo di GetItemAsync ed affini. Questo metodo esposto da StorageFolder permette di ottenere un’istanza di IStorageItem che punta ad un file o cartella.
    Se indicate il nome di una cartella, e questa cartella esiste, tutto bene.
    Se la cartella non esiste, ottenete un’eccezione.
    Se indicate il nome di un file, e questo file esiste, deve essere dichiarato nella sezione “File Type Associations” del file di manifest.
    Se il file non esiste, ottenete un’eccezione.
  • Meglio usare TryGetItemAsync, che restituisce null nel caso in cui il file o la cartella non esiste
  • Su uno smartphone Windows 10, è possibile ottenere un ID univoco della SD:
  • var id = await sf.Properties.RetrievePropertiesAsync(
        new List<string>()
        { “WindowsPhone.ExternalStorageId” });

Direi che è tutto!

Send to Kindle
.NET World

Focus Day, tutto ciò che non vi ho detto

60 minuti erano il minimo indispensabile per raccontarvi tutto ciò che avevo da dire. Ma i blog esistono anche per questo, e quindi aprofitto del mio per integrare un po’ di contenuti.

Mi rendo conto che i contenuti di questo post sono molto a rischio, per un semplice motivo. Ognuno di noi sviluppatori vede, credo, “la sua fetta di mondo”. Uno sviluppatore Web crede che il mondo sia governato solo ed esclusivamente dal Web; crede che in giro ci siano clienti che gli chiederanno solo ed esclusivamente portali, siti ed applicazioni Web. Assolutamente falso. Ma sbaglia anche lo sviluppatore desktop che crede che in giro ci sia solo ed esclusivamente desktop. In realtà, ci sono aziende che chiedono applicazioni a tutto tondo, capaci di esistere sul desktop, sul Web, sul cloud, sul mobile. Il Web è un grande collante, è una serie di fili che collega un dispositivo all’altro. Di sicuro non esiste più un’applicazione disconnessa: un’applicazione moderna in ogni caso sincronizza i dati, si basa sul cloud, salva su OneDrive, parla con servizi esterni e remoti, e via dicendo.

Quindi, di volta in volta dobbiamo scegliere le tecnologie giuste.

Arriviamo al dunque, in merito al contenuto della mia sessione al Focus Day.

Ma se Windows Phone muore, che senso ha UWP?
UWP non è solo telefono, anzi. Il destino di Windows Phone non c’entra nulla con il destino di Universal Windows Platform. Windows Phone è destinato a morire, per il semplice fatto che Windows Phone 8 o 8.1 è destinato a morire. Gli smartphone che potranno farlo migreranno verso Windows 10. E su Windows 10 c’è e e ci sarà UWP per un bel pezzo di tempo.

Windows 10 ha una piccola fetta di mercato, che senso ha imparare/sviluppare UWP?
Non è vero. Windows 10 non ha una piccola fetta di mercato, anzi tutt’altro. Lo è se considerate solo ed esclusivamente il mondo degli smartphone. E quindi dipende dal mercato che volete o dovete seguire. Ma Windows 10 attualmente è installato su 300.000.000 milioni di PC in giro per il mondo (fonte), e cresce ogni giorno. Se dovete sviluppare un client per Windows, UWP può essere una scelta.

Ma se Microsoft dismette gli smartphone Windows 10, che senso ha UWP?
Beh, ha perfettamente senso. Se come me è circa 20 anni che sviluppate per il desktop, UWP può essere la scelta ideale. Perchè dico “può”? Perchè ad oggi UWP richiede Windows 10, e può essere benissimo che i vostri clienti – per miliardi di motivi diversi – non possano e non vogliano migrare. Se cadete in questa situazione, scegliete ovviamente WPF. Per tornare alla domande, se anche gli smartphone Windows 10 dovessero scomparire, UWP continuerebbe ad esistere, perchè UWP significa Windows 10, e Windows 10 non è solo telefoni, ma molto di più.

Ma Microsoft non potrebbe inventarsi un porting di UWP verso Android ed iOS?
Sarebbe imho una grande follia. UWP è un framework strettamente legato a feature del sistema operativo Windows. Comprende classi legate a XAML ed al suo essere vettoriale, Cortana, Live Tile, drag’n’drop, push notification, OneDrive, utilizzo della penna o della tastiera, Azure. UWP è Windows, punto. Deve essere la scelta se il sistema che dovete raggiungere è esclusivamente Windows.

Non avrebbe davvero senso affrontare un porting UWP verso altri sistemi operativi. Se il vostro obiettivo è quello di scrivere codice una volta e di eseguirlo ovunque (write once, run everywhere), Xamarin Forms DEVE essere la vostra scelta.

UWP è in diretta concorrenza con Android?
No. Android è un sistema operativo mobile per smartphone/tablet, UWP è anche questo e molto di più. UWP è solo ed esclusivamente per Windows, e raggiunge smartphone, ultrabook, desktop, IoT, HoloLens, magari in futuro le XBOX.

UWP è in diretta concorrenza con iOS?
No. iOS è il sistema operativo di Apple, che raggiunge iPhone ed iPad. Valgono le stessa considerazioni fatte per Android qui sopra.

Dovrei scegliere UWP o Xamarin?
Bella domanda. UWP è legato esclusivamente al mondo Windows. Xamarin è un framework per lo sviluppo di applicazioni mobile cross-platform (Android, iOS, Windows Phone, ed anche UWP). Scegliete UWP se il vostro cliente vi chiede esclusivamente un’applicazione per Windows desktop/mobile, da distribuire tramite gli Store (pubblico o quello For Business); consiglio spassionato: scrivete bene il vostro codice (mvvm a go-go & PCL, tanto per citare due keyword), così siete pronti a reagire al cambiamento e potete scrivere codice non totalmente relegato alla piattaforma UWP. UWP secondo me è un’ottima scelta per applicazioni business simil-desktop, nativamente mobile, e se il sistema operativo è Windows 10. Potete realizzare app che una volta avreste implementato con WPF, ma con una miriade di feature in più, come ho accennato nella mia sessione del Focus Day del 27 maggio scorso: Cortana, Live Tile, notifiche push, impostazioni in roaming, distribuzione tramite gli Store (che vi evitano deploy via ClickOnce o via file zip da scaricare), etc.

Scegliete Xamarin se dovete fare un’app consumer multi-piattaforma, quindi se dovete raggiungere, oltre alla piattaforma Windows, anche Android iOS.

Send to Kindle
.NET World

Windows Store Api su GitHub

Lo scorso mese di marzo Microsoft ha reso disponibili delle API tramite le quali noi sviluppatori possiamo accedere alle informazioni conservate all’interno dei nostri account Dev Center. Le informazioni sono visibili su questa pagina del blog ufficiale di Microsoft. Con la versione attuale di queste API possiamo quindi facilmente avere accesso a:

  • App Acquisitions (i download)
  • IAP Acquisistions (gli acquisti in-app effettuati dagli utenti all’interno delle nostre app)
  • Ratings (il numero di stelline)
  • Reviews (le recensioni scritte manualmente dagli utenti)
  • App Health (ovver le app failure, le eccezioni scatenate all’interno delle nostre app)

A patto di litigare con Active Directory di Azure ed impostare il nostro Dev Center di conseguenza (guardate il link di prima per tutti i dettagli del caso), e successivamente di reperire Tenant Id, Client Id e Client Secret, possiamo effettuare chiamate http per ottenere oggetti JSON da deserializzare. Possiamo ordinare, applicare filtrare in base al tempo, etc. etc. Ed otteniamo alla fine oggetti che contengono tutte le informazioni che abbiamo richiesto in base alla API invocata.

Se si può servire, sappiate questo: come Brain-Sys abbiamo pubblicato un progetto su GitHub dedicato a questo tema. Il progetto contiene un po’ di codice open-source che dovrebbe semplificarvi un po’ il lavoro. In pratica non occorre più lavorare a basso livello con HttpClient, autenticarsi ed effettuare chiamate via http, ma c’è una libreria di classi (partendo dalla WindowsStoreApiConnector che è il cuore di tutto) molto più semplice ed efficace da utilizzare.

Oltre a tutto questo, da questa mattina trovate anche un bel pacchetto su NuGet pronto per essere utilizzato. E’ una libreria portable, per cui è utilizzabile da console, WPF, qualsiasi applicazione ASP.NET MVC, etc. etc.

Feedback sono ben accetti!

Send to Kindle
.NET World

Azure Service Bus Relay, ovvero come esporre un WCF on-premise sul cloud

L’Azure Service Bus Relay (d’ora in poi ASBR) è il servizio messo a disposizione da Azure che ho scoperto più di recente, grazie al mio capo Gabriele Gaggi che me ne ha parlato chiedendomi di studiarlo & provarlo, perchè ci sarebbe stato molto utile in uno scenario che dobbiamo risolvere. Effettivamente l’ASBR è molto molto interessante, e risolve un compito piuttosto ostico, con estrema eleganza e soprattutto relativamente poco lavoro per noi sviluppatori.

A che serve? Semplificando di molto, l’ASBR permette di esporre un servizio Windows Communication Foundation (WCF), che gira on-premise in una rete LAN aziendale, direttamente sul cloud pubblico. Senza bisogno di impazzire con l’apertura di porte sul firewall, di stabilire regole di routing, o più in generale di andare ad apportare cambiamenti pericolosi nell’infrastruttura di rete aziendale.

La tipica domanda che mi viene posta è la seguente: ma se ti serve un WCF sul cloud, perchè non ne fai direttamente il deploy su Azure? Se mi fate questa domanda è perchè siete troppo web-oriented-only, e questo è decisamente male. Spesso e volentieri sono costretto ad avere un servizio WCF che gira on-premise, per diverse ragioni: per motivi di policy, e soprattutto perchè il più delle volte questo servizio deve dialogare con una periferica via USB. Di conseguenza, sono obbligato ad avere un PC server che comunica con la periferica tramite WCF (hostato in un Windows Service, per esempio), che assolve a tutta una serie di compiti (logging, monitoraggio e controllo della periferica, reportistica, etc. etc.); nel mondo moderno, però, il web non è da trascurare, e quindi questo WCF deve anche essere reso disponibile sul cloud.
ASBR serve proprio a questo, con il minor sforzo possibile.

Il tutto è contenuto in questo articolo, che ho seguito passo-passo per ottenere il risultato finale.

1) Creare un service bus su Azure
Il primo step da completare è quello di creare un service bus su Azure. Nel momento in cui vi scrivo, questa operazione non è ancora possibile con l’Azure Portal nuovo, ma dovete accedere a quello classico, disponibile all’indirizzo https://manage.windowsazure.com.

image

Nel mio caso ne ho creato uno chiamato exposedwcf. Da questo servizio bus dovete recuperare il nome (che in realtà avete scelto voi) e la primary key (chiave primaria).

2) Creare il server WCF
Ovviamente, dovete innanzitutto avere un servizio WCF che gira sul PC, allo scopo di pubblicarlo sul cloud. Come esempio, potete vedere quello che ho pubblicato sul GitHub di Brain-Sys all’indirizzo https://github.com/Brain-Sys/ASBR_Server.

Si tratta di un banale WCF hostato in una Console Application.
Il servizio WCF prevede la seguente interfaccia:

namespace ASBR_Server
{
    using System.ServiceModel;

    [ServiceContract(Namespace = "urn:ps")]
    interface IProblemSolver
    {
        [OperationContract]
        int AddNumbers(int a, int b);
    }

    interface IProblemSolverChannel :
        IProblemSolver, IClientChannel { }
}

L’interfaccia è IProblemSolver, ed espone un unico metodo AddNumbers che, con molta fantasia, prende due integer e restituisce la loro somma. L’implementazione è estremamente banale.

static void Main(string[] args)
{
    var sh = new ServiceHost(typeof(ProblemSolver));
    sh.Open();
    Console.WriteLine("Server started!!!");
    Console.WriteLine("Press ENTER to close");
    Console.ReadLine();
    sh.Close();
}

Viene create un’istanza di ServiceHost, al quale viene passata in input l’interfaccia del servizio. Poi il servizio parte, con il classico Console.ReadLine() per fare in modo che il servizio rimanga in attesa (ovviamente questo vale per una Console Application).

Da notare che nel file app.config il servizio WCF ha due endpoint. Il primo è associato ad un binding di tipo netTcpBinding, il cui url è net.tcp://localhost:9358/solver, ed è l’indirizzo che viene esposto ovviamente all’interno della LAN sulla rete on-premise. Il secondo endpoint invece utilizza un binding di tipo netTcpRelayBinding, il cui url è sb://exposedwcf.servicebus.windows.net/solver. Notare il protocollo sb://, che indica appunto l’utilizzo del service bus su Azure. Questo è l’url al quale dovranno puntare i client che raggiungeranno il servizio da remoto, e quindi al di fuori della rete aziendale, accedendoci da Azure.

Fate riferimento al repository GitHub indicato prima per tutti i dettagli del caso.

3) Creare il client WCF


L’altra faccia della medaglia è il client. Anch’esso è una banale applicazione di tipo Console.

Fate riferimento al repository https://github.com/Brain-Sys/ASBR_Client su GitHub.

In questo progetto abbiamo ripetuto la definizione dell’interfaccia IProblemSolver vista prima. Il file Program.cs fa una cosa molto molto banale.

static void Main(string[] args)
{
    Random rnd = new Random((int)DateTime.Now.Ticks);
    var cf = new ChannelFactory<IProblemSolverChannel>("solver");
    var ch = cf.CreateChannel();

    for (int i = 0; i < 1000; i++)
    {
        int a = rnd.Next(1, 100);
        int b = rnd.Next(1, 100);
        Console.WriteLine("Richiesta " + (i + 1));
        Console.WriteLine(ch.AddNumbers(a, b));
    }

    Console.ReadKey();
}

Viene aperto un canale di comunicazione verso il servizio WCF (notare che la configurazione del servizio WCF è contenuta nel file App.config, che qui non si vede). Poi viene invocato per 1.000 volte il metodo AddNumbers(a, b), chiedendo al servizio WCF di risolvere la somma e di restituirci il risultato.

Et voilà, il gioco è fatto! Il client si connette al servizio WCF via Azure. Questo significa che a costo zero (ovvero: senza toccare nulla del nostro codice), possiamo prendere un servizio WCF già pronto ed esporlo sul cloud, semplicemente aggiungendo un nuovo endpoint tramite l’ASBR.

Anche in questo caso, date un’occhiata al repository GitHub ASBR_Client indicato prima.

4) Conclusione


E’ semplice fare una prova del nove. Lanciate il servizio WCF server sul vostro PC, che si metterà in attesa di un qualche client che si connetta. Compilate il client, passatelo ad un collega o a qualcuno che sta dall’altra parte del mondo, o comunque qualcuno al di fuori della vostra LAN, e chiedetegli di lanciare il client. Il suo client raggiungerà il servizio WCF via Azure: il suo client effettuerà 1.000 richieste di AddNumbers(), che verranno risolte dal WCF che gira sul vostro PC. La stessa cosa la potete anche fare voi in locale (server & client sullo stesso PC, per capirci), ma ovviamente non avete la percezione che il WCF sia effettivamente esposto sul Web.

Ulteriori informazioni sull’Azure Service Bus sono raggiungibili qui.

Send to Kindle
.NET WorldSoftware

Controlli UWP e DeferLoadStrategy

Questa sera qui sul mio blog linko semplicemente questo articolo che parla di Universal Windows Platform, dei suoi controlli e della possibilità di ritardare il loro caricamento tramite la proprietà DeferLoadStrategy. Quest’ultima sostanzialmente si occupa di evitare di caricare il visual tree di tutti quei panel (ma in generale è un ragionamento che si applica a tutti i controlli UWP) che soddisfano due condizioni:

  • DeferLoadStrategy = “Lazy”
  • Visibility = “Collapsed”

Davvero comodo in ottica UWP, dove sappiamo benissimo che le view devono adattarsi a diverse piattaforme e scenari. Giocando con queste nuove proprietà è possibile ottimizzare il parsing ed il caricamento dello XAML facendo in modo che il visual tree contenga solo i controlli UI previsti.

Me lo ero appuntato su Twitter, ma riportato qui sul mio blog mi risulta più comodo!

Buona lettura & Buon sviluppo!!!

Send to Kindle
.NET World

UWP, MVVM e ListView con multiselezione

Il problema di oggi è quello di avere un’app UWP (Universal Windows Platform), sviluppata seguendo la tecnica del Modem-View-ViewModel, che mostra una ListView con attivata la multiselezione. Ci sono alcuni passaggi che dobbiamo risolvere.

Innanzitutto, l’oggetto ListView espone la proprietà SelectedItem (singolare) ma non una proprietà SelectedItems. Quindi dobbiamo trovare un modo per bypassare il problema. Io ho risolto scrivendo una classe statica SelectionManager, che espone una proprietà List di tipo IList. Da viewmodel posso fare binding su questa dependency property con una proprietà ObservableCollection. All’interno, questa classe sottoscrivere l’evento SelectionChanged della ListView; ogni volta che l’utente seleziona o deseleziona un ListViewItem la lista viene sincronizzata.

public static class SelectionManager
{
public static IList GetList(DependencyObject obj)
{
return (IList)obj.GetValue(ListProperty);
}

public static void SetList(DependencyObject obj, IList value)
{
obj.SetValue(ListProperty, value);
}

public static readonly DependencyProperty ListProperty =
DependencyProperty.RegisterAttached("List",
typeof(IList),
typeof(SelectionManager),
new PropertyMetadata(null,
new PropertyChangedCallback(SetupList)));

private static void SetupList(DependencyObject d,
DependencyPropertyChangedEventArgs e)
{
ListViewBase lst = d as ListViewBase;
lst.SelectionChanged += Lst_SelectionChanged;
}

private static void Lst_SelectionChanged(object sender,
SelectionChangedEventArgs e)
{
ListViewBase element = sender as ListViewBase;
var arrayList = GetList(element);

foreach (object o in e.AddedItems)
{
arrayList.Add(o);
}

foreach (object o in e.RemovedItems)
{
if (arrayList.Contains(o))
{
arrayList.Remove(o);
}
}
}
}

A livello di ViewModel espongo due ObservableCollection pubblici: una funge come ItemsSource per la ListView stessa (grazie alla quale la ListView mostra l’elenco degli Items), l’altra invece è una ObservableCollection che mantiene costantemente aggiornato l’elenco degli Items selezionati. Ecco uno stralcio di XAML per rendere evidente questa cosa.

<ListView
ItemsSource="{Binding Cars}"
SelectionMode="Extended"
hlp:SelectionManager.List="{Binding SelectedCars}" />

La proprietà Cars è una ObservableCollection<Car> che fa da datasource alla ListView, come si fa di solito. La proprietà SelectedCars è un’altra ObservableCollection<Car> dedicata solamente al mantenimento degli items correntemente selezionati.

Un altro approccio che NON ho preferito

Un altro approccio che avrei potuto seguire è quello di passare al mio SelectionManager la stessa ObservableCollection impostata su ItemsSource (nel mio caso qui sopra, quindi, Cars). Con questa logica la mia SelectionManager avrebbe potuto impostarmi una proprietà booleana IsSelected su ciascuna delle istanze di Car che risultano selezionate.

Non ho preferito questa strada per due ragioni:

  1. Così facendo sarai stato costretto ad aggiungere una proprietà bool IsSelected sul mio viewmodel, cosa che non sempre vorrei/potrei fare. D’altro canto, invece, SelectionManager è in grado di lavorare con qualsiasi tipo di classe. Quando un oggetto risulta selezionato sulla UI esso viene aggiunto alla lista in binding; quando viene deselezionato l’oggetto viene rimosso
  2. Avendo una proprietà dedicata alla multiselezione, posso fare immediatamente binding sulla mia UI. Nel mio esempio ho una proprietà SelectedCars pronta all’uso ed aggiornata, quindi è semplicissimo aggiungere al visual tree una TextBlock la cui proprietà Text fa binding con un {Binding Path=SelectedCars.Count} (per visualizzare il numero di elementi selezionati). Con questo secondo approccio, invece, avrei un’unica proprietà Cars, e per ottenere l’elenco degli Items selezionati devo continuamente giocare con LINQ e fare una query per ottenere le Cars selezionate

Se serve, posso aggiungere un Command ed un CommandParameter

Una volta sviluppata la classe SelectionManager con il codice qui sopra, è un gioco da ragazzi aggiungere due nuove dependency property: una di tipo ICommand ed una di tipo object. Lo scopo in questo caso è quello potenzialmente di andare ad eseguire un Command sul mio viewmodel dopo che la selezione è stata gestita (se serve, mica è obbligatorio).

Ecco il codice completo della classe SelectionManager:

public static class SelectionManager
{
public static IList GetList(DependencyObject obj)
{
return (IList)obj.GetValue(ListProperty);
}

public static void SetList(DependencyObject obj, IList value)
{
obj.SetValue(ListProperty, value);
}

public static readonly DependencyProperty ListProperty =
DependencyProperty.RegisterAttached("List",
typeof(IList),
typeof(SelectionManager),
new PropertyMetadata(null,
new PropertyChangedCallback(SetupList)));

public static ICommand GetCommand(DependencyObject obj)
{
return (ICommand)obj.GetValue(CommandProperty);
}

public static void SetCommand(DependencyObject obj, ICommand value)
{
obj.SetValue(CommandProperty, value);
}

public static readonly DependencyProperty CommandProperty =
DependencyProperty.RegisterAttached("Command",
typeof(ICommand),
typeof(SelectionManager),
new PropertyMetadata(null,
new PropertyChangedCallback(SetupList)));

public static object GetCommandParameter(DependencyObject obj)
{
return obj.GetValue(CommandParameterProperty);
}

public static void SetCommandParameter(DependencyObject obj,
object value)
{
obj.SetValue(CommandParameterProperty, value);
}

public static readonly DependencyProperty CommandParameterProperty =
DependencyProperty.RegisterAttached("CommandParameter",
typeof(object),
typeof(SelectionManager),
new PropertyMetadata(null));

private static void SetupList(DependencyObject d,
DependencyPropertyChangedEventArgs e)
{
ListViewBase lst = d as ListViewBase;
lst.SelectionChanged += Lst_SelectionChanged;
}

private static void Lst_SelectionChanged(object sender,
SelectionChangedEventArgs e)
{
ListViewBase element = sender as ListViewBase;
var arrayList = GetList(element);

foreach (object o in e.AddedItems)
{
arrayList.Add(o);
}

foreach (object o in e.RemovedItems)
{
if (arrayList.Contains(o))
{
arrayList.Remove(o);
}
}

var command = GetCommand(element);

if (command != null)
{
var parameter = element.GetValue(CommandParameterProperty);

if (command.CanExecute(parameter))
{
command.Execute(parameter);
}
}
}
}

Dopo che l’evento SelectionChanged è stato scatenato, la proprietà del viewmodel viene sincronizzata con la selezione corrente. Dopodichè, se c’è un Command in binding viene eseguito, passando anche il suo eventuale parametro object.

 
Fine!
Send to Kindle
.NET World

Applicazioni UWP: come gestire correttamente il tasto Back

Le applicazioni Universal Windows Platform sono organizzate attraverso pagine (classe Page), esattamente come accadeva nelle precedenti versioni di Windows 8 ed 8.1. Questo significa che l’app parte mostrando una pagina iniziale, poi l’utente naviga e si sposta secondo un flusso che dipende dall’app stessa. L’utente può ovviamente anche tornare indietro fra le pagine, con diverse modalità dipendentemente dal software e dall’hardware che ha a disposizione. Ad esempio:

  • su uno smartphone l’utente può utilizzare il tasto hardware fisico Back, oppure può toccare il tasto software previsto da Windows (come ad esempio su un Lumia 640 LTE)
  • su un tablet o su un PC Windows 10 il tasto hardware Back non esiste, per cui l’utente può solamente cliccare/toccare il tasto Back software previsto dal sistema operativo. Questo tasto si può trovare in due punti distinti: se la modalità Tablet è spenta, allora il pulsante si trova sulla barra del titolo relativo all’app stessa. Se la modalità Tablet è attiva, allora il pulsante si trova sulla taskbar di Windows
  • Da notare che l’utente può posizionare la taskbar su uno dei quattro lati dello schermo, per cui il tasto Back fisicamente può ritrovarsi ai quattro angoli dello schermo:
    – taskbar inferiore : tasto Back nell’angolo in basso a sinistra
    – taskbar sul lato sinistro : tasto Back nell’angolo in alto a sinistra
    – taskbar superiore : tasto Back nell’angolo in alto a sinistra
    – taskbar sul lato destro : tasto Back nell’angolo in alto a destra
  • L’app può ovviamente anche contenere un pulsante Back previsto dallo sviluppatore ed inserito all’interno della pagina: aspetto, posizionamento e comportamento dipendono ovviamente da chi scrive il codice ed implementa la pagina

Qualche screenshot è d’obbligo per chiarire meglio le cose.

image

Sopra : Pulsante Back sulla barra del titolo in un’app UWP con modalità Tablet spenta.

image

Sopra : Pulsante Back sulla taskbar di Windows agganciata al lato sinistro dello schermo, con modalità Tablet attiva.

image

Sopra : Pulsante Back sulla taskbar di Windows agganciata al lato inferiore dello schermo, con modalità Tablet attiva.

Rendere visibile o invisibile il tasto Back 
Vediamo adesso qualche riga di codice C# per gestire tutti questi scenari.

Innanzitutto, occorre controllare la visibilità del pulsante Back, che di default è e rimane nascosto. Con Windows 10 questo pulsante non fa parte della nostra UI, non è un contenuto inserito nelle nostre pagine tramite XAML, per cui se vogliamo controllarlo dobbiamo passare da classi che consentono l’accesso a questo componente messo a disposizione dal sistema operativo. Ad esempio, se vogliamo rendere visibile il pulsante Back ogni volta che si naviga verso una nuova pagina il codice necessario è il seguente:

SystemNavigationManager.GetForCurrentView()
.AppViewBackButtonVisibility =
((Frame)sender).CanGoBack ?
AppViewBackButtonVisibility.Visible :
AppViewBackButtonVisibility.Collapsed;

Dove posizionare questo blocco di codice? Ecco il codice completo che suggerisco:

protected override void OnLaunched(LaunchActivatedEventArgs e)
{
Frame rootFrame = Window.Current.Content as Frame;

if (rootFrame == null)
{
rootFrame = new Frame();
rootFrame.NavigationFailed += OnNavigationFailed;
rootFrame.Navigated += RootFrame_Navigated;
Window.Current.Content = rootFrame;
}

if (rootFrame.Content == null)
{
rootFrame.Navigate(typeof(MainPage), e.Arguments);
}

Window.Current.Activate();
}

private void RootFrame_Navigated(object sender,
NavigationEventArgs e)
{
SystemNavigationManager.GetForCurrentView()
.AppViewBackButtonVisibility =
((Frame)sender).CanGoBack ?
AppViewBackButtonVisibility.Visible :
AppViewBackButtonVisibility.Collapsed;
}
Nel metodo OnLaunched() definito nella classe App sottoscrivo l’evento Navigated della classe Frame, che si scatena ogni qualvolta viene completata la navigazione verso una nuova pagina. Il bottone Back, di conseguenza, viene reso visibile se la proprietà CanGoBack del Frame è true.
Questo, però, completa solo metà del problema. Il bottone è visibile, ma se viene cliccato non accade nulla: per portare l’utente sulla pagina precedente dobbiamo scrivere ancora qualche riga di codice C#.

 

Intercettare la pressione del tasto Back e spostarsi alla pagina precedente

Capire quando l’utente clicca o tocca il pulsante Back e reagire di conseguenza è semplice:

public SecondPage()
{
this.InitializeComponent();

SystemNavigationManager.GetForCurrentView()
.BackRequested += SecondPage_BackRequested;
}

private void SecondPage_BackRequested(object sender,
BackRequestedEventArgs e)
{
if (this.Frame.CanGoBack)
{
this.Frame.GoBack();
}

e.Handled = true;
}

Si utilizza la classe SystemNavigationManager, si ottiene un riferimento alla view corrente e si sottoscrive l’evento BackRequested. Nell’event handler si effettua la navigazione alla pagina precedente: un buon modo per evitare eccezioni o problemi da questo punto di vista è verificare se sia possibile controllando la proprietà CanGoBack esposta da Frame.

 
Ed in un mondo MVVM?
Quando si sviluppa un’app con il pattern MVVM, le cose sono un po’ diverse, ma non molto. Non scriviamo codice nel code-behind della Page, bensì abbiamo a che fare con una classe ViewModel. Nel costruttore di ciascuna classe ViewModel sottoscriviamo l’evento BackRequested:
 
public AboutViewModel()
{
SystemNavigationManager.GetForCurrentView()
.BackRequested += (s, e) =>
{
e.Handled = true;
base.GoBack();
};
}
Si può evitare di sottoscrivere l’evento nella classe ViewModel relativa alla prima pagina che parte con l’app, perchè da questa pagina non si può tornare indietro da alcuna parte. Nel momento in cui l’utente clicca/tocca il tasto Back, viene scatenato l’evento BackRequested. Nel mio esempio qui sopra, vado a chiamare il metodo GoBack() esposto da tutti i miei ViewModel, che fa semplicemente:
 
protected void GoBack()
{
Frame rootFrame =
Windows.UI.Xaml.Window.Current.Content as Frame;

if (rootFrame.CanGoBack)
{
rootFrame.GoBack();
}
}
 
In questo modo si evita di andare a lavorare nel code-behind: qui semplicemente si recupera il Frame relativo alla vista corrente e se possibile si scatena la navigazione all’indietro.
 
Et voilà, il gioco è fatto!
Send to Kindle
.NET World

Spunti di Adaptive Code con UWP

Quando parliamo di Universal Windows Platform parliamo per diretta conseguenza di Windows 10, e quindi di tutti i possibili hardware capaci di far girare Windows 10, dallo smartphone a tutti gli altri possibili ed immaginabili dispositivi. E’ importante applicare quindi correttamente concetti come Adaptive UI (ovvero un’interfaccia utente capace di adattarsi e di scalare in base alla risoluzione ed allo schermo correnti), ma anche di Adaptive Code, ovvero di codice c# che capisca su quali famiglia di dispositivi ci troviamo e di adattarsi di conseguenza.

Per quale motivo? Ce ne sono tanti, a dir la verità.

Facciamo una cosa semplice. Supponiamo di creare ed istanziare una MessageDialog:

var dialog = new MessageDialog(
string.Format("Aprire il link {0} ?", url),
"Apertura sito Web");
dialog.Commands.Add(
new UICommand { Label = "&Sì", Id = 0 });
dialog.Commands.Add(
new UICommand { Label = "&No", Id = 1 });
dialog.DefaultCommandIndex = 0;
dialog.CancelCommandIndex = 1;
var res = await dialog.ShowAsync();
Notare che ho impostato le label come &Sì e &No, in modo tale che da tastiera sia possibile premere ALT+S e ALT+N per rispettivamente premere Sì oppure no. La tastiera però su smartphone non ce l’abbiamo, quindi questa è una feature che non è supportata. Purtroppo, con la build corrente di Windows 10 for Mobile quelle & commerciali compaiono comunque, con uno sgradevole effetto per l’utente finale. La soluzione è intercettare e capire su quale famiglia di device ci troviamo a run-time, ed adattarci in base allo scenario.
Possiamo capire su quale famiglia di dispositivi Windows 10 ci troviamo con questa linea di codice:
string family = Windows.System.Profile.AnalyticsInfo.VersionInfo.DeviceFamily;

Il valore restituito è una semplice stringa. E quindi possiamo scrivere:

string family = Windows.System.Profile
.AnalyticsInfo.VersionInfo.DeviceFamily;
string yesLabel = "Sì";
string noLabel = "No";

if (family == "Windows.Desktop")
{
yesLabel = "&Sì";
noLabel = "&No";
}

Windows.Desktop è il valore che quella DeviceFamily assume quando il codice gira sotto l’ambiente desktop (quindi PC di varia natura). Il valore diventa Windows.Mobile se il codice sta girando sotto smartphone.

Send to Kindle