Au menu aujourd’hui nous allons nous intéresser aux sujets suivants:
Néanmoins, puisque la Release Preview est disponible, j’ai porté l’intégralité de ce 2ieme jour sur cette version, ce qui m’a pris engros une bonne demi-journée. Les modifications étant importante dans les styles XAML, j’ai opté pour la stratégie, qui consistait à repartir d’un nouveauprojet en mode grille et de refaire toutes les vues XAML. Avec des correctionsmineures dans le Code C#.
A partir de maintenant, il vous faudra faire les exercices sur cette version ainsi que sur les outils associés que voustrouverez ici.
Téléchargement de la release Preview et des outils.
Vous retrouverez le code source liée à cet article ici : http://aka.ms/oitxqi
Comme nous visons toutes les places de marchés supportées par le store Windows 8,nous allons devoir internationaliser notre application. Pour ce faire, XAMLpour Windows 8 Metro fournit un service très simple.
Pour commencer, nous allons créer dans la solution, un répertoire Resources,dans lequel nous allons créer deux sous répertoires "en" et "fr"
Comme illustré sur la figure suivante :
Remarque :Windows gère non seulement la culture qui symbolise la langue parlée, maiségalement la région. Par exemple, pour symboliser la langue Française parlée enFrance, la chaine complète sera "fr-FR",pour le Québec, "fr-CA",pour la Belgique "fr-BE"et ainsi de suite.Si aucune ressource n'est disponible pour la langue Française région France("fr-FR") le gestionnaire de ressources, ira chercher la culture de plus haut niveau "fr". Dans notre cas ici juste la langue Française.Si ce fichier de ressources n’est pas présent, il ira chercher le fichier deressources par défaut
Ensuite, il faut ajouter dans chaque repertoire (ie "en" et "fr") unfichier de ressources
Laissez le nom Resources.resw, car par défaut c'est ce nom de fichier qui sera utilisé par le système de gestion de ressources de XAML.
Une ressource est définie par, le nom de la ressource, et par la valeur de la ressource, comme illustré :
Exemple pour le fichier de ressources Anglais
Name | Value
Number.Text | Number:
Power.Text | Power:
Pour celui en Français
Number.Text | Numéro :
Power.Text | Puissance :
Dans le nom de la ressource, j'indique également le nom de la propriété pour que le gestionnaire de ressourcessache sur qu'elle propriété du contrôle il faut associer la valeur de la ressource. Ici la propriété Text, mais comme vous leremarquerez en examinant les fichiers de ressources associées aux code de cet article, on peut également indiquer unepropriété comme la propriété Width concernant la largeur d'un contrôle.
Pour la langue Anglaise
CboColor.Width 120
Pour la langue Française
CboColor.Width 196
En effet, il faut bien garder à l'esprit que la longueur de la chaine "All colors", est plus petite, que la chaine "Toutes les couleurs". Ces une desmanières de pouvoir adapter un contrôle à l'écran automatiquement.
Ensuite, pour faire la liaison entre la ressource et le contrôle XAML , il faut utiliser sonattribut x:Uid avec le nom de la ressource, mais sans utiliser le préfixe type propriété .Text.
Par exemple dans notre vue Card, nos contrôles de type TexBlock qui affichent le libellé des caractéristiques d'une carte.
<TextBlock Style="{StaticResource UrzaCardLabelTextBlock}" x:Uid="Number"/>
<TextBlock Grid.Column="1" Grid.Row="0" Text="{Binding number}" Style="{StaticResource UrzaCardTextBlock}" ></TextBlock>
<TextBlock Grid.Row="1" Style="{StaticResource UrzaCardLabelTextBlock}" x:Uid="Type" />
<TextBlock Grid.Column="1" Grid.Row="1" Text="{Binding type}" Style="{StaticResource UrzaCardTextBlock}" ></TextBlock>
<TextBlock Grid.Row="3" Style="{StaticResource UrzaCardLabelTextBlock}" x:Uid="Power" />
...
Ou dans la vue Expansions pour que le contrôle Combobox adapte sa longueur automatiquement.
<ComboBox Grid.Column="1" x:Name="cboFilterByColors" ItemsSource="{Binding Path=FilterByColors}" Margin="4,0,0,8" Width="120" x:Uid="CboColor"
Style="{StaticResource ComboBoxStyle}" Background="{x:Null}" Template="{StaticResource ComboBoxControlTemplateFilter}"/>
Remarque : Même si ici la propriété Width est indiquée, c'est bien la longueur définie dans le fichier de ressources qui sera pris en compte. Le gestionnairede ressources fera le reste, il n'y a rien à faire d'autre.
L'autre scénario, c'est d'aller rechercher directement une ressource dans le fichier de ressourcespour l'affecter à la propriété Text d'un TexBlock par exemple.
Pour ce faire, nous allons utiliser Windows.ApplicationModel.Resources.ResourceLoader
ResourceLoader possède la méthode GetString() qui prend comme paramètre le nom de la ressource.
Par exemple .
String ressource = _resourceLoader.GetString("ReceiveBytesMessage");
Dans ce scénario, le nom de la ressource, n'a pas besoin d'être préfixée par une propriété,de plus le fichier de ressources, n'est pas non plus forcement obligé de s'appeler Ressource.resw Du coup j'ai construit un classe statique qui n'expose quedes propriétés statiques en lecture seule, dont le nom est constitué du nom de la ressource et qui charge le fichier de ressources approprié.
Exemple ici je charge le fichier Strings.Resw.
static class UrzaResources
{
static ResourceLoader _resourceLoader = new ResourceLoader("Strings");
public static String ReceiveBytesMessage
get { return _resourceLoader.GetString("ReceiveBytesMessage"); }
}
public static String CreatingLocalResourcesMessage
get { return _resourceLoader.GetString("CreatingLocalResourcesMessage"); }
public static String ByName {
get { return _resourceLoader.GetString("ByName"); }
public static String ByNumber
get { return _resourceLoader.GetString("ByNumber"); }
De cette manière, on peut utiliser la ressource plus facilement, en utilisantdirectement la propriété statique.
async void _dataSource_DownloadFileInProgress(object sender, JSONFileDownloadInProgressEventArgs e)
if (DownloadFileProgress != null)
if (e.TotalBytesToReceived > e.BytesReceived)
String Message = String.Format(UrzaResources.ReceiveBytesMessage,
e.BytesReceived.ToString(),
e.TotalBytesToReceived.ToString());
await this.Dispatcher.RunAsync(CoreDispatcherPriority.Normal, new DispatchedHandler(() =>
DownloadFileProgress(this, new JSONFileDownloadInfoEventArgs(Message));
}));
En dernier lieu, il est possible de forcer par code le langage qui sera utilisé
Windows.Globalization.ApplicationPreferences.PreferredLanguage = "fr";
Mais le mieux, c'est encore d'utiliser l'interface de Windows et de laisser faire legestionnaire de ressources en modifiant la position du langage comme illustrésur la figure ci-dessous.
Certains utilisateurs ne souhaiteront peut-être pas utiliser de l'espace sur leur disque pour sauvegarder les images en local. Nous allons donc rajouter une propriétédans les settings pour gérer l'activation du mode offline.
Tout d'abord, nous allons modifier notre classe Settings en lui rajoutant une méthode IsOffLineModeOn()qui test si le mode OffLine est activé.
static public Boolean IsOffLineModeOn()
Boolean offlineMode = false;
if (ApplicationData.Current.RoamingSettings.Values.ContainsKey(UrzaResources.OffLineMode))
offlineMode = (Boolean)ApplicationData.Current.RoamingSettings.Values[UrzaResources.OffLineMode];
return offlineMode;
La propriété OffLineMode permettra de faire le pont avec le controle SettingsControl.xaml(Rappelez vous nous passons dans son constructeur une instance de la classe Settings)
Puis enfin j'ajoute une méthode qui sauvegarde les paramètres itinérants.
public void SaveSettings(Double zoomfactor, Boolean offlinemode)
ApplicationData.Current.RoamingSettings.Values[UrzaResources.ZoomFactor] = zoomfactor;
ApplicationData.Current.RoamingSettings.Values[UrzaResources.OffLineMode] = offlinemode;
Ensuite nous allons ajouter deux contrôles de type RadioBouton dans notre contrôlepersonnalisé SettingsControl.xaml
<RadioButton x:Name="rdOn" Content="On" Grid.Column="1" HorizontalAlignment="Left" Margin="10,98,0,0" Grid.Row="2" VerticalAlignment="Top" Height="48" Width="74" RenderTransformOrigin="-0.261999994516373,-0.0599999986588955" Background="White" Foreground="Black" Style="{StaticResource UrzaRadioButtonStyle}" />
<RadioButton x:Name="rdOff" Content="Off" Grid.Column="1" HorizontalAlignment="Left" Margin="129,98,0,0" Grid.Row="2" VerticalAlignment="Top" Height="29" Width="74" RenderTransformOrigin="-0.261999994516373,-0.0599999986588955" Background="White" IsChecked="True" Foreground="Black" BorderThickness="0" BorderBrush="Black" Click="CheckOffLineMode" Style="{StaticResource UrzaRadioButtonStyle}"/>
Puis nous allons lié nos contrôles avec la propriété OffLineMode de notre instance de classe Settings sur leurpropriété IsChecked
rdOff.IsChecked = !_roamingSettings.OffLineMode;
rdOn.IsChecked = _roamingSettings.OffLineMode;
et enfin sur le déchargement du contrôle nous sauvegardons les paramètres d'itinérance
void SettingsControl_Unloaded(object sender, RoutedEventArgs e)
_roamingSettings.SaveSettings(this.slider.Value, (Boolean)rdOn.IsChecked);
L'appel à la méthode URZASettings.IsOffLineModeOn() cefait à chaque fois qu'une ressource de type image doit être chargée
La vue Cards, ne permet de visualiser qu'une seule carte à la fois, avec le contrôle FlipViewnous allons pouvoir naviguer dans les différentes cartes, sans être obligé derevenir à la vue Expansions
La bonne nouvelle, c'est qu'il n'y presque rien à faire, si ce n'est de modifierlégèrement le XAML pour prendre en compte le FlipView, et le code C# pourbinder les données correctement.
1) Le XAML qui permettait d'afficher nos données, nous allons l'encapsuler dans un contrôle <UserControl>.
2) Ensuite nous encapsulons ce contrôle comme étant le DataTemplate pour les items du FlipView. FlipView que nous lions avec lapropriété cards de notre VueData .
<FlipView Grid.Row="1" ItemsSource="{Binding cards}" x:Name="flipViewCards">
<FlipView.ItemTemplate>
<DataTemplate>
<UserControl>
<Grid Grid.Row="1" Margin="116,0,0,0">
<Grid.RowDefinitions>
<RowDefinition Height="34*"/>
<RowDefinition Height="279*"/>
<RowDefinition Height="144*"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="100*"/>
<ColumnDefinition Width="500*"/>
</Grid.ColumnDefinitions>
<TextBlock Grid.Row="2" Style="{StaticResource UrzaCardLabelTextBlock}" x:Uid="Color" />
<TextBlock Grid.Column="1" Grid.Row="2" Text="{Binding color}" Style="{StaticResource UrzaCardTextBlock}" ></TextBlock>
<TextBlock Grid.Column="1" Grid.Row="3" Text="{Binding power}" Style="{StaticResource UrzaCardTextBlock}" ></TextBlock>
<TextBlock Grid.Row="4" Style="{StaticResource UrzaCardLabelTextBlock}" x:Uid="Text" />
<TextBlock Grid.Column="1" Grid.Row="4" Text="{Binding text}" Style="{StaticResource UrzaCardTextBlock}" ></TextBlock>
<TextBlock Grid.Row="5" Style="{StaticResource UrzaCardLabelTextBlock}" x:Uid="Flavor" />
<TextBlock Grid.Column="1" Grid.Row="5" Text="{Binding flavor}" Style="{StaticResource UrzaCardTextBlock}" ></TextBlock>
<TextBlock Grid.Row="6" Style="{StaticResource UrzaCardLabelTextBlock}" x:Uid="Author" />
<TextBlock Grid.Column="1" Grid.Row="6" Text="{Binding author}" Style="{StaticResource UrzaCardTextBlock}" ></TextBlock>
<Image Stretch="Uniform" Source="{Binding ImageInfo, Converter={StaticResource pictureInfoConverter}}" Grid.Column="2" Grid.Row="0" Grid.RowSpan="8" d:LayoutOverrides="Margin" >
<Image.Transitions>
<TransitionCollection>
<AddDeleteThemeTransition/>
</TransitionCollection>
</Image.Transitions>
</Image>
<UrzaControl:UrzaImage Stretch="None" Source="{Binding bannerInfo}" Grid.Row="7" Grid.Column="0" Grid.ColumnSpan="2" HorizontalAlignment="Left" VerticalAlignment="Top" Margin="57,20,0,0" />
</Grid>
</UserControl>
</DataTemplate>
</FlipView.ItemTemplate>
</FlipView>
3) La modification du code ce résume, à changer la liaison du DataContext d'unseul élément, a l'ensemble des éléments de l'expansion, et à sélectionner dansle FlipView, la carte courante.
protected override void LoadState(Object navigationParameter, Dictionary<String, Object> pageState)
VueData vueData = (VueData)navigationParameter;
this.DataContext = vueData.CurrentExpansion;
this.flipViewCards.SelectedItem = vueData.CurrentCard;
this.flipViewCardsSnapped.SelectedItem = vueData.CurrentCard;
C'est tout !!!
Windows 8 Metro impose le support de la vue “snapped”pour chacun des écrans de l’application. Ce mode s’active quand votre application est mise sur le côté d’une application principale. Elle va alorsoccuper une largeur de 320 pixels.
Comme sur la figure suivante :
Il faut donc décider dans le cadre du design de son application, ce que devracontenir ce mode pour chacun des écrans.
Dans le cas de UrzaGatherer, j’ai pris le parti de fournir une expériencequasi-complète en repensant mes écrans sans enlever de fonctionnalités.
Il faut voir le mode snapped, comme une autre manière d’afficher les données dans la vue.
En gros comme ça marche ?
Par défaut dans nos vues Home et Expansions, nous avons des GridViews pour afficher et naviguerdans nos données en mode Horizontal. Comme le mode snapped est plutôt vertical, nous allons y ajouter une ListView et qui sera liée à nos donnéesde la même manière que la GridView, mais qui s’adaptera à une taille de 320pixels. Visual Studio fournit d’ailleurs un outil bien pratique, qui permet devisualiser les différents états de la vue, vous permettant ainsi de les mettreau point.Par défaut cette ListView n’est pas visible, mais lorsque la vue snappedest activée, le gestionnaire VisualStateManager (que nous aborderons un peu plus loin) la passera dans un état visible, alorsque la GridView sera dans un état non visible. Rien de magique ici, on rend visible/invisible un contrôle en fonction de l’état de la vue.Exemple dans notre vue Expansions, j’ai ajouté une ListView (nommée CardsListView) et qui est invisible pardéfaut. <GridView
x:Name="CardsItemsGridView"
SelectionMode="None"
Grid.Row="2"
Margin="0,-3,0,0"
Padding="116,0,40,46"
IsItemClickEnabled="True"ItemClick="cardsItemsGridView_ItemClick"
ItemsSource="{Binding Source={StaticResourcegroupedCardsItemsViewSource}}">
<GridView.ItemTemplate>
<Button Style="{StaticResourceUrzaExpansionGridViewStyle}" Width="{Binding Source={StaticResourceurzaSettings},Path=Zoom.Width}"
Height="{Binding Source={StaticResourceurzaSettings},Path=Zoom.Height}"></Button>
</GridView.ItemTemplate>
</GridView>
<!-- Vertical scrollinglist only used when snapped -->
<ListView
IsItemClickEnabled="True"
ItemClick="cardsItemsGridView_ItemClick"
x:Name="CardsListView"
Visibility="Collapsed"
Margin="0,-10,0,0"
Padding="10,0,0,60"
ItemsSource="{Binding Source={StaticResourcegroupedCardsItemsViewSource}}"
ItemTemplate="{StaticResource UrzaExpansionsListView}">
</ListView>
Comme indiqué plus haut, c’est le VisualStateManager qui fera la liaison entre chaque état de la vue. Mais pour cela, il faut luiindiquer la manière de se comporter, lorsqu’on passe d’un état à un autre. On va donc définir en XAML des groupes VisualStateGroups,d’états Visuelles VisualState qui vont permettre d’afficher ou pas des contrôles.
Dans l’exemple suivant, en mode FullScreenLandscape, et ou Filled, on laisse tel que parcontre si on passe en mode Snapped, on rend invisible avec une animation la GridView, et on rend Visible laListView.
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="ApplicationViewStates">
<VisualState x:Name="FullScreenLandscape"/>
<VisualState x:Name="Filled"/>
<VisualState x:Name="Snapped">
<Storyboard>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFramesStoryboard.TargetName="CardsListView"Storyboard.TargetProperty="Visibility">
<DiscreteObjectKeyFrameKeyTime="0" Value="Visible"/>
<ObjectAnimationUsingKeyFramesStoryboard.TargetName="CardsItemsGridView"Storyboard.TargetProperty="Visibility">
<DiscreteObjectKeyFrameKeyTime="0" Value="Collapsed"/>
</Storyboard>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
La dernière étape est d’indiquer au VisualStateManager dans quel état visual l’application veuts’afficher, afin qu’il puisse faire la liaison avec ce que nous venons d’ajouter en XAML.
Pour récupérer l’état en cours, nous allons utiliser la classe Windows.UI.ViewManagement.ApplicationView qui nous fournit entre autre la propriétéValue, qui récupère l’état visuel en cours. string visualState = DetermineVisualState(ApplicationView.Value);
à positionner sur l’évènement WindowSizeChanged de la vue.ApplicationView.Value, retournera selon le cas une chaine "Snapped", "Fill","FullScreenLandscape", "FullScreenPortrait"
Puis nous allons forcer l’état. VisualStateManager.GoToState(layoutAwareControl,visualState, false);
Néanmoins la bonne nouvelle, c’est que vous aurez remarqué que toutes nos vues héritent dela classe LayoutAwarePage (grâce au modèle par défaut de Visual Studio) qui nous fournit déjà cette mécanique dechangement d’état visuel, il n’y a donc rien à coder, tout est déjà fait pournous ;-)
Plus d’infos
Guidelines forsnapped and fill views (Metro style apps)
Enfin il faut que nos images et plus particulièrement celle de la vue Cards s'adaptent aux différentes résolutions.
Pour ce faire vous pouvez utiliser le contrôle ViewBox, pour ma part , dans la vue cards j'ai juste décidé d'utiliser un espacement proportionnel. C'est à dire que notre image prendra la place de 8 row en taille proportionnelle définie par le symbole "*" sur 1 colonne à taille proportionnelle
<Image Stretch="Uniform" Source="{Binding ImageInfo, Converter={StaticResource pictureInfoConverter}}" Grid.Column="2" Grid.Row="0" Grid.RowSpan="8" >
Image en 1920*1080
Image en 1366*768
Image en 1024*768
Image en 2560*1440
Guidelines for scaling to screens (Metro style apps)
http://msdn.microsoft.com/fr-fr/library/windows/apps/hh465424#ancrage_et_mise___l__chelle
Pour la suite de notre menu, nous verrons :
Eric Vernié