di Michele Locuratolo- Microsoft MVP
Nella prima parte di questo articolo, abbiamo analizzato il funzionamento di State and Notification Broker guardandone l’architettura, andando a leggere alcune informazioni di stato e ricevendo le relative notifiche di cambiamento dei valori da parte del sistema.
Abbiamo iniziato ad apprezzare l’utilità di questa API andando a modificare il comportamento del nostro software in funzione del valore di alcuni stati ed alla variazione di essi ed abbiamo visto quanto sia semplice l’utilizzo nel modo Managed.
In questa seconda parte, ci concentreremo su alcuni aspetti più avanzati di questo sistema, guardando in particolare il modo in cui possiamo filtrare le notifiche (specializzandole) ed il modo in cui possiamo avviare la nostra applicazione al verificarsi di un evento sul sistema operativo.
.gif)
Specializzare le notifiche
Ricevere le notifiche relative al cambiamento del valore degli stati del sistema operativo, se da un lato è una funzionalità davvero comoda, dall’altro può rivelarsi estremamente svantaggiosa qualora le notifiche dovessero essere troppe o troppo frequenti.
Proviamo infatti ad immaginare una situazione in cui abbiamo la necessità di notificare al nostro software solo le chiamate in arrivo da un determinato contatto. La prima soluzione che viene in mente è quella di filtrare la notifica usando un costrutto if o switch. Sebbene la soluzione potrebbe essere corretta, di sicuro non è quella ottimale.
Come detto in precedenza infatti, è possibile specializzare le notifiche in base a determinati criteri di filtro. Tale specializzazione ha come scopo quello di restringere il range di notifiche inviate all’applicazione solo a determinati valori da noi imposti.
L’applicazione del filtro avviene attraverso l’impostazione di due proprietà specifiche dell’oggetto SystemState. Le proprietà interessate sono: ComparisonType e ComparisonValue.
La propietà ComparisonTyperappresenta, come si intuisce dal nome, il tipo di filtro che vogliamo applicare alla notifica. L’impostazione di questa proprietà avviene attraverso l’enumerazione StatusComparisonType i cui valori sono elencati in tabella 1.
| Enumerazione | Descrizione |
| Equal | Il valore presente nel registro al momento della notifica è uguale a quello impostato nella proprietà ComparisonValue |
| NotEqual | Il valore presente nel registro al momento della notifica è diverso da quello impostato nella proprietà ComparisonValue |
| Greater | Il valore presente nel registro al momento della notifica è maggiore da quello impostato nella proprietà ComparisonValue |
| GreaterOrEqual | Il valore presente nel registro al momento della notifica è maggiore o uguale a quello impostato nella proprietà ComparisonValue |
| Less | Il valore presente nel registro al momento della notifica è minore di quello impostato nella proprietà ComparisonValue |
| LessOrEqual | Il valore presente nel registro al momento della notifica è monore o uguale a quello impostato nella proprietà ComparisonValue |
| Contains | Il valore presente nel registro al momento della notifica contiene quello impostato nella proprietà ComparisonValue |
| StartsWith | Il valore presente nel registro al momento della notifica inizia con quello impostato nella proprietà ComparisonValue |
| EndWith | Il valore presente nel registro al momento della notifica finisce con quello impostato nella proprietà ComparisonValue |
| AnyChange | Non viene eseguita nessuna comparazione. Le notifiche vengono inviate al cambiamento di qualsiasi valore. Questo, oltre ad essere il valore di default della proprietà ComparisonType, è il comportamento classico in assenza di filtri. |
Tabella 1: valore dell'enumerazione StatusComparisoType
La seconda proprietà (ComparisonValue) è di tipo Object in modo da rendere possibile impostare un qualsiasi oggetto come parametro. Resta comunque inteso che il tipo da inserire come filtro deve essere compatibile con il tipo di destinazione che stiamo filtrando.
Di seguito, un esempio di codice che mostra come ricevere le notifiche solo quando la chiamata in arrivo sul device arriva da un contatto specifico:
using System;
using System.Windows.Forms;
using Microsoft.WindowsMobile.Status;
namespace MSDN.Article.SNBroker {
public partial class Form3 : Form {
/// <summary>
/// Chiamata in arrivo sul device
/// </summary>
private readonly SystemState _incomingCaller;
public Form3() {
InitializeComponent();
_incomingCaller = new SystemState(SystemProperty.PhoneIncomingCallerName);
//Imposto il ComparisonType ad Equal
in modo da recuperare le sole chiamate in arrivo da un contatto specifico
_incomingCaller.ComparisonType = StatusComparisonType.Equal;
//Imposto il ComparisonValue
_incomingCaller.ComparisonValue = "MSDN, Italia";
//Registro l'evento di cambio del caller
_incomingCaller.Changed += new ChangeEventHandler(_incomingCaller_Changed);
}
/// <summary>
/// Gestione dell'evento di cambio dello stato
/// </summary>
/// <param name="sender"></param>
/// <param name="args"></param>
void _incomingCaller_Changed(object sender, ChangeEventArgs args) {
//Recupero il nome del chiamante da ChangeEventArgs
string CallerName = args.NewValue.ToString();
//Aggiornamento della label con il contatto chiamante
lblCaller.Text = "Last call from: " + CallerName;
//Aggiornamento dell numero di chiamate relative al chiamante specifico
UpdateCallValue(CallerName);
}
/// <summary>
/// Chiusura dell'applicazione
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void menuItem1_Click(object sender, EventArgs e) {
Application.Exit();
}
}
}
Come è facile intuire, il contatore delle chiamate in arrivo verrà incrementato solo per lo specifico contatto su cui si è seguito il filtro (vedi Fig. 1).
Figura 1: l'applicazione in esecuzione
In questo semplice esempio, abbiamo visto come incrementare il numero di chiamate in arrivo da uno specifico cliente. Una possibile applicazione reale potrebbe essere quella di voler memorizzare nell’archivio della nostra applicazione (sia esso un Data Base, un file XML, un albero di oggetti serializzato etc.), il numero di chiamate in arrivo dai clienti registrati all’applicazione (magari per fini statistici o di controllo).
Modificando leggermente il codice mostrato, sarà possibile assolvere al compito in modo semplice e con poche righe di codice.
Allo stesso modo, possiamo praticamente filtrare qualsiasi tipo di stato in modo da specializzarlo per l’uso che ne faremo nella nostra applicazione.
Persistent Notification
Sia nel precedente articolo che in questo, abbiamo visto come rendere il nostro software in grado di leggere alcuni valori relativi al sistema operativo ed eventualmente riceverne le notifiche di cambiamento dello stato stesso.
In tutti gli esempi però, abbiamo usato una applicazione già in esecuzione che oltre a leggere gli stati, al variare di alcuni di essi modificava il comportamento del software.
Lavorando in ambiente mobile però, è noto che si deve cercare di limitare quanto più possibile gli sprechi di risorse (molto più che in ambienti desktop). Avere quindi una applicazione sempre in esecuzione ed in attesa di un evento di cambio stato che potrebbe verificarsi anche raramente, è uno spreco inaccettabile. Se a questo aggiungiamo il fatto che all’avvio del device, l’applicazione potrebbe non partire in automatico, oltre allo spreco di risorse si aggiunge un elevato rischio di perdere delle notifiche importanti.
Nell’esempio precedente abbiamo visto come tenere traccia delle chiamate in arrivo da uno specifico cliente, memorizzando l’informazione nella nostra applicazione. Qualora l’applicazione stessa non fosse avviata, tale numero non verrebbe memorizzato mancando, di fatto, un sottoscrittore all’evento di notifica del cambio di stato della proprietà SystemProperty.PhoneIncomingCallerName.
State and Notification Broker fornisce una soluzione molto semplice ed elegante a questo tipo di problema attraverso il meccanismo di “notifiche persistenti” (Persistent Notificazion).
Il principio alla base di questo meccanismo è molto semplice: al fine di poter avviare una applicazione allo scatenarsi di un determinato evento, Windows Mobile deve essere in grado di valutare quale applicazione avviare e sulla base di quale evento. Come visto nella prima parte di questo articolo, il repository unificato in cui le informazioni di stato vengono memorizzate è il registro di Windows. E’ quindi qui che andranno inserite tutte le informazioni necessarie.
Usando codice Managed, eseguire questa operazione è molto semplice: sarà infatti sufficiente creare innanzitutto una istanza di SystemState passando l’informazione relativa allo stato di cui vogliamo ricevere le notifiche, e successivamente richiamare il metodo EnableApplicationLauncher dell’istanza appena creata. Nella sua forma più semplice, tale metodo accetta come parametro in ingresso una stringa che identifica l’applicazione.
Di seguito un esempio di codice che riprende quello dell’esempio precedente, estendendolo in modo da rendere le notifiche persistenti:
/// <summary>
/// Id dell'applicazione
/// </summary>
private const string _APPID_ = "MSDN.Article.SNBroker.PersistentNotificationExample";
public Form3() {
InitializeComponent();
_incomingCaller = new SystemState(SystemProperty.PhoneIncomingCallerName);
//Imposto il ComparisonType ad Equal
in modo da recuperare le sole chiamate in arrivo da un contatto specifico
_incomingCaller.ComparisonType = StatusComparisonType.Equal;
//Imposto il ComparisonValue
_incomingCaller.ComparisonValue = "MSDN, Italia";
//Rendo la notifica persistente
_incomingCaller.EnableApplicationLauncher( _APPID_ );
//Registro l'evento di cambio del caller
_incomingCaller.Changed += new ChangeEventHandler(_incomingCaller_Changed);
}
L’esecuzione del metodo EnableApplicationLauncher comporterà la crezione delle opportune chiavi nel registro (Fig. 2)
Figura 2: il registro con la notifica persistente appena creata
Come si nota dall’immagine in Fig. 2, tra le varie informazioni automaticamente memorizzate nel registro, emergono:
-
Conditional Target: avendo specializzato la notifica filtrando l’Incoming Caller Name, nel registro viene memorizzato il valore in base a cui la notifica deve essere inviata alla nostra applicazione
-
Application: è il path completo dell’applicazione da avviare se le condizioni di notifica si verificano
-
Value Name: è lo stato a cui siamo registrati per ricevere le notifiche di cambiamento
-
HKEY: è il riferimento alla chiave del registro HKEY_LOCAL_MACHINE
Il metodo EnableApplicationLauncher,oltre alla sua forma base, ha due overload molto utili. Il primo accetta, oltre all’ApplicationID, il path dell’applicazione da lanciare. Questo ci da la possibilità, da una applicazione A, di impostare le notifiche persistenti da inviare ad una applicazione B.
Il secondo overload, oltre all’ApplicationId ed al Path, ci permette di inviare una serie di parametri all’applicazione. L’invio di parametri può risultare davvero comodo in scenari in cui l’applicazione deve comportarsi in modo diverso in base al modo in cui viene avviata. Un semplice esempio di questo comportamento è visibile nell’esempio in basso:
using System;
using System.Collections.Generic;
using System.Windows.Forms;
namespace MSDN.Article.SNBroker {
static class Program {
/// <summary>
/// The main entry point for the application.
/// </summary>
[MTAThread]
static void Main(string[] args) {
Application.Run( new Form3(args) );
}
}
}
public Form3(string[] args) {
InitializeComponent();
if (args.Length > 0) {
if (args[0].ToString() == "-call") {
//Se l'applicazione viene avviata a causa di una notifica, lo indico nella label
lblDBG.Text = "Notified";
} else {
//Se il parametro non è riconosciuto, imposto il valore della label a Not Recognized
lblDBG.Text = "Not Recognized";
}
}else {
lblDBG.Text = "Normal";
}
}
Al verificarsi della condizione che fa scattare l’evento di notifica (nel caso dell’esempio, una chiamata in arrivo dal contatto memorizzato come “MSDN, Italia”), State and Notification Broker deve inviare l’evento di notifica all’applicazione. Per farlo, il primo passo è di verificare che l’applicazione non sia già in esecuzione. Tale ricerca viene svolta internamente dalla funzione RegistryNotifyApp il cui scopo è quello di verificare che l’applicazione specificata nella chiave Application non sia già in esecuzione. Qualora lo fosse, la notifica verrà inviata direttamente, senza dover rilanciare l’applicazione stessa.
Se l’applicazione non viene trovata, State and Notification Broker si occuperà di avviarla recuperando il path dalla chiave Application ed accodando due argomenti: /notify ed AppId.
Una volta lanciato il comando di avvio dell’applicazione, il sistema rieseguirà la ricerca dell’applicazione in esecuzione prima di inviare la notifica vera e propria. La ricerca viene fatta ogni secondo per dieci secondi. Qualora l’applicazione non dovesse essere trovata in esecuzione (magari per problemi di avvio), la notifica non verrà inviata. In caso contrario, tutto funzionerà come già visto nel paragrafo precedente.
NOTA : testando questa funzionalità con l’emulatore, potrebbe capitare che, a causa dei rallentamenti dovuti all’emulazione, trascorra il termine dei 10 secondi previsto per l’invio della notifica. Se durante i test le notifiche non dovessero arrivare, è consigliabile testare l’applicazione su un device reale.
Memorizzando le informazioni relative alla notifica da inviare nel registro, è possibile evitare che l’applicazione esegua nuovamente questa applicazione ad ogni avvio. Nel codice mostrato precedentemente infatti, non c’è alcun controllo sulla presenza o meno di una notifica persistente già registrata.
L’oggetto SystemState espone, a questo scopo, un metodo statico chiamato IsApplicationLauncherEnabled(appId) il cui tipo di ritorno è un bool ed il parametro da passare in ingresso è l’ApplicationID. Lo scopo di questo metodo è appunto quello di verificare, all’interno del registro, la presenza della una notifica persistente già registrata ed eventualmente di usarla anziché ricrearla. Di seguito un breve esempio di codice:
private void Form3Load(object sender, EventArgs e) {
if (SystemState.IsApplicationLauncherEnabled(_APPID_)) {
_incomingCaller = new SystemState(_APPID_);
} else {
_incomingCaller = new SystemState(SystemProperty.PhoneIncomingCallerName);
//Imposto il ComparisonType ad Equal
in modo da recuperare le sole chiamate in arrivo da un contatto specifico
_incomingCaller.ComparisonType = StatusComparisonType.Contains;
//Imposto il ComparisonValue
_incomingCaller.ComparisonValue = "Michele";
//Rendo la notifica persistente
_incomingCaller.EnableApplicationLauncher(_APPID_);
}
//Registro l'evento di cambio del caller
_incomingCaller.Changed += new ChangeEventHandler(_incomingCaller_Changed);
}
In questo caso, se IsApplicationLauncherEnabled torna true, vengono usate le informazioni presenti nel registro per ricreare l’istanza di SystemState. In caso contratrio, l’istanza viene creata manualmente e viene richiamato il metodo EnableApplicationLauncher per rendere la notifica persistente. In ogni caso, viene sottoscritto nuovamente l’evento Changed dell’istanza specifica di SystemState.
Notifiche personalizzate
Come detto nella prima parte di questo articolo, State and Notification Broker fornisce un repository unificato per la memorizzazione e la lettura degli stati, oltre ad un comodo sistema di notifica basato sulla sottoscrizione. Oltre agli stati esposti dal sistema operativo (che, come detto, sono più di 100), State and Notification Broker è talmente flessibile da permettere la creazione di stati personalizzati, il cui funzionamento è identico a quello degli stati del sistema.
Questa comoda funzionalità ci permette, ad esempio, di creare uno stato personalizzato relativo ad una applicazione A che viene letto e sottoscritto da una applicazione B esattamente come se fosse uno stato del sistema come quelli visti nel corso di questi articoli.
Gli stati personalizzati vanno memorizzati all’interno della chiave di registro HKEY_CURRENT_USER\Software\State\ specificando, per una convezione, il nome dell’azienda e dell’applicazione. Se ad esempio volessimo creare uno stato per notificare gli articoli recenti su MSDN, una possibile chiave di registro potrebbe essere HKEY_CURRENT_USER\Software\State\MSDN\MSDNArticle.
Di seguito un breve esempio di codice:
public partial class MainForm : Form {
const string exampleAppPersonalStateKey = @"HKEY_CURRENT_USER\Software\State\MSDN\MSDNArticle";
const string exampleAppPersonalStateValue = "RecentArticle";
private void FormMain_Load(object sender, EventArgs e)
{
_recentArticleState =
new RegistryState(exampleAppPersonalStateKey, exampleAppPersonalStateValue);
lblRecentArticle.Text = (string)_recentArticleState.CurrentValue;
_recentArticleState.Changed += new ChangeEventHandler(_recentArticleState_Changed);
}
RegistryState _recentArticleState;
// ...
// ...
}
A questo punto, all’interno del registro del device, sarà presente un nuovo stato che potrà essere sottoscritto da altre applicazioni.
Conclusioni
State and Notification Broker API è indubbiamente una delle rivoluzioni che riguardano la piattaforma Windows Mobile. Si è passati infatti da un sistema in cui “far girare” le applicazioni, ad un sistema con cui le nostre applicazioni possono integrarsi in modo semplice e, in alcuni casi, possono anche interagire tra loro o con il sistema operativo.
Nel precedente articolo abbiamo visto in generale il principio di funzionamento di State And Notification Broker mentre in questo ne abbiamo apprezzato la flessibilità e la configurabilità.
Il mio invito è quello di approfondire quanto più possibile questo argomento in quanto, il suo utilizzo nelle applicazioni, può davvero fare la differenza.
Approfondimenti (in inglese)
Per maggiori informazioni, si invita alla consultazione di queste risorse on line:
http://msdn2.microsoft.com/library/aa456240.aspx
http://msdn2.microsoft.com/library/bb286907.aspx
http://msdn2.microsoft.com/library/bb499669.aspx
http://msdn2.microsoft.com/library/bb154491.aspx