Questo articolo è la naturale prosecuzione di un articolo precedente in cui introduciamo lo sviluppo di applicazioni per Windows Phone 7. L'obiettivo stavolta è quello di mostrare alcune API che consentono l'interazione con le feature hardware del nuovo Windows Phone 7. Non è necessario utilizzare un device fisico, anche se, come abbiamo avuto modo di accennare nell'articolo introduttivo, non potremmo provare realmente alcune API se non simulando da codice l'invio dei dati.
Microsoft ha scolpito nella pietra le caratteristiche hardware minime che qualunque device Windows Phone 7 dovrà avere; ogni produttore potrà aggiungere nuovi dispositivi o feature oppure potrà aumentare le "capacità di quelle obbligatorie". Oltre alla caratteristiche fisiche e alle API relative è importante capire cosa offre la piattaforma dal punto di vista delle classi .NET, ovvero quali sono le funzionalità disponibili alle applicazioni.
Le funzionalità messe a disposizione per chi scrive applicazioni sono un superset delle funzionalità disponibili nella versione 3 di Silverlight già disponibile ormai da tempo per l'ambiente Web. È stato scelto di utilizzare la versione 3 in quanto la versione 4 era ancora in fase beta all'inizio della presentazione degli strumenti di sviluppo: strada conservativa per lavorare su un runtime stabile.
Il runtime di Silverlight 3 è comunque stato rivisto da due punti di vista: il primo riguarda una serie di ottimizzazioni, rese possibili dalla natura mono-piattaforma di Windows Phone 7 rispetto alle necessità di lavorare su più piattaforme di Silverlight per il Web. Il secondo punto di vista riguarda l'aggiunta di alcune feature specifiche per l'ambiente Windows Phone 7, come ad esempio il supporto per l'input via "dito", o, l'alleggerimento delle politiche di restrizione della sandbox vista la natura personale del device e l'installazione via marketplace delle applicazioni.
Nota: Prima di iniziare a lavorare sul codice ricordiamo che possiamo scaricare gratuitamente l'SDK, meglio conosciuto come Windows Phone Developer Tools e che abbiamo a disposizione anche una home page dedicata agli sviluppatori, dove trovare altre informazioni importanti: la guida per la definizione della user interface così come la guida per lo sviluppo di siti web.
Nelle pagine seguenti inizieremo a lavorare direttamente sul codice per esaminare alcune delle caratteristiche più interessanti della programmazione per Windows Phone 7, dal touch screen alla memorizzazione dei file, fino al GPS.
Touch Screen
Parlando di Silverlight 3 abbiamo già accennato la prima caratteristica hardware disponibile su qualunque telefono di qualunque produttore, ovvero il supporto al touch screen con quattro punti di contatto. Le classi messe a disposizione dall'ambiente .NET, simili a quelle già presenti in WPF e Silverlight, consentono di intercettare l'interazione dell'utente con lo schermo del device. Nell'esempio di codice seguente, tramite l'attributo ManipulationStarted, indichiamo al runtime il nome del metodo che consente di intercettare l'evento di inizio di "manipolazione" dell'utente.
<Grid x:Name="ContentGrid" Grid.Row="1">
<TextBlock Height="119" Width="394"
OrizontalAlignment="Left" VerticalAlignment="Top"
Margin="44,88,0,0" TextWrapping="Wrap"
Name="TextBlock1" Text="Manipulation Sample ..."
ManipulationStarted="textBlock1_ManipulationStarted" />
</Grid>
Nota: ci sono diverse metodologie per intercettare queste operazioni, come ad esempio utilizzare il metodo OnManipulationStarted
a livello di singola Page
. In questo articolo, qualunque esempio, viene realizzato volutamente con la tecnica più semplice al fine di introdurre gli argomenti.
Il codice della Page
che ospita l'event handler mostra come intercettare l'evento ManipulationStarted
per, ad esempio, modificare il codice del TextBox stesso:
namespace _40_Manipulation
{
public partial class MainPage : PhoneApplicationPage
{
public MainPage()
{
InitializeComponent();
SupportedOrientations = SupportedPageOrientation.Portrait | SupportedPageOrientation.Landscape;
}
private void textBlock1_ManipulationStarted(Object sender, ManipulationStartedEventArgs e)
{
TextBlock txtblk = sender as TextBlock;
Random rand = new Random();
Color clr = Color.FromArgb(255, (byte)rand.Next(256), (byte)rand.Next(256), (byte)rand.Next(256));
txtblk.ForeGround = new SolidColorBrush(clr);
}
}
}
Eseguendo l'esempio, anche sull'emulatore, possiamo iniziare a "manipolare" il TextBox
tramite il click e trascinamento del mouse. Il metodo ManipulationStarted
riceve un'istanza di ManipulationStartedEventArgs
che ci mette a disposizione la proprietà ManipulationOrigin
, che a sua volta espone X
e Y
per indicarci la posizione di inizio dell'operazione da parte dell'utente.
È anche possibile indicare al runtime, impostando la proprietà Handled
a true, che abbiamo correttamente gestito l'evento evitando di invocare eventuali altri event handler nella "route" degli eventi stessi.
Ogni device avrà almeno 3 pulsanti, tra l'altro raffigurati nell'emulatore già disponibile con l'SDK: Back, Start e Search; questi tre pulsanti devono essere "fisici" su ogni device; ogni produttore hardware potrà poi aggiungere altri pulsanti sul device come ad esempio la gestione del volume, l'attivazione della fotocamera e così via, ma è garantita la presenza dei tre pulsanti principali.
Media - Launcher e Chooser
Una libreria consente la gestione della parte "Media & Playback": è possibile attivare la fotocamera da codice, sia per scattare una foto che iniziare una ripresa video impostando il livello di qualità così come altre caratteristiche del "media" da gestire.
Queste API consentono di svelare il comportamento dell'applicazione rispetto alla fotocamera anche sull'emulatore: l'emulatore infatti non ha un menu per attivare direttamente la fotocamera, ma consente di scattare una foto (ovviamente di test) se attivato da codice.
Questo argomento introduce un concetto importante: Launcher & Chooser; con questi due termini si identificano due modalità con cui l'applicazione può interagire con API presenti sull'ambiente:
Tipo di interazione | Descrizione |
---|---|
Launcher | consente di attivare e demandare il controllo ad un componente esterno |
Chooser | facciamo riferimento a quanto conosciamo oggi come dialog, ovvero una interfaccia visiva che l'applicazione richiama attivando un componente di sistema. Il compito del componente di sistema è quello di restituire un valore all'applicazione sia esso un contatto o un messaggio oppure un elemento dell'album fotografico come nel nostro caso. |
CameraCaptureTask
è un launcher che consente di attivare la fotocamera per scattare una foto; il codice di attivazione è molto semplice:
CameraCaptureTask task = new CameraCaptureTask();
task.Show();
PhotoChooserTask
è un chooser che consente di scegliere una foto già presente sul device o sulla scheda di memoria.
PhotoChooserTask task = new PhotoChooserTask();
task.Show();
I chooser restituiscono il controllo all'applicazione tramite un metodo della Page
che ha attivato il chooser stesso. Ad esempio, nel codice seguente recuperiamo la foto scelta dall'utente e, dopo il classico controllo di sicurezza e il controllo sulla scelta del pulsante "ok" da parte dell'utente, la assegniamo ad un controllo image
definita nel codice XAML:
public override void OnChooserReturn(Object sender, EventArgs e)
{
TaskEventArgs<PhotoResult> result = e as TaskEventArgs<PhotoResult>;
if (result != null)
{
if (result.TaskResult == TaskResult.OK)
{
BitmapImage bmp = new BitmapImage();
bmp.SetSource(result.Result.ChosenPhoto);
image1.Source = bmp;
}
}
base.OnChooserReturn(sender, e);
}
Da notare, come sia possibile debuggare il codice al rientro sull'applicazione.
Analizzando con Reflector le librerie disponibili nella April CTP Refresh disponibile ad oggi, sveliamo i seguenti chooser:
L'interfaccia IChooser
deriva a sua volta da ITask
(da qui il suffisso Task
che abbiamo visto nelle classi del codice precedente); tramite questa interfaccia vengono definiti altri componenti per l'interazione con il device, componenti che sveliamo sempre con Reflector:
I componenti che implementano ITask
o IChooser
sono definiti nell'assembly Microsoft.Phone.Tasks
nell'omonimo namespace. Da notare come alcuni launcher abbiamo la classe corrispondente con il suffisso Launcher
mentre altri abbiamo il suffisso Task
.
Isolated Storage
L'unico store disponibile, vista l'assenza in questa prima versione, di SQL Compact Edition (che in realtà è presente sul device ma non accessibile dal codice della sandbox) è l'Isolated Storage ben conosciuto a chi sviluppa applicazioni Silverlight.
Diversamente da "Silverlight Web", l'isolated storage non ha limiti sulla dimensione quindi abbiamo a disposizione tutto lo spazio disponibile sul device.
L'Isolated Storage divide lo spazio disco in spazi isolati per ogni applicazione: è impossibile accedere all'isolated storage di un'altra applicazione tramite le API, così come è impossibile accedere al file system del device da una applicazione: questo spazio può essere considerato quindi sicuro rispetto ad accessi indesiderati. Lo spazio viene creato all'installazione dell'applicazione e viene eliminato alla sua disinstallazione da parte dell'utente.
Le API messe a disposizione consentono di sfruttare lo storage in due diverse modalità: la prima per memorizzare file, dati, media e così via, la seconda per memorizzare le impostazioni delle applicazioni. Qualche riga di codice vale più di mille parole. Le righe di codice sono pilotate dalla seguente user interface:
Lasciando i nomi dei controlli ai valori di default, questo il codice per scrivere (button1_Click
) un file nell'Isolated Storage e per leggere (button2_Click
) il file appena creato.
public partial class MainPage : PhoneApplicationPage
{
public MainPage()
{
InitializeComponent();
SupportedOrientations = SupportedPageOrientation.Portrait | SupportedPageOrientation.Landscape;
}
private button1_Click(Object sender, RoutedEventArgs e)
{
IsolatedStorageFile myStore = IsolatedStorageFile.GetUserStoreForApplication();
myStore.CreateDirectory("RootFolder");
StreamWriter writeFile = new StreamWriter(
new IsolatedStorageFileStream("RootFoldermyFile.txt", FileMode.OpenOrCreate, myStore));
writeFile.WriteLine(textBox1.Text);
writeFile.Close();
writeFile.Dispose();
}
private button2_Click(Object sender, RoutedEventArgs e)
{
IsolatedStorageFile myStore = IsolatedStorageFile.GetUserStoreForApplication();
StreamReader readFile = new StreamReader(
new IsolatedStorageFileStream("RootFoldermyFile.txt", FileMode.Open, myStore));
string fileText = readFile.ReadLine();
textBox1.Text = fileText;
writeFile.Close();
writeFile.Dispose();
}
}
La prima operazione da eseguire è ottenere un riferimento allo store tramite il metodo statico GetUserStoreForApplication
; il risultato è appunto una istanza di tipo IsolatedStorageFile
, che, a discapito del nome, rappresenta lo storage dove poter memorizzare file e directory.
La riga successiva infatti crea una directory denominata RootFolder
in cui inserire un file, tramite un IsolatedStorageFileStream
. Nel nostro caso stiamo creando (o riaprendo se già esiste) un file denominato myFile.txt
dove eseguire una WriteLine
del contenuto del textbox (TextBox1
).
Il codice per rileggere il contenuto del file prevede le stesse operazioni utilizzando uno StreamReader
al posto dello StreamWriter
. In questo caso eseguiamo un ReadLine
per valorizzare poi il TextBlock presente sulla page.
Oltre allo spazio per salvare manualmente elementi, l'Isolated Storage consente di salvare impostazioni applicative senza bisogno di definire un file flat e il codice per impacchettare e spachettare le informazioni. Il terzo e quarto pulsante della page precedente gestiscono queste operazioni:
private button4_Click(Object sender, RoutedEventArgs e)
{
textBox2.Text = IsolatedStorageSettings.ApplicationSettings["Data"].ToString();
}
private button3_Click(Object sender, RoutedEventArgs e)
{
IsolatedStorageSettings.ApplicationSettings["Data"] = DateTime.Now.ToString();
// N.B. Save automatico quando si chiude l'applicazione
// se stateless è bene salvare subito
IsolatedStorageSettings.ApplicationSettings.Save();
}
}
Nel nostro esempio è presente una nota che apre una discussione sulla modalità con cui girano le applicazioni in foreground rispetto all'attivazione di Chooser e Launcher, rispetto all'apertura di nuove applicazioni e rispetto all'arrivo di richieste da parte del sistema, discussione che richiede un articolo separato. È comunque buona norma persistere le informazioni a fronte di una modifica alle impostazioni stesse.
Per quanto riguarda la connettività verso servizi, è disponibile un subset di WCF allineato a quanto conosciamo con Silverlight 3: importante sottolineare come l'accesso ai servizi sia asincrono per definizione e consenta chiamate SOAP, REST e JSON verso i servizi.
Caratteristiche del telefono
Il device, per tornare sulle caratteristiche hardware, deve fornire
Caratteristica | Descrizione |
---|---|
Wi-Fi | per consentire l'accesso a internet |
Fotocamera | da almeno 5 megapixel con flash. Le applicazioni possono registrarsi come "Photo Extra Application" per apparire nel menu apposito della fotocamera: questo consente di richiamare applicazioni (si pensi al ritocco fotografico) durante l'interazione dell'utente con la fotocamera |
Accelerometro | le API consentono di intercettare l'accelerazione intesa come cambio di velocità. L'accelerometro, con la fotocamera "in Still" consente di rispondere alla gravitazionalità, indicare l'accelerazione in senso orizzontale, verticale e di profondità. L'acceletrometro consente di ottenere un vettore tridimensionale che indica come la fotocamera sia orientrata rispetto alla terra. Da codice è possiible richiedere il vettore tramite l'abbonamento ad eventi ovvero è possibile richiedere alle API di notificare l'applicazione quando c'è una modifica nei dati del vettore. L'accelerometro consente anche di intercettare movimenti bruschi del device |
Bussola | ad oggi sembra che le applicazioni non possano accedere a questi dati |
Location | in base alle impostazioni, il servizio presente sul device consente di rintracciare la posizione dell'utente in base a GPS, Wi-Fi e rete cellulare. Vedremo un esempio fra un attimo |
Speech | Il device supporta sia "speech synthesis" che "speech recognition" attraverso le classi disponibili in .NET 4.0 |
Vibration | pilotabile anche da codice |
Push Notifications | il servizio consente di inviare informazioni al device sotto forma di notifiche evitando che il device stesso debba effettuare operazioni di polling che, oltre a non essere efficienti dal punto di vista informatico, consumerebbero inutilmente la batteria |
Alcuni servizi richiedono articoli specifici: nell'attesa, è possibile trovare qui si trovano vari esempi nella sezione media e alcuni post nei blog con qualche dettaglio delle varie possibilità e relativo codice.