Technology Experience

.NET World

Programmazione, libri, snippet di codice, articoli tecnici

.NET World

App UWP localizzate solo in italiano: qualche problema con MvvmLightLibs e CommonServiceLocator

Introduzione
Una volta mi impegnavo a scrivere i titoli dei miei post sul blog concisi ed efficaci: ormai ci ho rinunciato. Passiamo alle cose serie.

Ho avuto un grave problema mischiando i seguenti ingredienti:

  1. Blank App (Universal Windows) con Visual Studio 2015, quindi parliamo di UWP
  2. Aggiunta di MvvmLightLibs tramite NuGet (attualmente la versione è la 5.2.0)
  3. Impostazione nel Package.appxmanifest (file di manifest dell’app) della lingua “it-IT” (voglio che la mia app sia solo ed esclusivamente localizzata in italiano)

Compilando l’app così creata otteniamo immediatamente 4 warning:

  1. MakePRI : warning 0xdef00522: Resources found for language(s) ‘en-us’ but no resources found for default language(s): ‘it-IT’. Change the default language or qualify resources with the default language. http://go.microsoft.com/fwlink/?LinkId=231899
  2. MakePRI : warning 0xdef01051: No default or neutral resource given for ‘Microsoft.Practices.ServiceLocation.Properties.Resources/ActivateAllExceptionMessage’. The application may throw an exception for certain user configurations when retrieving the resources.
  3. MakePRI : warning 0xdef01051: No default or neutral resource given for ‘Microsoft.Practices.ServiceLocation.Properties.Resources/ServiceLocationProviderNotSetMessage’. The application may throw an exception for certain user configurations when retrieving the resources.
  4. MakePRI : warning 0xdef01051: No default or neutral resource given for ‘Microsoft.Practices.ServiceLocation.Properties.Resources/ActivationExceptionMessage’. The application may throw an exception for certain user configurations when retrieving the resources.

I warning riguardano la libreria Common Service Locator, dipendenza di MvvmLightLibs, che NON vedete nelle reference del progetto, ma che in realtà c’è eccome. Sostanzialmente ci viene detto che ci sono tre risorse che NON hanno una resource per la nostra lingua italiana. Ho sempre ignorato questi warning, fino al momento in cui ho creato il package della mia app UWP da inviare sullo Windows Store. Una volta terminato l’upload, infatti, lo Store chiede l’inserimento delle informazioni dell’app in due lingue: italiano ed inglese. Questo perchè il package in realtà contiene risorse un po’ in italiano (e fin qua tutto ok), ed un po’ in inglese (falso!!!), e quindi dobbiamo dare supporto ad entrambe le lingue. Grazie anche al mio amico Twitter Fela, che mi ha dato qualche dritta sull’utility “makepri”, che mi ha permesso di indagare più a fondo.

Per risolvere ho provato a rimuovere totalmente la libreria MvvmLightLibs, ed a importare direttamente i suoi file sorgenti, ed effettivamente la situazione si sblocca. Importando uno ad uno i file .cs di MvvmLightLibs (solo lo stretto necessario), la libreria Common Service Locator non viene più inclusa,  e quindi quei 4 warning non compaiono più. Ma la cosa non mi piaceva, per nulla.

Soluzione finale
La tattica risolutiva è la seguente: nel nostro progetto dobbiamo trovare il modo di aggiungere nelle reference la libreria MvvmLightLibs, escludendo la Common Service Locator (fonte del problema). Non possiamo affidarci a NuGet, purtroppo, perchè scaricando MvvmLightLibs da lì viene automaticamente aggiunta la Common Service Locator. La soluzione però è dietro l’angolo. Se abbiamo scaricato almeno una volta MvvmLightLibs significa anche che ce l’abbiamo in locale sul nostro hard-disk, ovvero sul nostro PC. Nel caso specifico, essa si trova nel path:

%userprofile%.nugetpackagesMvvmLightLibs5.2.0lib
et45

Perciò basta aggiungerla direttamente da qui, saltando NuGet e compagnia bella. In questo modo la Common Service Locator è automaticamente esclusa, non avrete più quei warning e una volta uploadato il package sullo Store avrete a che fare con un’app interamente e solamente italiana.

image

 

Vinto!

Send to Kindle
.NET World

App UWP, Cortana ed attivazione vocale

Windows 10 include l’assistente vocale Cortana, comparso per la prima volta in Windows Phone. Attraverso Cortana possiamo comandare il PC a voce, con tutta una serie di comandi predefiniti di sistema. Come sviluppatori Universal Windows Platform, inoltre, possiamo configurare tutta una serie di frasi che il sistema operativo può riconoscere. Quando lo fa, attiva la nostra app, e la nostra app può intercettare questo evento e reagire di conseguenza.

Vediamo passo passo come ottenere questo obiettivo.

Implementazione
La prima cosa da fare è modificare il file App.xaml.cs, e nell’override del metodo OnLaunched aggiungere le righe seguenti:

Task.Run(async () =>
{
var storageFile = await StorageFile.GetFileFromApplicationUriAsync
(new Uri("ms-appx:///VoiceCommands.xml"));
await VoiceCommandDefinitionManager.
InstallCommandDefinitionsFromStorageFileAsync(storageFile);
});

In pratica, viene utilizzato il file VoiceCommands.xml – ovviamente incluso nel progetto come Content e “Copy if newer” – e registrato a livello di sistema operativo. In questo modo, Windows 10 riconoscerà le frasi specificate nel file XML, anche quando la nostra app UWP è chiusa.

Come è composto questo file XML? Ecco un esempio:

<?xml version="1.0" encoding="utf-8" ?>
<VoiceCommands xmlns="http://schemas.microsoft.com/voicecommands/1.1">
<CommandSet xml:lang="it-IT" Name="VoiceCommands">
<CommandPrefix> Nome App, </CommandPrefix>
<Example> Chi ha vinto la medaglia di bronzo ? </Example>
<Command Name="medal">
<Example> Chi ha vinto la medaglia di bronzo ? </Example>
<ListenFor> Chi ha vinto la medaglia di {medal} </ListenFor>
<Feedback> Chi ha vinto la medaglia di {medal} </Feedback>
<Navigate/>
</Command>
<PhraseList Label="medal">
<Item> bronzo </Item>
<Item> argento </Item>
<Item> oro </Item>
</PhraseList>
</CommandSet>
</VoiceCommands>

Grazie a questo XML, possiamo chiedere a Windows 10 ed alla nostra app chi ha vinto un certo tipo di medaglia. Il riconoscimento di questa frase – ripeto – avviene a livello di sistema operativo. Una volta eseguite le righe di codice C# indicate qui sopra, possiamo:

  1. Premere Win+C per avviare Cortana in modalità ascolto (oppure attivare Cortana con la voce pronunciando “Hey Cortana”, se questa funzionalità è stata preventivamente configurata ed attivata)
  2. Pronunciare ad esempio “Nome App, Chi ha vinto la medaglia di argento?”

Windows riconosce la frase ed avvia automaticamente l’app. Quando la nostra app viene attivata da un comando vocale come in questo caso, si passa dal metodo OnActivated della classe App (e non da OnLaunched) che si trova in App.xaml.cs.

protected override void OnActivated(IActivatedEventArgs e)
{
if (e.Kind == ActivationKind.VoiceCommand)
{

}
}

Il parametro e espone una proprietà Kind che ci dice il tipo di attivazione a cui siamo soggetti. Quando è di tipo ActivationKind.VoiceCommand, sappiamo che si tratta di un comando vocale. Giunti a questo punto, possono esserci due strade differenti:

  1. la nostra app è già aperta in foreground
  2. la nostra app è chiusa

Anche nel caso (1) l’esecuzione passa sempre e comunque da OnActivated. Nel caso (2), invece, la nostra app è completamente chiusa, perciò sta a noi decidere quale page visualizzare. Questo è il codice che ho utilizzato in una mia app reale:

protected override void OnActivated(IActivatedEventArgs e)
{
if (e.Kind == ActivationKind.VoiceCommand)
{
var commandArgs = e as VoiceCommandActivatedEventArgs;
var speechRecognitionResult = commandArgs.Result;
var textSpoken = speechRecognitionResult.Text;

if (this.rootFrame == null)
{
// App chiusa
rootFrame = new Frame();
Window.Current.Content = rootFrame;
this.rootFrame.Navigate(typeof(MainPage), textSpoken);
Window.Current.Activate();
}

// Invio un messaggio che verrà intercettato dal MainViewModel
// sia ad app già aperta, che appena avviata dal comando vocale
Messenger.Default.Send<TellMeMedalWinnerMessage>
(new TellMeMedalWinnerMessage(textSpoken));
}
}

Notare che ho spostato la dichiarazione dell’oggetto rootFrame a livello di classe, al contrario di quello che fa il template di base, che invece lo dichiara in modo locale al metodo OnLaunched.

Se l’oggetto rootFrame è null, significa che l’app era chiusa, quindi creo il frame, scateno la navigazione, imposto Window.Current.Content, etc. etc. Se invece rootFrame è diverso da null, significa che la mia app è già aperta. Per evitare di scrivere tutta la logica di riconoscimento in App.xaml.cs – davvero brutto – ho adottato questa soluzione: sia in un caso che nell’altro, grazie al Messenger di MvvmLight spedisco un messaggio di tipo TellMeMedalWinnerMessage. Questo è un passaggio molto importante. Il messaggio viene di conseguenza intercettato dal viewmodel che governa la UI, in questo modo:

// Nel costruttore
Messenger.Default.Register<TellMeMedalWinnerMessage>(this, GetMedalWinner);

// Funzione
private async void GetMedalWinner(TellMeMedalWinnerMessage message)
{
// invoco un servizio?
}

Nel costruttore del viewmodel registro quel messaggio, ed imposto il delegate da eseguire quando quel particolare messaggio viene ricevuto. La logica è tutta contenuta nel metodo GetMedalWinner: non fa altro che riceve in input un oggetto di tipo TellMeMedalWinnerMessage, che è una classe anemica implementata in questo modo:

class ScoreboardPositionMessage : BaseMessage
{
public string Phrase { get; set; }

public ScoreboardPositionMessage(string phrase)
{
this.Phrase = phrase;
}
}

Questa classe contiene solamente il testo della frase pronunciata dall’utente della nostra app. Adesso sta a noi decidere cosa farne. Possiamo elaborarla sul client Windows 10, oppure possiamo invocare un servizio remoto che elabora la stringa, consulta un database e restituisce un risultato al client.

Ci sono altre considerazioni da fare (grammatica, come gestire frasi libere oppure comandi più precisi, gestire più lingue): saranno tema di un prossimo post che scriverò nei prossimi giorni.

Alla prossima!!

Send to Kindle
.NET World

Impostazioni di un’app UWP

Ho scritto in questi giorni una semplice classe SettingsHelper che permette di leggere od impostare un valore di un setting relativamente ad una Universal Windows App per Windows 10.

Il codice è il seguente:

static class SettingsHelper
{
public static T GetValue<T>(string name, T defaultValue)
{
return GetValue<T>(ApplicationData.Current.LocalSettings, name, defaultValue);
}

public static T GetValue<T>(ApplicationDataContainer container, string name, T defaultValue)
{
object obj = null;
T result = default(T);

bool canRead = container.Values.TryGetValue(name, out obj);

if (canRead)
{
result = (T)obj;
}

return result;
}

public static void SetValue(string name, object value)
{
ApplicationData.Current.LocalSettings.Values[name] = value;
}

public static void SetValue(ApplicationDataContainer container, string name, object value)
{
container.Values[name] = value;
}
}

L’utilizzo è molto semplice. E’ statica e quindi non va istanziata. Dal punto di vista del consumatore della classe la cosa avviene in modo molto trasparente. Se un certo setting esiste, allora ne viene restituito l’effettivo valore, altrimenti viene restituito un valore di default preventivamente passato in input all’atto della chiamata. Ad esempio:

// Get
var autoUpdate = SettingsHelper.GetValue<bool>("AutoUpdate", false);
int page = SettingsHelper.GetValue<int>("Page", -1);
int hs = SettingsHelper.GetValue<int>(
ApplicationData.Current.RoamingSettings,
"HighScore", 0);
SettingsHelper.GetValue<DateTime>(
ApplicationData.Current.RoamingSettings,
"LastUpdate", DateTime.MinValue);

// Set
SettingsHelper.SetValue("AutoUpdate", true);
SettingsHelper.SetValue("Page", 9);
SettingsHelper.SetValue(ApplicationData.Current.RoamingSettings, "OrderCriteria", "4");
SettingsHelper.SetValue(ApplicationData.Current.RoamingSettings, "AutoSave", true);

L’utilizzo dei generics evita casting sparsi un po’ ovunque. E grazie all’utilizzo di questa SettingsHelper possiamo evitarci l’utilizzo di TryGetValue, potenziali valori null nel caso in cui un certo setting non esiste, if e test.

Ho scritto anche degli overload sia per il SetValue che per il GetValue, perchè di default la classe accede ai LocalSettings dell’app, mentre così è possibile accedere anche ai RoamingSettings.

Chiaccherata sui LocalSettings e RoamingSettings

Colgo l’occasione per ricordare che i LocalSettings sono impostazioni locali dell’app, salvate sul dispositivo Windows che stiamo utilizzando. I RoamingSettings invece sono impostazioni dell’app che vengono automaticamente replicate su tutti gli altri nostri device a cui accediamo con lo stesso Microsoft Account. Diciamo che possiamo definire i LocalSettings come impostazioni per-device, mentre i RoamingSettings sono per-user.

A cosa possono servire? Nelle app che ho pubblicato negli anni scorsi, per Windows Phone e Windows, ho utilizzato i LocalSettings per memorizzare tutti quei settings che possono cambiare da un device all’altro, chiaramente per la stessa app. Ad esempio, impostazioni relative a grafica o performance. Vi faccio un esempio. Supponiamo di avere un’app che permetta di vedere la traccia di un volo aereo; questa traccia può essere più o meno precisa in base alla densità dei punti che si decide di scaricare da Internet e successivamente di renderizzare sullo schermo. Quindi su un device a basse performance potrei decidere di sacrificare precisione ed accuratezza per avere più velocità e consumi minori. Su un desktop potrei decidere invece di vedere la traccia di volo con la massima precisione possibile. I LocalSettings sono proprio lo strumento più adatto per scenari di questo tipo: supponendo che la precisione sia espressa con un setting chiamato “FlightLogTraceAccuracy”, di tipo int, questo andrebbe memorizzato nei LocalSettings, in modo che su smartphone abbia un valore, sul desktop un altro. Oppure, ancora, l’utente potrebbe decidere di attivare le push notification su un device mentre su un altro no, e quindi ancora una volta i LocalSettings sono il posto migliore.

Nei RoamingSettings, d’altro canto, andrei a memorizzare tutti questi settings indipendenti dal device, ma associati all’app. Ad esempio, il mio nome, la mia data di nascita, il peso, il punteggio massimo di un gioco, e così via. Indipendentemente dal device che sto utilizzando, quelle informazioni sono sempre le stesse.

Send to Kindle
.NET World

La parte server dell’invio di una push notification con una tile ed un’immagine dinamica

Nei giorni scorsi mi sono imbattuto in un problema che mi ha fatto perdere tantissimo, ma davvero tantissimo tempo, problema che non ho visto risolto da nessuna parte. Spero con questo articolo di aiutare tutti coloro che in futuro potranno ricadere nello stesso scenario.

Sto parlando di inviare tramite push notification una nuova tile relativa ad un’app Windows 10, quindi UWP. Ma facciamo un passo indietro.

Il blocco XML più semplice che un server deve inviare al client per poter aggiornare una tile è:

<tile>
<visual version="2">
<binding template="TileWide310x150Image">
<image id="1" src="http://server/nomeimmagine.png"/>
</binding>
</visual>
</tile>

Ci sono tanti template da poter utilizzare, chiaramente. Questo è il template TileWide310x150Image, che invia al client le informazioni minime per aggiornare solo la tile rettangolare 310×150. Il parametro src indicato nell’XML è semplicemente l’url della nuova immagine. Nulla di particolarmente complicato. Invio un’immagine ogni volta diversa, e quindi la tile su ogni client si aggiorna.

Il problema arriva quando invece di puntare ad un’immagine fissa (jpg o png, per esempio), voglio puntare un qualche url che invece è una Action di ASP.NET MVC, Action che restituisce un’immagine sotto forma, ovviamente, di binario.

Immaginate di avere ad esempio un codice server simile al seguente:

public ActionResult RandomBackground()
{
MemoryStream ms = new MemoryStream();
Image image = new Bitmap(320, 240);

using (Graphics drawing = Graphics.FromImage(image))
{
Random rnd = new Random((int)DateTime.Now.Ticks);
int r = rnd.Next(0, 256);
int g = rnd.Next(0, 256);
int b = rnd.Next(0, 256);
Color c = Color.FromArgb(255, r, g, b);
drawing.Clear(c);

image.Save(ms, System.Drawing.Imaging.ImageFormat.Png);
}

return File(ms.ToArray(), "image/png");
}

Questa è una Action di ASP.NET MVC, quindi stiamo ragionando sulla parte server dell’invio di una push notification. Questa Action risponde ad un url simile al seguente…

http://server/controller/RandomBackground

e non fa altro che restituire ogni volta un’immagine 320×240 di colore diverso, ad ogni colpo di F5. Supponiamo quindi di inviare una push notification come questa:

<tile>
<visual version="2">
<binding template="TileWide310x150Image">
<image id="1" src="http://server/controller/RandomBackground"/>
</binding>
</visual>
</tile>

Sui dispositivi Windows 8 / 8.1 / 10 questa push notification ha un comportamento ben particolare. La prima volta la notifica arriva, il device la riceve ed aggiorna la tile. Fin qua nulla di strano. Dalla seconda volta in poi la tile non si aggiorna più. Perchè? Deduco, dopo infinite prove & test, perchè l’url della tile è sempre lo stesso, e quindi il device reputa che sia inutile aggiornarla, perchè interpreta che sia sempre la stessa tile. Peccato, per il ragionamento di cui sopra, che in realtà l’url punti ad un contenuto dinamico generato server-side, e quindi DEVE aggiornarla, anche se l’url è lo stesso.

Sono inoltre giunto alla conclusione che il meccanismo di ricezione di push notification sul client ignori tutti i parametri passati in querystring, così url come questi sono esattamente gli stessi:

http://server/controller/RandomBackground?index=534543
http://server/controller/RandomBackground?a=blablabla
http://server/controller/RandomBackground?guid=589353985u39835u49

Per risolvere, bisogna effettivamente puntare ad un url differente. La firma della Action insomma deve diventare qualcosa del genere:

public ActionResult RandomBackground(string id)
{
// codice
}

Il parametro id non ci serve, ma a questo punto noi possiamo invocare l’Action in questo modo:

http://server/controller/RandomBackground/34
http://server/controller/RandomBackground/aaaa
http://server/controller/RandomBackground/fkldglkdfm34
http://server/controller/RandomBackground/;ert;54353

Gli url qui puntano alla Action RandomBackground, passando diversi valori nel parametro id, valori che vengono bellamente ignorati, ma che semplicemente servono a scatenare effettivamente l’aggiornamento della tile. Cosa significa questo? Significa che server-side, in fase di preparazione dell’XML da inviare a ciascun client, l’url deve essere generato ogni volta diverso, quindi giocare con System.Random per fare una cosa del genere:

string tileUri = string.Format("http://server/controller/RandomBackground/{1}",
new Random((int)DateTime.Now.Ticks).Next(1, 999999));

L’url della tile, insomma, viene generato random ogni volta, mandato al client, che lo aggiorna senza farsi troppe domande. Ok, primo step risolto.

Ulteriori complicazioni

Fin qua, volendo, è tutto semplice, perchè la Action RandomBackground che abbiamo ipotizzato negli esempi qui sopra non ha bisogno di veri parametri di input. Supponiamo che la generazione della tile abbia a che fare con il meteo, e che la Action debba restituire l’immagine di una tile contenente le previsioni della provincia passata in input fra i parametri. Ad esempio:

http://server/controller/Meteo/Lodi
http://server/controller/Meteo/Roma
http://server/controller/Meteo/Lecce
http://server/controller/Meteo/Ancona

Ma torniamo ai discorsi di prima. Se inviassi questo url ad un device Windows 10, la prima volta la tile verrebbe aggiornata, dalla seconda volta in poi no. E quindi dobbiamo generare url ogni volta sempre diversi, accodando un secondo parametro fittizio come qui sotto:

http://server/controller/Meteo/Lodi/5437534
http://server/controller/Meteo/Roma/989
http://server/controller/Meteo/Lecce/1123
http://server/controller/Meteo/Ancona/9043

Ovviamente i numeri sono sparati a caso. Per ottenere questo, bisogna modificare le regole di routing di ASP.NET MVC, per indicare che nell’url possiamo indicare un parametro aggiuntivo opzionale. Quindi quello che ho fatto è stato andare nel file RouteConfig.cs e scrivere quanto segue:

public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");

routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}/{key}",
defaults: new { controller = "Home", action = "Index",
id = UrlParameter.Optional,
key = UrlParameter.Optional }
);
}

Cioè: tutti gli url possono contenere un parametro {key} opzionale. Di conseguenza, la firma della nostra Action Meteo diventa:

public ActionResult Meteo(string id, string key)
{
// Codice
}

All’interno del parametro id avremmo la provincia passata. All’interno del parametro key arriverebbe un secondo parametro, generato ogni volta casualmente, bellamente ignorato, inserito al solo scopo di avere un url ogni volta differente.

Conclusioni

Direi che è tutto. Bel problema, non c’è che dire.

Finalmente risolto, e le mie tile arrivano puntuali e precise.

Send to Kindle
.NET World

App Control Panel per UWP

Seguendo il gruppo Sviluppatori Windows Italia su Facebook, mi sono ritrovato nell’intento di voler ricreare una specie di Pannello di Controllo di Windows 10, applicando Adaptive UI per fare in modo che la visualizzazione cambi da griglia a lista, esattamente come fa il pannello di controllo nativo del sistema operativo. Perciò ho creato questo. Che ne dite?

Vedete come ridimensionando la finestra, la griglia si adatti in modo opportuno, e quando la dimensione della finestra scende al di sotto dei 600 pixel, la visualizzazione passa in modalità lista.

Passiamo alla parte tecnica. La visualizzazione a griglia è rappresentata dal seguente XAML:

   1: <GridView ItemsSource="{Binding Items}" VerticalAlignment="Center"

   2:     HorizontalAlignment="Center" x:Name="ControlPanelGrid">

   3:     <GridView.ItemsPanel>

   4:         <ItemsPanelTemplate>

   5:             <ItemsWrapGrid Orientation="Horizontal" />

   6:         </ItemsPanelTemplate>

   7:     </GridView.ItemsPanel>

   8:     <GridView.ItemTemplate>

   9:         <DataTemplate>

  10:             <Grid Width="200" Height="200" Padding="16" Margin="2">

  11:                 <Grid.RowDefinitions>

  12:                     <RowDefinition Height="Auto" />

  13:                     <RowDefinition Height="Auto" />

  14:                     <RowDefinition Height="Auto" />

  15:                 </Grid.RowDefinitions>

  16:                 <TextBlock Text="{Binding Icon}" Foreground="Olive" FontFamily="Segoe UI Symbol"

  17:                            FontSize="50" FontWeight="ExtraLight" TextAlignment="Center"

  18:                            TextWrapping="Wrap" Grid.Row="0" VerticalAlignment="Bottom" />

  19:                 <TextBlock Text="{Binding Name}" Foreground="Black" FontSize="18"

  20:                            FontWeight="ExtraLight" TextAlignment="Center" TextWrapping="Wrap"

  21:                            Grid.Row="1" VerticalAlignment="Bottom" />

  22:                 <TextBlock Text="{Binding Description}" Foreground="DarkGray"

  23:                            FontWeight="ExtraLight" FontSize="14" TextAlignment="Center"

  24:                            TextWrapping="Wrap" Grid.Row="2" VerticalAlignment="Top" />

  25:             </Grid>

  26:         </DataTemplate>

  27:     </GridView.ItemTemplate>

  28: </GridView>

La visualizzazione a lista invece è la seguente:

   1: <GridView ItemsSource="{Binding Items}" x:Name="ControlPanelList"

   2:           HorizontalContentAlignment="Stretch">

   3:     <GridView.ItemTemplate>

   4:         <DataTemplate>

   5:             <Grid Margin="2" HorizontalAlignment="Stretch" Padding="4" BorderThickness="1">

   6:                 <Grid.ColumnDefinitions>

   7:                     <ColumnDefinition Width="60" />

   8:                     <ColumnDefinition />

   9:                 </Grid.ColumnDefinitions>

  10:                 <Grid.RowDefinitions>

  11:                     <RowDefinition />

  12:                     <RowDefinition />

  13:                 </Grid.RowDefinitions>

  14:                 <TextBlock Grid.RowSpan="2" Text="{Binding Icon}" Foreground="Olive"

  15:                            FontFamily="Segoe UI Symbol" FontWeight="ExtraLight" FontSize="32"

  16:                            Grid.Row="0" VerticalAlignment="Bottom" Margin="8 0 0 0" />

  17:                 <TextBlock Text="{Binding Name}" Grid.Column="1" Foreground="Black"

  18:                            FontWeight="ExtraLight" FontSize="20" Grid.Row="0"

  19:                            VerticalAlignment="Bottom" Margin="8 0 0 0" />

  20:                 <TextBlock Text="{Binding Description}" Grid.Column="1" Foreground="DarkGray"

  21:                            FontWeight="ExtraLight" FontSize="16" TextWrapping="Wrap"

  22:                            Grid.Row="1" VerticalAlignment="Top" Margin="8 0 0 0" />

  23:             </Grid>

  24:         </DataTemplate>

  25:     </GridView.ItemTemplate>

  26:     <GridView.ItemsPanel>

  27:         <ItemsPanelTemplate>

  28:             <ItemsStackPanel Orientation="Vertical" />

  29:         </ItemsPanelTemplate>

  30:     </GridView.ItemsPanel>

  31:     <GridView.ItemContainerStyle>

  32:         <Style TargetType="GridViewItem">

  33:             <Setter Property="HorizontalContentAlignment" Value="Stretch" />

  34:         </Style>

  35:     </GridView.ItemContainerStyle>

  36: </GridView>

Notare che in ambedue i casi si tratta sempre di una GridView, solo che nel secondo caso (modalità lista) ho cambiato il pannello di default, impostandolo su:

   1: <GridView.ItemsPanel>

   2:     <ItemsPanelTemplate>

   3:         <ItemsStackPanel Orientation="Vertical" />

   4:     </ItemsPanelTemplate>

   5: </GridView.ItemsPanel>

In questo modo, pur trattandosi di una GridView, in realtà gli Items internamente vengono visualizzati con uno StackPanel, e quindi verticalmente. Perchè questo?

L’effetto di “OnMouseOver” di una GridView renderizza il bordo dell’elemento, come si vede nel video, e questo è proprio l’effetto che fa il pannello di controllo di Windows, ed è quello che volevo. La ListView, invece, renderizza il backgrund dell’elemento, e non è l’effetto che cercavo. Quindi: GridView in tutti e due i casi, solo che la modalità a lista utilizza uno StackPanel verticale. Più semplice di così si muore.

Ma arriviamo al sodo. Come ho utilizzato gli AdaptiveTrigger per switchare da una modalità all’altra? Con questo blocco di XAML:

   1: <VisualStateManager.VisualStateGroups>

   2:     <VisualStateGroup x:Name="VisualStateGroup">

   3:         <VisualState x:Name="VisualStateNarrow">

   4:             <VisualState.StateTriggers>

   5:                 <AdaptiveTrigger MinWindowWidth="0" />

   6:             </VisualState.StateTriggers>

   7:             <Storyboard>

   8:                 <DoubleAnimation Duration="0"

   9:                     Storyboard.TargetProperty="Opacity"

  10:                     Storyboard.TargetName="ControlPanelGrid"

  11:                     To="0" d:IsOptimized="True" />

  12:                 <ObjectAnimationUsingKeyFrames Duration="0"

  13:                     Storyboard.TargetProperty="(UIElement.IsHitTestVisible)"

  14:                     Storyboard.TargetName="ControlPanelGrid">

  15:                     <DiscreteObjectKeyFrame KeyTime="0" Value="False" />

  16:                 </ObjectAnimationUsingKeyFrames>

  17:             </Storyboard>

  18:         </VisualState>

  19:         <VisualState x:Name="VisualStateWide">

  20:             <VisualState.StateTriggers>

  21:                 <AdaptiveTrigger MinWindowWidth="600" />

  22:             </VisualState.StateTriggers>

  23:             <Storyboard>

  24:                 <DoubleAnimation Duration="0"

  25:                     Storyboard.TargetProperty="Opacity"

  26:                     Storyboard.TargetName="ControlPanelList"

  27:                     To="0" d:IsOptimized="True" />

  28:                 <ObjectAnimationUsingKeyFrames Duration="0"

  29:                     Storyboard.TargetProperty="(UIElement.IsHitTestVisible)"

  30:                     Storyboard.TargetName="ControlPanelList">

  31:                     <DiscreteObjectKeyFrame KeyTime="0" Value="False" />

  32:                 </ObjectAnimationUsingKeyFrames>

  33:             </Storyboard>

  34:         </VisualState>

  35:     </VisualStateGroup>

  36: </VisualStateManager.VisualStateGroups>

Nel Visual State Manager ho definito due viste: VisualStateNarrow e VisualStateWide.

La prima interviene con una Width minima di 0 pixel, ed attiva la visualizzazione degli elementi a lista, quindi modalità verticale. La seconda interviene con una Width minima di 600 pixel, ed attiva la visualizzazione degli elementi a griglia. Due note importanti:

  • per visualizzare o nascondere utilizzo la proprietà Opacity delle due GridView
  • di conseguenza ho dovuto impostare anche IsHitTestVisible a False, per fare in modo che un oggetto che nel visual tree compare “sopra” un altro, in realtà non partecipi all’intercettazione degli eventi di input. Uao, che frase complicata. Se non andassi ad impostare IsHitTestVisible a False, il controllo ControlPanelGrid non funzionerebbe come voluto, perchè davanti ci sarebbe il ControlPanelList (nonostante la sua Opacity a 0). Non è frutto del mio sacco, ho visto l’utilizzo di questa proprietà in Template 10, ed in effetti è una bella comodità

Direi che è tutto.

Il codice sorgente di questo progetto è su GitHub, e lo trovate qui.

Send to Kindle
.NET World

UWP e la Adaptive UI

Dunque, partiamo da qualche presupposto tecnologico.

Con Windows 8 e Windows 8.1 TUTTE le Windows Store App venivano disegnate per essere visualizzate “solo” in full-screen. Il “solo” l’ho messo tra virgolette perchè c’era un altra casistica: quando le app venivano snappate sul lato sinistro o destro dello schermo. E siccome stiamo parlando di app per Windows bisognava pensarle per tutte le risoluzioni possibili ed immaginabili. E bisognava pensarle sia in landscape che portrait. In ogni caso, l’utente non aveva la possibilità di ridimensionare l’app, perchè di fatto non c’era alcuna finestra con nessun bordo.

Con Windows 10 le cose sono un po’ diverse. Innanzitutto esiste la Modalità Tablet, che se attivata fa agire le nuove Universal Windows App (UWP) esattamente come sotto Windows 8/8.1. Partono in full-screen, possiamo snapparle, etc. etc. Quando la Modalità Tablet è spenta, le UWP vengono inserite in una finestra, al punto che l’utente visivamente non le distingue più dalle classiche applicazioni desktop. L’utente può ridurre ad icona l’app, può spostare la finestra e soprattutto può ridimensionarla.

E qui arriva il tema della Adaptive UI offerta dall’SDK di Windows 10. Non è scopo di questo post raccontare tutti gli aspetti della Adaptive UI, anche perchè io stesso ho ancora un po’ di confusione in testa. Lo scopo di per sè è piuttosto semplice:

Permettere ad una Universal Windows App di adattare il proprio contenuto in base a risoluzione & dimensione.

Ricordo, punto fondamentale, che un’app UWP gira ovunque sia presente Windows 10: desktop, portatile, mega-schermo, in futuro smartphone, XBOX, e via dicendo. Quindi, sostanzialmente, significa adattare il contenuto in base alla risoluzione/dimensione e fare in modo che il tutto sia sempre fruibile nel migliore dei modi. E quindi parliamo di schermi che vanno dal 10” di un ultrabook al 22” di un desktop, al 5” di uno smartphone (e la cosa divertente è che magari la risoluzione è sempre Full-HD).

Ecco una serie di articoli che trattano meglio questo argomento:

A Developer’s Guide to Windows 10: (07) Adaptive UI

Windows 10 Universal Apps – Adaptive Triggers

Template 10

Parlando di Template 10 mi sono guardato e studiato questa MainPage.xaml inserita nel progetto Sample. E’ un ottimo esempio di come la view possa reagire in base alla dimensione della finestra.

snip

Analizzando questa view, da cui sono partito, vi propongo una mia soluzione, che illustro in questo post. Esso utilizza ovviamente l’SDK di UWP, AdaptiveTrigger, Model-View-ViewModel con MvvmLight, e qualche altra nozione indispensabile.

Introduzione
Supponiamo di avere un’app UWP che mostra un elenco di automobili.

Ecco la nostra “bellissima” UI:

image

Qui abbiamo un MainViewModel che governa l’intera interfaccia. Questo viewmodel espone una proprietà Cars di tipo ObservableCollection<Car>. I dati sono stati inseriti manualmente nel costruttore. La UI è composta da un Hub con 3 HubSection al suo interno, uguali uno all’altro, sia nell’aspetto che nei dati visualizzati:

<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"
    DataContext="{StaticResource vm}">
    <Hub Grid.Row="1" x:Name="HorizontalView">
        <HubSection x:Name="CarsList1" Header="car list 1" Width="512"
                    HorizontalContentAlignment="Stretch"
                ContentTemplate="{StaticResource CarsListTemplateWide}" />
        <HubSection x:Name="CarsList2" Header="car list 2" Width="512"
                HorizontalContentAlignment="Stretch"
                ContentTemplate="{StaticResource CarsListTemplateWide}" />
        <HubSection x:Name="CarsList3" Header="car list 3" Width="512"
                HorizontalContentAlignment="Stretch"
                ContentTemplate="{StaticResource CarsListTemplateWide}" />
    </Hub>
</Grid>

Il contenuto di ciascun HubSection è prelevato dal DataTemplate CarsListTemplateWide.

In questo momento la nostra UI non è assolutamente adattiva. Se lanciamo l’app e ridimensioniamo la finestra, la UI non reagisce in alcun modo: al massimo ottenete ScrollBar verticali o orizzontali per permettere all’utente di scorrere e vedere tutto il contenuto.

Ciò che ci permettono di fare gli Adaptive Trigger di UWP è quello di intercettare i cambiamenti delle dimensioni e reagire in modo opportuno. Il tutto in modo dichiarativo usando lo XAML, senza scrivere alcuna linea di codice C#.

Concettualmente, quindi, possiamo andare a cambiare il ContentTemplate di ciascuno di quei 3 HubSection, switchando da CarsListTemplateWide a, per esempio, CarsListTemplateSmall. E supponiamo di adattare la UI dicendo: se la nostra finestra si stringe fino ad un certo limite, allora facciamo in modo che l’elenco delle automobili si adatti, rimuovendo alcune informazioni che decidiamo essere superflue.

Questa è la seconda visualizzazione che implementa questa logica. Stringendo la finestra, due colonne ad un certo punto scompaiono (quelle relative al modello e all’anno di immatricolazione).

image

Questo grazie al seguente blocco di XAML:

<VisualStateManager.VisualStateGroups>
	<VisualStateGroup x:Name="VisualStateGroup">
		<VisualState x:Name="VisualStateWide">
			<VisualState.StateTriggers>
				<AdaptiveTrigger MinWindowWidth="600" />
			</VisualState.StateTriggers>
			<VisualState.Setters>
				<Setter Target="CarsList1.ContentTemplate"
					Value="{StaticResource CarsListTemplateSmall}" />
				<Setter Target="CarsList2.ContentTemplate"
					Value="{StaticResource CarsListTemplateSmall}" />
				<Setter Target="CarsList3.ContentTemplate"
					Value="{StaticResource CarsListTemplateSmall}" />
				<Setter Target="CarsList1.Width" Value="300" />
				<Setter Target="CarsList2.Width" Value="300" />
				<Setter Target="CarsList3.Width" Value="300" />
			</VisualState.Setters>
		</VisualState>
		<VisualState x:Name="VisualStateVeryWide">
			<VisualState.StateTriggers>
				<AdaptiveTrigger MinWindowWidth="1024" />
			</VisualState.StateTriggers>
			<VisualState.Setters>
				<Setter Target="CarsList1.ContentTemplate"
					Value="{StaticResource CarsListTemplateWide}" />
				<Setter Target="CarsList2.ContentTemplate"
					Value="{StaticResource CarsListTemplateWide}" />
				<Setter Target="CarsList3.ContentTemplate"
					Value="{StaticResource CarsListTemplateWide}" />
				<Setter Target="CarsList1.Width" Value="512" />
				<Setter Target="CarsList2.Width" Value="512" />
				<Setter Target="CarsList3.Width" Value="512" />
			</VisualState.Setters>
		</VisualState>
	</VisualStateGroup>
</VisualStateManager.VisualStateGroups>

Vengono definiti 2 VisualState: uno è VisualStateWide, l’altro è VisualStateVeryWide. Il primo è valido dai 600 pixel in su, il secondo dai 1024 pixel in su. Giocando con i Setter del Visual State Manager vado a cambiare alcune proprietà degli oggetti sulla UI, principalmente due:

  • la larghezza degli HubSection (300 pixel oppure 512)
  • Il ContentTemplate degli HubSection (CarsListTemplateSmall oppure CarsListTemplateWide)

Il Setter agisce in modo immediato, e la UI si aggiorna come stabilito.

Conclusioni

Questa soluzione è molto carina da vedere in esecuzione. In forma statica, qui sul blog, rende molto poco l’idea. E soprattutto ha molto più senso in un’applicazione reale complessa, con molte informazioni da mostrare: in un esempio semplice come questo non risaltano bene le potenzialità. Pian piano, forse, l’Adaptive UI mi entra in testa!

Send to Kindle
.NET World

SignalR e notifiche di allarmi su diversi tipi di client, desktop e mobile

Introduzione
E’ da molto tempo che volevo studiare, almeno superficialmente, SignalR. Sebbene non segua molto le tecnologie Web, ogni tanto nella mia testa suona una specie di campanello di allarme (o di notifica, meglio), che mi dice: “Ok, quella cosa riguarda il Web, ma sento che potrebbe essere utile anche a me“. Anche perchè, parliamoci chiaro, io del Web odio solamente l’HTML ed i browser: la parte più stupida del client, insomma: passatemi questa definizione. SignalR è uno di quei topic che hanno fatto suonare quel campanello, senza mai trovare davvero il tempo e le risorse da dedicarci. Fino alla settimana scorsa, quando mio fratello Omar, improvvisamente, senza alcun input da parte mia, mi manda un bel sample proprio su SignalR. Uno di quei sample perfetti, di quelli che apri con Visual Studio, premi F5, compila e parte tutto, capisci alla perfezione, e poi passi ad analizzare il codice. Ecco, è andata proprio così. E quindi, grazie a quel sample ed all’intervento di mio fratello, è nato questo post.

Di cosa parliamo?
La settimana scorsa mi sono messo ad implementare un piccolo prototipo, che poi ho pubblicato sul mio account GitHub, che implementa la seguente struttura:

WP_20150902_10_26_09_Pro

In parole povere, abbiamo i seguenti componenti:

  • un device fisico, rappresentato a sinistra, cioè un dispositivo di qualche tipo (un sensore di temperatura? un frigorifero? un’auto? una caldaia? oppure…un razzo?). Questo dispositivo, tra le altre cose, è in grado di rilevare i suoi allarmi interni (temperatura alta, fuori traiettoria, perdita liquido refrigerante, motore fuori servizio), e di comunicarli a qualcuno
  • questo “qualcuno” è un dispatcher, rappresentato al centro, che nel mio caso è un server implementato con SignalR, server che ho pubblicato su Azure, quindi raggiungibile in modo globale. Qual è il suo compito? Ogni volta che riceve un allarme, avvisa tutti i client che risultano connessi, inviando loro un messaggio (leggesi: una classe DTO)
  • I client di qualsiasi tipo, rappresentati a destra, ricevono il messaggio ed avvisano l’utente nel modo più consono ed opportuno. Ciascun client all’avvio si collega al server SignalR implementato nel punto precedente di questo elenco

Trovate qui il progetto: https://github.com/VivendoByte/AlarmNotifier.

E’ importante che la cosa sia il più possible in real-time, ed è per questo che ho deciso di studiare e di passare attraverso SignalR.

Qualche dettaglio tecnico in più
Diamo un’occhiata al Solution Explorer che vi trovate nel momento in cui aprite la solution che ho pubblicato. Eccolo qui:

solution_explorer

  • C’è una solution folder denominata Client, al cui interno ho messo 4 tipi diversi di progetto: Console, Universal App per Windows 10, Windows Phone 8.1 e classica applicazione desktop in WPF. Questi progetti rappresentano i tipi diversi di client che possono ricevere la notifica di allarme
  • C’è una solution folder denominata Dispatcher, al cui interno ho messo 2 tipi diversi di progetto: Console e Web. Questi due progetti rappresentano il dispatcher degli allarmi. Uno è console e quindi apre un server SignalR sul vostro PC. L’altro è un progetto di tipo Web, ed è lo stesso che ho pubblicato su Azure
  • Infine, ci sono altri due progetti: uno è VivendoByte.AlarmNotifier.Messages, una class library di tipo Portable, che contiene solo una piccola classe DTO per poter inviare e ricevere il messaggio. L’altro progetto è VivendoByte.AlarmNotifier.RocketDevice, che rappresenta il nostro device fisico capace di generare allarmi: è un po’ una cosa brutta, effettivamente, ma l’ho implementato come applicazione WPF, così è possibile premere un bottone e simulare lo scatenarsi di un allarme

Parte Server – Il Dispatcher
Vediamo per prima cosa la parte server di SignalR. il punto focale è l’Hub, che io ho chiamato NotifierHub e che ho implementato come segue:

[sourcecode language='csharp'  padlinenumbers='true']
public class NotifierHub : Hub
{
	public void RaiseAlarm(AlarmMessage notification)
	{
		Clients.All.SendNotification(notification);
	}
	public override Task OnConnected()
	{
		return base.OnConnected();
	}

	public override Task OnDisconnected(bool stopCalled)
	{
		return base.OnDisconnected(stopCalled);
	}
}
[/sourcecode]

Il metodo RaiseAlarm che vedete implementato qui sopra viene invocato dal device (lo vediamo dopo), nel nostro caso il razzo. Ogni volta che il razzo segnala un allarme, si entra nel metodo RaiseAlarm; l’istanza di AlarmMessage che arriva come parametro contiene tutti i dettagli sull’allarme: data/ora, gravità, descrizione, eccetera. Quello che fa SignalR è rigirare l’allarme a tutti i client che risultano connessi in quel momento, con quella semplice linea di codice. Il dispatcher fa solo questo: riceve un allarme e lo rimanda ai client. Da notare due cose:

  • la proprietà All è di tipo dynamictooltip
  • il metodo SendNotification, che qui chiamiamo internamente al dispatcher, viene poi invocato anche sui client: lo vedremo più tardi

Il device fisico – Il Razzo

Adesso vediamo in breve il dispositivo fisico. Questo è il device capace di segnalare gli allarmi. Come dicevo prima, l’ho scritto in WPF, così ha un’interfaccia utente per poter premere un bottone e scatenare un allarme. Il code-behind è piuttosto semplice. Per prima cosa, ho implementato un metodo chiamato Connect:

[sourcecode language='csharp' ]
public MainWindow()
{
    InitializeComponent();
    this.Connect();
}

private async void Connect()
{
    Connection = new HubConnection(ServerURI);
    HubProxy = Connection.CreateHubProxy("NotifierHub");

    try
    {
        await Connection.Start();
    }
    catch (HttpRequestException)
    {
        return;
    }
}
[/sourcecode]

Questo metodo viene eseguito dal costruttore della finestra. In questo modo il nostro razzo si connette immediatamente al server SignalR, e ci mettiamo nella condizione di poter comunicare gli allarmi. Notate che il parametro del metodo CreateHubProxy, di tipo stringa, deve corrispondere al nome della classe Hub definita server-side. Quando nel razzo si scatena un allarme (ripeto: nell’esempio dobbiamo premere manualmente un bottone) accade quanto segue:

[sourcecode language='csharp' ]
private void SendAlarm_Click(object sender, RoutedEventArgs e)
{
    AlarmMessage a = new AlarmMessage();
    HubProxy.Invoke("RaiseAlarm", a);
}
[/sourcecode]

Molto semplice, direi. Costruisco un’istanza di AlarmMessage e la mando al server SignalR di cui abbiamo parlato prima. Il nome del metodo RaiseAlarm è espresso tramite una stringa, ed è quello che abbiamo visto implementato prima nel NotifierHub.

Ovviamente, in produzione un device comunicherebbe un allarme nel momento in cui si scatena realmente, e non c’è alcun utente a premere un bottone.

Parte Client – I diversi tipi di client

La parte client è l’ultima parte di tutto questo giro. Nel progetto su GitHub ne ho implementati quattro tipi diversi, qui parliamo solo della Console Application perchè è la più semplice da trattare. E’ importante dire che il codice per la connessione al server SignalR è lo stesso: diverso semmai è il comportamento che il cliente assume nel momento in cui riceve l’allarme, perchè ogni client ha un’interfaccia diversa e quindi comunica all’utente in modo diverso.

Ecco il codice:

[sourcecode language='csharp' ]
static void Main(string[] args)
{
    Connect();
}
static async void Connect()
{
    Connection = new HubConnection(ServerURI);
    HubProxy = Connection.CreateHubProxy("NotifierHub");

    HubProxy.On<AlarmMessage>("SendNotification", (notification) =>
        {
            System.Console.WriteLine(notification);
        }
    );

    try
    {
        Connection.Start();
    }
    catch (HttpRequestException)
    {
        return;
    }

    System.Console.WriteLine("Console Client started!!!");
    System.Console.ReadKey();
}
[/sourcecode]

Il metodo Connect crea un’istanza di HubProxy, specificando “NotifierHub” come parametro. Si sottoscrive alla ricezione del messaggio AlarmMessage. Il tutto viene gestito in questo caso tramite un anonymous method: quando viene ricevuto un allarme, la Console Application non fa nient’altro che scriverlo sulla console. Nel caso di applicazioni più complesse, come ad esempio un’applicazione WPF, potremmo aggiungere l’oggetto AlarmMessage ad una ListBox, oppure mostrare un balloon di notifica sullo schermo. Il primo parametro del metodo On è “SendNotification”, ed è quello che abbiamo specificato sul server SignalR; il secondo parametro è un Action<T>, dove T nel nostro caso è AlarmMessage.

Conclusioni

SignalR, come recita il sito ufficiale, è una libreria per sviluppatori ASP.NET in grado di implementare con poco sforzo una comunicazione real-time alle nostre applicazioni. E per citare… “What is “real-time web” functionality? It’s the ability to have your server-side code push content to the connected clients as it happens, in real-time”. Direi che è uno scenario perfetto per la comunicazione di allarmi, ma anche per applicazioni di chat, o di videogiochi, o per tutto quello che ci passa per la testa.

Per chiudere, fate clone dal mio repository su GitHub e provate a dargli un’occhiata.

Send to Kindle
.NET World

[UWP] Gestire dimensione e posizione delle finestre in un’app Windows 10

In questi giorni sto indagando pesantemente sulle modalità di funzionamento delle finestre del mondo di Universal Windows Platform, ed ho notato che ci sono ovviamente delle pesanti differenze con WPF. Mi sono schiarito un po’ le idee, ed ho deciso di scrivere questo post per riassumere un po’ le cose.

Windows Presentation Foundation

  1. Mondo a finestre, che possono girare sia come finestre sia a pieno schermo (com’è tradizione del caro buon vecchio mondo Windows, insomma)
  2. Possiamo impostare la dimensione e la posizione di ciascuna finestra, sia attraverso lo XAML che attraverso il codice
  3. Le applicazioni WPF per loro natura non ricordano la loro posizione e la loro dimensione: se vogliamo che un’app ricordi posizione & dimensione è tutto a carico del dev dell’applicazione stessa

Universal Windows Platform

Da notare che le linee di codice che vedrete qui sotto si riferiscono alla Release Candidate di Visual Studio 2015, ed alla Build 10240 di Windows 10. Le cose potrebbero cambiare quando il tutto sarà rilasciato in versione finale il 29 Luglio.

  1. Le app UWP possono girare in finestra, esattamente come una finestra WPF
  2. Le app UWP possono girare massimizzate, esattamente come una finestra WPF
  3. Possiamo impostare Windows 10 per girare in modalità Tablet, che “costringe” tutto il desktop a funzionare a pieno schermo. Le app in questo caso si comportano come in Windows 8.1
  4. Non posso mai impostare la posizione di una finestra (nè con XAML nè con codice): lieto di essere smentito
  5. Non posso mai impostare la dimensione di una finestra attraverso lo XAML, mentre da codice sì: lieto di essere smentito
  6. Le app UWP nativamente ricordano la loro ultima dimensione e posizione. Va da sè che quando l’utente riapre l’app XYZ la finestra si riaprirà nello stesso posto
  7. Posso evitare che un’app UWP ricordi la propria dimensione/posizione giocando con la proprietà ApplicationView.PreferredLaunchWindowingMode (che può valere Auto, FullScreen oppure PreferredLaunchViewSize)

Il tutto passa attraverso l’utilizzo della classe ApplicationView.

Chiedere ad un’app UWP di andare a pieno schermo

var view = ApplicationView.GetForCurrentView();
bool result = view.TryEnterFullScreenMode();

All’interno del codice di un’app UWP possiamo chiedere al sistema operativo di andare a pieno schermo. L’operazione può riuscire oppure no: ecco il motivo per cui restituisce bool. Evitate di impostare un breakpoint in corrispondenza della chiamata a TryEnterFullScreenMode(): l’esecuzione non va come dovrebbe.

Ovviamente possiamo uscire dalla modalità full-screen nel modo seguente:

view.ExitFullScreenMode();

In questo caso non abbiamo alcun valore di ritorno.

Possiamo controllare se la nostra app sta girando in full-screen con la proprietà:

view.IsFullScreenMode

Si tratta di una proprietà in sola lettura: l’unico modo che abbiamo per switchare da una modalità all’altra è quella di passare attraverso i metodi descritti precedentemente,

Chiedere ad un’app UWP se sta girando in tablet mode

La proprietà IsFullScreenMode – come abbiamo appena detto – ci dice se l’app sta girando in pieno schermo. Se vogliamo capire se Windows 10 sta girando in tablet mode possiamo interrogare la proprietà:

view.IsFullScreen

Anche in questo caso si tratta di una proprietà in sola lettura.

Chiedere ad un’app UWP di ridimensionarsi ad una certa dimensione

Dimenticatevi di impostare le proprietà ActualWidth e ActualHeight nello XAML.

Dimenticatevi di impostare le stesse proprietà da codice.

Se in un’app UWP volete modificare la dimensione di una finestra il codice è il seguente:

var view = ApplicationView.GetForCurrentView();
Size size = new Size(640, 480);
bool result = view.TryResizeView(size);

Anche in questo caso, possiamo capire se l’operazione è andata a buon fine oppure no controllando il valore di ritorno del metodo TryResizeView. Da notare che la dimensione minima di un’app UWP è 500 x 320: lieto di essere smentito. Se tentiamo di ridimensionare la finestra a, per esempio, 499 x 320, l’operazione fallisce, e TryResizeView ci restituisce false.

Ottenere le dimensioni correnti della finestra

In questo caso le cose sono più semplici, perchè è sufficiente leggere il valore delle proprietà ActualWidth e ActualHeight, esattamente come accadeva con le tecnologie precedenti.

Capire se la finestra è dockata a destra o a sinistra

Come in Windows 8.1, anche con Windows 10 posso agganciare una finestra al lato sinistro o destro del desktop. Per farlo, devo prima attivare la modalità tablet: l’app viene di conseguenza massimizzata sullo schermo, ed a questo punto la posso trascinare a destra o a sinistra, e posso anche ridimensionarla per la sua larghezza. Da codice posso scrivere quanto segue:

var view = ApplicationView.GetForCurrentView();
bool leftSide = view.AdjacentToLeftDisplayEdge;
bool rightSide = view.AdjacentToRightDisplayEdge;

Grazie alle proprietà AdjacentToLeftDisplayEdge e AdjacentToRightDisplayEdge (entrambe in sola lettura) posso eventualmente capire in quale situazione mi trovo. Ovviamente possono valere entrambe false in caso di finestra (normale o massimizzata), mentre non potrà mai accadere che entrambe valgano true.

Ottenere o modificare il titolo della nostra applicazione

var view = ApplicationView.GetForCurrentView();
view.Title = "mio titolo";

Le righe di codice qui sopra modificano il titolo dell’app in esecuzione. C’è una particolarità: la stringa che impostiamo viene sempre messa come prefisso rispetto al titolo stabilito nel file di manifest. Non so dire se è un comportamento voluto, oppure se è un qualche problema dovuto a Visual Studio 2015 RC o alla versione corrente di Windows 10.

Send to Kindle
.NET World

[OneNote.8] Utilizzare il OneNote Tool

Uno dei punti di forza di Microsoft OneNote è l’integrazione con il sistema operativo e con le applicazioni Office installate. Abbiamo visto in questo post come sia facile inserire in una pagina documenti Word e fogli elettronici Excel, per esempio. Vedremo anche in un piccolo post dedicato come OneNote si possa integrare anche con Outlook.

Quello di cui parleremo oggi, invece, è l’integrazione con il sistema operativo. Quando OneNote viene installato sul PC, infatti, nella tray-bar viene posizionato anche un piccolo ed efficace tool, chiamato OneNote Tool, pronto ad aiutarci quando lo chiamiamo in azione. Questo tool permette di aggiungere nuovi contenuti all’interno dei nostri blocchi appunti.

image

Questo tool è richiamabile di default in tre modi differenti, elencati qui sotto partendo da quello che personalmente ritengo essere il più comodo e veloce:

  • premendo la combinazione Win+N sulla tastiera
  • facendo doppio-click sull’icona nella tray-bar di Windows
  • dalla ribbon View di OneNote, come illustrato qui sopra

image

Questo tool ci permette in modo rapido di:

  • catturare l’immagine di una porzione dello schermo (tasto S)
  • inviare un contenuto di un’applicazione all’interno di OneNote (tasto D)
  • inserire rapidamente un nota, che finirà automaticamente e senza alcuno sforzo nella sezione denominata Quick Notes di OneNote (tasto N)

Vediamo una ad una queste possibilità.

Catturare l’immagine di una porzione dello schermo
Questa caratteristica ci permette di ritagliare una porzione del nostro schermo, e di inserire la corrispondente immagine in una pagina di uno qualsiasi dei nostri blocchi appunti. Questa caratteristica è attivabile anche con la combinazione Win+Shift+S (molto comoda se siamo veloci con la tastiera). Una volta attivata, il vostro schermo diventerà completamente bianco: il passaggio successivo consiste nel disegnare un rettangolo sullo schermo che includa ciò che desideriamo inserire nella pagina di OneNote. Da notare che la combinazione Win+Shift+S è attiva sempre, per cui non è necessario portarsi su OneNote per usufruirne: indipendentemente da dove ci troviamo, quindi, spostiamoci sul programma che vogliamo catturare e premiamo la combinazione. Il resto è un gioco da ragazzi.

Una volta catturato lo schermo, OneNote fa comparire sullo schermo una finestra di dialogo, tramite la quale possiamo scegliere in quale punto vogliamo aggiungere/inserire il nuovo contenuto di tipo immagine. Tale finestra di dialogo si presenta come segue:

image

Le regole sono le seguenti:

  • non è possibile selezione un intero blocco appunti (facendo doppio-click su Trainer, Appunti Personali e Quick Notes, riportati qui sopra, semplicemente non accade nulla)
  • se si fa doppio-click su una sezione (ad esempio: Da pagare, Auto, Note rapide, Cose da fare, Trasferta Modena o Vacanze) l’immagine viene automaticamente inserita in una nuova pagina nella sezione corrispondente. Il nome di questa pagina assumerà la forma di “GG/MM/AAAA HH:SS – Screen Clipping” (ovvero data ed ora correnti); ovviamente è possibile rinominarla in qualsiasi momento come se fosse una normalissima pagina
  • se si fa doppio-click su una pagina specifica, l’immagine viene accodata ad eventuali contenuti già presenti sulla pagina

Più in generale, il pulsante Send to Selected Location è spento (come nell’immagine qui sopra) quando la selezione corrente non è supportata.

La finestra di dialogo ci permette di cercare una sezione od una pagina particolari tramite un filtro, così da rendere più agevole e rapido l’inserimento del nuovo contenuto.

Inviare un contenuto di un’applicazione all’interno di OneNote
Questa caratteristica permette di inviare il contenuto di un’applicazione all’interno di OneNote:

  • una pagina Web in Internet Explorer
  • un foglio elettronico Excel
  • una presentazione PowerPoint
  • un documento Word (non ditelo a nessuno, ma io non ce l’ho fatta)

La procedura è la seguente:

  1. aprite la pagina web o il documento che volete importare in OneNote
  2. aprite il OneNote Tool premendo Win+N o uno degli altri metodi disponibili
  3. il pulsante centrale del tool dovrebbe riportare l’icona del programma aperto al punto (1), quindi le ‘e’ di Explorer, oppure l’icona del programma Office
  4. cliccare il pulsante centrale

Comparirà la stessa finestra di dialogo già esaminata nel paragrafo precedente, che permette di selezionare la destinazione in cui verrà inserito il documento. Come venga effettuata l’importazione dipende dall’applicazione: Excel riporta il foglio elettronico nella pagina selezionata, mentre PowerPoint genera tante pagine quante sono le diapositive contenute nella presentazione.

Inserire rapidamente un nota
La terza ed ultima opzione disponibile nel OneNote Tool permette di aggiungere rapidamente una nuova nota in forma testuale. Questa feature è raggiungibile anche con la combinazione Win+Alt+N.

Non appena premuta la combinazione, sullo schermo comparirà un rettangolo in cui digitare:

image

All’interno di questa finestra (assimilabile ad un tradizionale post-it) possiamo digitare nuovi contenuti, in piena libertà. Notare che OneNote ci mette comunque a disposizione due elementi:

  • i tre puntini (rettangolo blu) permettono di aprire le ribbon standard di OneNote, col quale raggiungere tutte le funzionalità esplorate sinora
  • il pulsante di chiusura e la freccina di ridimensionamento (rettangolo verde) che permettono rispettivamente di chiudere la finestra e di raggiungere la visualizzazione standard di OneNote

Questi nuovi contenuti vengono automaticamente inseriti nella sezione Quick Notes

image

La sezione Quick Notes viene sempre posizionata in basso a sinistra, dopo l’elenco delle sezioni che ci siamo creati nostri all’interno dell’applicazione. La sezione Quick Notes è una sezione più limitata rispetto alle altre, perchè non può contenere al suo interno ulteriori sezioni, ma solo pagine, e non ha un suo cestino privato. Il click destro sulla sezione Quick Notes non sortisce alcun effetto. Nelle pagine delle note veloci possiamo inserire qualsiasi contenuto, come nelle pagine normali.

Doppio-click sull’icona di OneNote Tool
Possiamo anche fare un veloce doppio-click sull’icona posizionata nella tray-bar di Windows. Il comportamento di default è quello di aprire il tool stesso, ma è possibile modificarlo con il menù contestuale:

image

Dal menù contestuale è possibile raggiungere anche tutte le funzionalità esposte sinora.

Conclusioni
Questo tool è tanto semplice quanto pratico, intuitivo e veloce nell’utilizzo. Grazie al OneNote Tool possiamo importare nuovi contenuti dall’esterno, immagini o documenti, direttamente dall’applicazione che stiamo utilizzando. I documenti possono essere pagine Web o documenti delle applicazioni Office. Personalmente ritengo molto comode le combinazioni della tastiera, decisamente più veloci. Questo tool è attivo di default, perciò è uno strumento che possiamo utilizzare fin da subito. Se lo si conosce è meglio, insomma!

Arrivederci alla prossima puntata!

Send to Kindle
.NET World

Microsoft negli ultimi mesi & anni, e soprattutto dopo Build 2015

Facciamo il punto della situazione.

  • Il mio codice .NET gira su Windows e potrà girare sotto Linux e Mac OS (grazie al fatto che Microsoft sta facendo il porting del .NET Framework nelle altre piattaforme)
  • Chi sviluppa codice per iOS o Android potrà ricompilare le proprie applicazioni per farle girare sotto Windows 10 (esempio: Objective-C)
  • Windows 10 può girare su ogni tipo di hardware: pc desktop, notebook, ultrabook, smartphone, tablet, XBOX, Surface, HoloLens, device IoT
  • Le nuove Windows Universal App potranno girare su tutti i device Windows 10, senza alcun bisogno di ricompilare. Lo stesso binario nativamente gira ovunque.
  • Potrò interagire con Windows 10 con tastiera, mouse, touch, voce, penna
  • Ci sono nuove API per accedere a contatti, chiamate, appuntamenti, mail, SMS e dati utente
  • Visual Studio è l’IDE per eccellenza, sempre di più: posso scrivere codice desktop, web, cloud; posso compilare codice sorgente per iOS/Android verso il mondo Windows; posso debuggare da remoto su device Windows 10, di ogni tipo, XBOX compresa
  • Nei giorni scorsi Microsoft ha rilasciato Visual Studio 2015 Release Candidate (maggiori info qui)
  • Tanto tanto Azure; il cloud praticamente ci circonda
  • Microsoft ha rilasciato Visual Studio Code, ovvero un Visual Studio ridotto per Mac (supporta una vagonata incredibile di linguaggi, e supporta debugging ed Intellisense)
  • Cortana è un’assistente vocale, ma è molto di più; è sempre più intelligente e con Windows 10 sarà sempre più utile per portare a compimento operazioni “mixate” (comincio chiedendo qualcosa a Cortana con la voce, poi digito con la tastiera, poi ritorno alla voce, etc.)
  • Unity comincia il supporto a Microsoft HoloLens
  • Continuum, ovvero: il tuo smartphone Windows 10 diventa un PC Windows 10. Se collego una tastiera ed un mouse Bluetooth ad uno smartphone Windows 10, esso diventa a tutti gli effetti un PC Windows 10. Per far questo, c’è solo bisogno di avere Windows 10 sul telefono, senza bisogno di alcun hardware nuovo (su quest’ultimo punto si discute ancora, è un po’ controverso)
  • Lo store Windows 10 potrà accogliere non solo le nuove Universal App, ma anche le tradizionali applicazioni Windows desktop, le applicazioni Win32 & WPF, ed anche quelle per Android/iOS (ovviamente ricompilate per il mondo Windows). Conterrà anche musica & video.
  • Gli applicativi Office già oggi esistono sotto Windows, Android ed iOS
  • Voglio provare ed avere Microsoft HoloLens prima di morire. E’ un computer olografico. Non posso cercare di spiegarvi di cosa si tratta in forma scritta: guardate un po’ di video su YouTube su questo argomento
  • Microsoft Edge è il nuovo browser di casa Microsoft
  • Windows 10 supporta l’autenticazione biometrica (basta password, ma riconoscimento facciale, scansione della retina o delle impronte digitali). Tutto questo cade sotto il nome di Microsoft Hello.
  • Per il primo anno Windows 10 sarà un upgrade gratuito per chi arriva da Windows 7, Windows 8 e Windows 8.1
  • Microsoft è sempre più vicina al mondo Arduino, ormai vanno a braccetto (mondo dei maker e IoT)
  • Project Centennial: distribuzione di applicazioni Windows oltre il classico msi o l’installer ClickOnce. Interessato
  • “Microsoft research group is the biggest in the technology industry” (cit.)
  • “Wherever your code was born, you can bring it to Windows” (cit.)
  • Mi è stato fatto giustamente notare che Microsoft ha reintrodotto il menù Start, anche se chiaramente si tratta di un’evoluzione rispetto a quello di Windows 7: è molto più modern, contiene le tradizionali applicazioni desktop e le Universall app, può girare in full-screen per adeguare l’interfaccia ai tablet, e molto altro ancora

Ci sono stati così tanti cambiamenti nelle ultime 48 ore che mi gira la testa ancora adesso. Tanta, tanta carne al fuoco. Ma tanta davvero.

Non mi interessa il successo commerciale di tutto questo. Già le volte precedenti gli utenti Windows si sono dimostrati refrattari al cambiamento. Non mi interessa davvero. Sono un tecnico, e mi interessano solo le cose tecniche. Microsoft ha davvero imboccato una strada meravigliosa. Quando sento dire da qualcuno “Microsoft ha copiato questo o quello” ormai sorrido. Ha innovato più Microsoft negli ultimi anni che tutte le altre aziende ICT mondiali messe assieme. Ripeto: non mi interessa minimamente il successo commerciale di tutto questo. Mi interessano principalmente due cose: quanto viene stimolata la parte nerd di me con tutte queste innovazioni, e quanto andranno ad influenzare le mie competenze ed il mio lavoro nei prossimi anni.

Ci sarà da divertirsi.

P.S. : se mi sono dimenticato di qualcosa di importante, segnalatemelo che lo aggiungo in lista.

Send to Kindle