Una delle caratteristiche più interessanti del nuovo runtime di Windows 8 è rappresentato da una serie di contratti cui le applicazioni Windows Store possono aderire, impegnandosi ad esporre determinate funzionalità (di ricerca, di condivisione, ecc.) verso il sistema operativo o altre applicazioni, i quali possono dunque accedere ai contenuti e alle funzionalità esposte senza che sia necessaria una previa conoscenza dei meccanismi di funzionamento interno dell'applicazione "target"..
Tramite l'implementazione del contratto di Search, in particolare, è possibile per l'utente effettuare ricerche all'interno dell'applicazione in qualunque momento, anche se l'applicazione non è in esecuzione, semplicemente accedendo al charm di ricerca. Quest'ultimo consente di ricercare contenuti all'interno del sistema, siano essi file, applicazioni, siti web, impostazioni del pannello di controllo, elementi all'interno delle librerie dell'utente come foto o video, ovvero ‐ e questo è il caso preso in esame in questo articolo ‐ all'interno di altre applicazioni (quelle che, per l'appunto, implementano il relativo contratto di Search).
Per esempio, nell'immagine seguente abbiamo ricercato il testo "learn" fra le applicazioni installate sulla macchina (un po' come avveniva in Windows 7 tramite il box di ricerca nel menu Start):
Nell'immagine precedente si può notare che sotto Apps, Settings e Files, sono presenti una serie di applicazioni. Queste applicazioni si sono registrate al motore di search di Windows 8 tramite l'implementazione del relativo contratto.
Ad esempio, potremmo cercare un testo (nel nostro caso "thinkahead") all'interno dello Store, ovvero dell'applicazione installata localmente per la visualizzazione del Windows Store. Il sistema operativo chiederà all'applicazione locale di effettuare una ricerca e di presentare i risultati.
Perché la nostra applicazione venga utilizzata come target di ricerca occorre implementare il Search Contract. In questo articolo introduttivo ripercorriamo i passi da seguire per ottenere una semplice applicazione ricercabile.
Per prima cosa, in Visual Studio 2012 creiamo un nuovo progetto Windows 8. Nella finestra di dialog, selezioniamo Windows Store per accedere all'elenco dei template proposti da Visual Studio per applicazioni Windows 8. Scegli Blank App (XAML) come template, assegna al progetto il nome che preferisci e seleziona la directory su disco dove salvare la soluzione:
Prima di proseguire oltre, popoliamo la nostra applicazione con alcuni di dati d'esempio su cui effettuare semplici ricerche. In questo esempio non avremo nessuna pretesa grafica né tantomeno utilizzeremo pattern architetturali per rimanere sul pezzo.
Aggiungiamo al progetto una nuova classe, denominata Bike
, che rappresenti, poniamo, alcuni modelli di mountain bike (passatempo assai diffuso qui in Toscana), e una classe Biz
il cui unico metodo, GetAllBikes()
, restituisca una collezione di... biciclette, appunto. Questo che segue è il relativo codice:
public List GetAllBikes()
{
return new List()
{
new Bike() { BikeName = "Specialized Stumpjumper" },
new Bike() { BikeName = "Scott Spark" },
new Bike() { BikeName = "Kona Abra Cadabra" },
new Bike() { BikeName = "Cannondale Jekill" },
new Bike() { BikeName = "Rocky Mountain Altitude" },
new Bike() { BikeName = "Bianchi Metanol" },
new Bike() { BikeName = "Santacruz Tallboy" },
new Bike() { BikeName = "Canyon Torque" }
};
}
Agganciamo adesso i dati alla MainPage
della nostra applicazione. Per prima cosa, modifichiamo la pagina XAML aggiungendo un controllo ListView per mostrare l'elenco delle nostre entità (evidenziato in grassetto nel listato seguente):
<Page
x:Class="Html.it.Demo.SearchContractSample.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:Html.it.Demo.SearchContractSample"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d">
<Grid Background="{StaticResource ApplicationPageBackgroundThemeBrush}">
<ListView x:Name="list" DisplayMemberPath="BikeName" />
</Grid>
</Page>
Nel code behind (MainPage.xaml.cs
) mettiamo in binding il controllo appena creato con la nostra lista di biciclette (le linee da aggiungere sono evidenziate in grassetto):
public MainPage()
{
this.InitializeComponent();
var biz = new Biz();
list.ItemsSource = biz.GetAllBikes();
}
Se eseguiamo la nostra applicazione premendo F5, ecco quello che otteniamo:
Ora che abbiamo qualche dato con cui verificare gli esempi, per implementare la funzionalità di search occorre dichiarare la relativa funzionalità nell'application manifest della nostra applicazione.
Per farlo, è sufficiente aprire il file Package.appxmanifest
all'interno del nostro progetto e, nel tab Declarations, selezionare la funzionalità di Search tra le opzioni disponibili all'interno della dropdown list denominata Available Declarations.
Così facendo, abbiamo registrato la nostra applicazione come search provider, ovvero come applicazione in grado di rispondere in qualunque momento alle ricerche effettuate dall'utente, fornendo i contenuti che la app stessa gestisce.
Per vedere che cosa è successo a seguito della registrazione, cliccate con il tasto destro sul progetto e selezionate Deploy per installare l'applicazione sul PC. Tornate alla Start Page di Windows 8 (tasto Windows), attivate la Charms Bar (premete Windows+C
oppure spostate il mouse nell'angolo in basso a destra dello schermo) e selezionate il charm Search. La nuova applicazione appare adesso nell'elenco delle applicazioni in cui poter effettuare ricerche:
Dal momento che non abbiamo ancora implementato il contratto di search, se provassimo a cliccare sulla nostra applicazione e a inserire un testo da cercare, non otterremmo alcun risultato.
Implementare il search contract
Il secondo passo da compiere è dunque quello di implementare il contratto. Per questo è sufficiente aggiungere un nuovo Search Contract al nostro progetto (composto da una pagina XAML e dal relativo code behind).
Visual Studio vi chiederà se importare automaticamente una serie di file (classi helper, converter, collezioni di stile, etc.) funzionali alla corretta implementazione del contratto. Premete OK per confermare. L'aggiunta del contratto da Visual Studio inserisce automaticamente la declaration nel manifest che abbiamo visto prima.
Ecco come appare il contenuto del progetto al termine della nostra operazione:
Oltre a creare la pagina in cui verranno mostrati i risultati della ricerca (di default, SearchResultPage1.xaml
) e ad aggiungere i file necessari, Visual Studio modifica anche la classe App.cs
aggiungendovi l'handler (asincrono, come si può notare) incaricato di gestire l'evento di attivazione della ricerca da parte dell'utente. Il codice del metodo è riportato qui di seguito:
protected async override void OnSearchActivated(Windows.ApplicationModel.Activation.SearchActivatedEventArgs args)
{
// TODO: Register the Windows.ApplicationModel.Search.SearchPane.GetForCurrentView().QuerySubmitted
// event in OnWindowCreated to speed up searches once the application is already running
// If the Window isn't already using Frame navigation, insert our own Frame
var previousContent = Window.Current.Content;
var frame = previousContent as Frame;
// If the app does not contain a top-level frame, it is possible that this
// is the initial launch of the app. Typically this method and OnLaunched
// in App.xaml.cs can call a common method.
if (frame == null)
{
// Create a Frame to act as the navigation context and associate it with
// a SuspensionManager key
frame = new Frame();
Html.Demo.SearchContract.Common.SuspensionManager.RegisterFrame(frame, "AppFrame");
if (args.PreviousExecutionState == ApplicationExecutionState.Terminated)
{
// Restore the saved session state only when appropriate
try
{
await Html.Demo.SearchContract.Common.SuspensionManager.RestoreAsync();
}
catch (Html.Demo.SearchContract.Common.SuspensionManagerException)
{
//Something went wrong restoring state.
//Assume there is no state and continue
}
}
}
frame.Navigate(typeof(SearchResultsPage1), args.QueryText);
Window.Current.Content = frame;
// Ensure the current window is active
Window.Current.Activate();
}
Come si vede, il metodo riceve come parametro un oggetto di tipo SearchActivatedEventArgs
. È proprio all'interno di questo oggetto che viene passata la stringa di ricerca digitata (query string) dall'utente nella relativa maschera.
Senza addentrarci troppo nei dettagli di funzionamento del metodo (che, in buona sostanza, controlla se l'applicazione era in esecuzione o meno per ripristinarne lo stato o creare il frame di contenuti da zero), è interessante osservare come la query string digitata dall'utente (ed evidenziata in grassetto nel listato sopra riportato) venga poi passata come parametro alla pagina di ricerca per poter essere gestita nel modo più appropriato e per mostrare il risultato a video.
Vediamo di ricapitolare quanto accaduto finora: l'utente ha attivato il charm di Search e ha digitato del testo da ricercare all'interno della nostra applicazione. L'evento di attivazione della ricerca è stato intercettato dal metodo OnSearchActivated
della nostra classe App
, il quale ha anche provveduto a spostare il frame principale dell'applicazione sulla pagina di ricerca (SearchResultPage1.xaml
, nel nostro esempio), passando il criterio di ricerca ricevuto (args) e ad attivarla (Window.Current.Activate()
) in modo da mostrare all'utente i risultati dell'operazione.
Il binding dei dati
Ma che cosa è successo nel frattempo alla nostra query string? In estrema sintesi, al momento dell'attivazione della pagina di ricerca viene invocato (passando dal metodo OnNavigatedTo che intercetta l'evento di attivazione della pagina) il metodo LoadState, il quale riceve come parametro proprio la stringa di ricerca introdotta dall'utente.
Ecco il listato completo del metodo LoadState, proposto di default dal contratto di search nella pagina SearchResultPage1, cui spetta di ricevere la richiesta dell'utente e di elaborare i relativi risultati:
protected override void LoadState(Object navigationParameter, Dictionary pageState)
{
var queryText = navigationParameter as String;
// (omissis)
var filterList = new List();
filterList.Add(new Filter("All", 0, true));
// Communicate results through the view model
this.DefaultViewModel["QueryText"] = 'u201c' + queryText + 'u201d';
this.DefaultViewModel["Filters"] = filterList;
this.DefaultViewModel["ShowFilters"] = filterList.Count > 1;
}
Questa classe utilizza il DefaultViewModel per mettere in binding i risultati della ricerca e per esporre alcune informazioni relative alla ricerca stessa, come la query string utilizzata, l'elenco dei filtri disponibili (in questo caso, un generico filtro "All") e il numero di record trovati.
Ciò che dobbiamo fare è aggiungere, nel Biz
, un metodo che permetta (seppur in modo primitivo) di operare la ricerca sui campi che ci interessano e ci restituisca l'elenco delle voci che soddisfano il criterio di ricerca; questo metodo verrà poi richiamato nel metodo LoadData
sopra illustrato, passando come parametro la query string digitata dall'utente.
Ecco il metodo da aggiungere alla classe Biz:
public List SearchBikes(String bikeName)
{
return this.GetAllBikes().Where(m => m.BikeName.Contains(bikeName)).ToList();
}
Ora modifichiamo il metodo LoadData del contratto di ricerca in modo da ottenere l'elenco di entità che soddisfano i criteri di ricerca inseriti dall'utente, elenco che dovrà essere messo in binding con il DefaultViewModel utilizzato dalla pagina per mostrare i risultati.
Già che ci siamo, modifichiamo il filtro di default, affinché mostri il numero di record trovati (bikes.Count), e visualizziamolo a schermo (filterList.Count >= 1). In grassetto sono evidenziate le modifiche apportate al codice:
protected override void LoadState(Object navigationParameter, Dictionary pageState)
{
var queryText = navigationParameter as String;
// Ricerco le bici
var biz = new Biz();
var bikes = biz.SearchBikes(queryText);
// TODO: Application-specific searching logic. The search process is responsible for
// creating a list of user-selectable result categories:
//
// filterList.Add(new Filter("", ));
//
// Only the first filter, typically "All", should pass true as a third argument in
// order to start in an active state. Results for the active filter are provided
// in Filter_SelectionChanged below.
var filterList = new List();
filterList.Add(new Filter("All", bikes.Count, true));
// Communicate results through the view model
this.DefaultViewModel["Results"] = bikes;
this.DefaultViewModel["QueryText"] = 'u201c' + queryText + 'u201d';
this.DefaultViewModel["Filters"] = filterList;
this.DefaultViewModel["ShowFilters"] = filterList.Count >= 1;
}
L'ultima modifica consiste mostrate a video le proprietà delle entità che ci interessano. Per far questo è sufficiente modificare il controllo GridView denominato resultsGridView
nel modo che segue:
<GridView
x:Name="resultsGridView"
AutomationProperties.AutomationId="ResultsGridView"
AutomationProperties.Name="Search Results"
TabIndex="1"
Grid.Row="1"
Margin="0,-238,0,0"
Padding="110,240,110,46"
SelectionMode="None"
IsSwipeEnabled="false"
IsItemClickEnabled="True"
ItemsSource="{Binding Source={StaticResource resultsViewSource}}">
<GridView.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding BikeName}" Margin="0,20,0,0" Foreground="#7CFFFFFF" HorizontalAlignment="Left" />
</DataTemplate>
</GridView.ItemTemplate>
<GridView.ItemContainerStyle>
<Style TargetType="Control">
<Setter Property="Height" Value="70"/>
<Setter Property="Margin" Value="0,0,38,8"/>
</Style>
</GridView.ItemContainerStyle>
</GridView>
Effettuiamo un nuovo deployment dell'applicazione e proviamo ad attivare il charm di ricerca inserendo un qualsiasi testo da ricercare. Ecco il risultato:
Sebbene in questo esempio ci siamo limitati a restituire delle semplici stringhe di testo, non c'è alcun limite al tipo di dati e contenuti da mostrare come risultato della ricerca (immagini, contenuti multimediali, impostazioni di configurazione, ecc.). Non solo, ma è anche possibile sfruttare le API esposte da WinRT per offrire all'utente dei suggerimenti mentre sta digitando il testo da ricercare.
Come abbiamo visto, implementare il contratto di ricerca è molto semplice; se non consideriamo la parte grafica e la logica interna di ricerca che ovviamente potrebbe essere molto complessa, è sufficiente aggiungere il Search Contract al progetto, scegliere un nome per la pagina di ricerca e modificarla per visualizzare i risultati nel modo più adatto.