Comment “cuisiner” une application Windows 8 avec XAML et C# en une semaine–Jour 0
Il y de cela quelques semaines, David Catuhe, a commencé à écrire une série d'articles, sur la manière de "Cuisiner" une application Windows 8 avec HTML 5, CSS 3 et JavaScript.
Le but étant étant de fournir une recette pragmatique pour écrire une application Windows 8 à partir de zéro.
A ça demande, je reprends son idée de l'application UrzaGatherer https://urzagatherer.codeplex.com/ (Application pour aider les collectionneurs de cartes Magic The Gathering à gérer leur collection) et vous propose une série d'articles mais cette fois-ci en .NET avec C# et en XAML. (Bien évidement vous pouvez choisir également Visual Basic .NET, ou alors de le faire en natif avec C++ et XAML).
Note : La version C# sera ISO fonctionnelle par rapport à la version HTML ni plus ni moins.
Pour développer l’application, vous allez donc avoir besoin de :
• Un ordinateur avec Windows 8 Consumer Preview installé dessus (Vous pouvez le télécharger ici: https://preview.windows.com)
• Visual Studio 11 (La version Express Beta pour Windows 8 peut être téléchargée ici: https://msdn.microsoft.com/en-us/windows/apps/br229516)
• Et c’est tout!
Note : Cet série d'articles est consacré à la manière de créer une application Windows 8 au Style Metro de bout en bout, afin que vous développeurs, vous en maitrisiez les concepts et que votre application adhère aux principes d'une application au style Metro. Pour les puristes, pas de MVVM, d'injection de contrôle et autres modèles de développement, ce n'est pas le but recherché. Mais bon vous pouvez toujours améliorer la recette en lui ajoutant une pincée de MVVM, MVMM light étant désormais disponible pour WinRT https://mvvmlight.codeplex.com/.
La solution peut-être téléchargée ici : Day0
Créer le projet
La première chose à faire est de créer un projet vide (nous aurions bien sur pu utiliser un modèle plus complet de Visual Studio comme le ‘Grid Application"’ mais l’objectif ici est de comprendre comme tout fonctionne) en utilisant le menu Fichier/Nouveau projet
Le projet est ainsi crée avec uniquement les fichiers nécessaires
Créer les ressources graphiques
Le fichier package.appxmanifest permet de décrire votre application auprès de Windows 8. Il contient particulièrement la description de l’application ainsi que tous les logos et ressources graphiques nécessaires :
Nous allons ajouter ces ressources afin de donner tout de suite un air de “vraie application”.
Le “splash screen” par exemple est très important car c’est la première chose que vos utilisateurs vont voir et comme chacun sait la première impression est toujours la plus importante :
Cette partie est souvent la plus dure au final car les développeurs ne sont que très rarement de bons designers
Structurer le projet
La structuration va dépendre de votre manière de travailler, mais elle aide le plus souvent à s'y retrouver plus rapidement. Dans notre exemple nous avons choisi une structuration tout à fait commune.
En créant deux répertoires supplémentaire par rapport à la solution de base,
DataModel, pour le modèle de données, et Views qui contiendra les vues de l’application
Connexion aux données
La connexion aux données sera faite dans le fichier /DataModel/UrzaGathererDataSource.cs
Pour UrzaGatherer, les données sont composées de :
• Un fichier all.json qui décrit toutes les cartes supportées
• Les images de chaque carte (plus de 4Go!!)
• Un logo pour chaque extension (sachant qu’une carte appartient à une extension qui appartient à un bloc)
• Un logo pour chaque bloc
Dans un premier temps, nous allons ajouter dans le répertoire DataModel, une nouvelle classe UrzaGathererDataSource.cs
Code Snippet
- namespace UrzaGatherer.DataModel
- {
- class UrzaGathererDataSource
- {
- }
- }
Avant d’implémenter quoi que ce soit dans cette classe, nous allons créer dans l’espace de nom UrzaGatherer.DataModel, trois autres classes JSONBlock, JSONExpansion et JSONCard, dérivant de la classe UrzaGatherer.Common.BindableBase. Cette dernière étant une classe helper , créée automatiquement par Visual Studio 11 et qui implémente INotifyPropertyChanged pour le Binding de données.
Code Snippet
- public class JSONBlock : UrzaGatherer.Common.BindableBase
- {
- private String _name;
- public String name
- {
- get { return _name; }
- set { this.SetProperty(ref this._name, value); }
- }
- private String _logo;
- public String logo {
- get{return _logo;}
- set {this.SetProperty(ref this._logo, value);
- }
- }
- private int _id;
- public int id {
- get { return _id; }
- set{this.SetProperty(ref this._id, value);
- }
- }
- private int _index;
- public int index {
- get { return _index; }
- set{ this.SetProperty(ref this._index, value);}
- }
- public ObservableCollection<JSONExpansion> expansions { get; set; }
- }
- public class JSONExpansion : UrzaGatherer.Common.BindableBase
- {
- private JSONBlock _block;
- public JSONBlock block {
- get { return _block;}
- set { this.SetProperty(ref this._block, value); }
- }
- private String _name;
- public String name
- {
- get { return _name; }
- set { this.SetProperty(ref this._name, value); }
- }
- private String _logo;
- public String logo
- {
- get { return _logo; }
- set
- {
- this.SetProperty(ref this._logo, value);
- }
- }
- private int _id;
- public int id
- {
- get { return _id; }
- set
- {
- this.SetProperty(ref this._id, value);
- }
- }
- private int _orderInBlock;
- public int orderInBlock
- {
- get { return _orderInBlock; }
- set
- {
- this.SetProperty(ref this._orderInBlock, value);
- }
- }
- public ObservableCollection<JSONCard> cards { get; set; }
- }
- public class JSONCard
- {
- public String power { get; set; }
- public String flavor { get; set; }
- public String picturePath { get; set; }
- public String name { get; set; }
- public String author { get; set; }
- public String color { get; set; }
- public String rarity { get; set; }
- public String text { get; set; }
- public String type { get; set; }
- public int id { get; set; }
- public int number { get; set; }
- public int? force { get; set; }
- public int? defense { get; set; }
- public double? price { get; set; }
- }
Implémentons maintenant notre classe UrzaGathererDataSource
Code Snippet
- public class CardsLoadEnventArgs : EventArgs
- {
- public ObservableCollection<JSONBlock> Blocks;
- public CardsLoadEnventArgs(ObservableCollection<JSONBlock> blocks)
- {
- Blocks = blocks;
- }
- }
- class UrzaGathererDataSource
- {
- const String CARDS_ROOT = "https://urzagatherer.blob.core.windows.net";
- const String REMOTE_FILE_PATH = "/cards/all.json";
- const String LOCAL_FILE = "all.json";
- const int MAX_BUFFER_SIZE = 10 * 1024 * 1024;
- public delegate void CardsLoadEventHandler (Object sender, CardsLoadEnventArgs e);
- public event CardsLoadEventHandler CardsLoaded;
- private ObservableCollection<JSONBlock> _blocks;
- public ObservableCollection<JSONBlock> blocks {
- get { return _blocks;}
- set {_blocks=value;}
- }
- private StorageFile _cardsFile;
- public StorageFile Cards {
- get { return _cardsFile;}
- set { _cardsFile=value;}
- }
- /// <summary>
- /// Does the all.json file is already in the cache ?
- /// if yes get the file for futur use
- /// </summary>
- /// <returns></returns>
- public async Task<bool> IsCardsOnCache()
- {
- try
- {
- var localCache = ApplicationData.Current.LocalFolder;
- _cardsFile = await localCache.GetFileAsync(LOCAL_FILE);
- return true;
- }
- catch(System.IO.FileNotFoundException)
- {
- return false;
- }
- }
- /// <summary>
- /// Download the all.json file, and save it on the local folder
- /// to avoid futur download
- /// </summary>
- public async void LoadRemoteCardsAsync()
- {
- var requestStr=CARDS_ROOT + REMOTE_FILE_PATH;
- var client = new HttpClient();
- client.MaxResponseContentBufferSize = MAX_BUFFER_SIZE;
- var response=await client.GetAsync(new Uri(requestStr));
- var result = await response.Content.ReadAsStringAsync();
- //if I'm here I need to save the data on the cache
- var localCache = ApplicationData.Current.LocalFolder;
- var fileCache=await localCache.CreateFileAsync(LOCAL_FILE, CreationCollisionOption.ReplaceExisting);
- await Windows.Storage.FileIO.WriteTextAsync(fileCache, result);
- CreateBlocks(result);
- }
- /// <summary>
- /// Read the data from the cache
- /// </summary>
- public async void ReadJsonDataFromCacheAsync()
- {
- var stream = await _cardsFile.OpenReadAsync();
- var input = stream.GetInputStreamAt(0);
- var reader = new DataReader(input);
- uint count = await reader.LoadAsync((uint)stream.Size);
- var result = reader.ReadString(count);
- CreateBlocks(result);
- }
- public void CreateBlocks(String cards)
- {
- this.blocks = new ObservableCollection<JSONBlock>();
- var blockItems= JsonArray.Parse(cards);
- int count = blockItems.Count();
- for (uint i = 0; i < count; i++)
- {
- JSONBlock block = new JSONBlock();
- var objBlock = blockItems.GetObjectAt(i);
- block.id = (int)objBlock.GetNamedNumber("id");
- block.index = (int)objBlock.GetNamedNumber("index");
- block.name = objBlock.GetNamedString("name");
- block.logo = block.logo = CARDS_ROOT + "/blocks/" + block.name.Replace(":", "-") + ".png";
- JsonArray expansionsItems = objBlock.GetNamedArray("expansions");
- CreateExpansions(block, expansionsItems);
- blocks.Add(block);
- }
- if (CardsLoaded != null)
- {
- CardsLoaded(this, new CardsLoadEnventArgs(blocks));
- }
- }
- public void CreateExpansions(JSONBlock parentBlock,JsonArray expansionsItems)
- {
- var expansions = new ObservableCollection<JSONExpansion>();
- int count=expansionsItems.Count();
- for (uint i = 0; i < count; i++)
- {
- var objExpension=expansionsItems.GetObjectAt(i);
- JSONExpansion expansionItem = new JSONExpansion();
- expansionItem.block = parentBlock;
- expansionItem.id=(int)objExpension.GetNamedNumber("id");
- expansionItem.name=objExpension.GetNamedString("name");
- expansionItem.logo = CARDS_ROOT + "/logos/" + expansionItem.name.Replace(":", "_") + ".png";
- expansionItem.orderInBlock = (int)objExpension.GetNamedNumber("orderInBlock");
- //Disable for Day 0
- //JsonArray cardsItems = objExpension.GetNamedArray("cards");
- //expansionItem.cards = CreateCards(cardsItems);
- expansions.Add(expansionItem);
- }
- parentBlock.expansions = expansions;
- }
- public ObservableCollection<JSONCard> CreateCards(JsonArray cardsItems)
- {
- var cards = new ObservableCollection<JSONCard>();
- int count = cardsItems.Count();
- for (uint i=0; i < count; i++)
- {
- var objCard = cardsItems.GetObjectAt(i);
- JSONCard cardItem = new JSONCard();
- cardItem.author = objCard.GetNamedString("author");
- cardItem.id = (int)objCard.GetNamedNumber("id");
- cardItem.color = objCard.GetNamedString("color");
- cardItem.defense = (int)objCard.GetNamedNumber("defense");
- cardItem.flavor = objCard.GetNamedString("flavor");
- cardItem.force = (int)objCard.GetNamedNumber("force");
- cardItem.name = objCard.GetNamedString("name");
- cardItem.number = (int)objCard.GetNamedNumber("number");
- cardItem.picturePath = objCard.GetNamedString("picturePath");
- cardItem.power = objCard.GetNamedString("power");
- cardItem.price = objCard.GetNamedNumber("price");
- cardItem.rarity = objCard.GetNamedString("rarity");
- cardItem.text = objCard.GetNamedString("text");
- cardItem.type = objCard.GetNamedString("type");
- cards.Add(cardItem);
- }
- return cards;
- }
- }
Le fichier all.json , contenant les données sera téléchargé au démarrage de l’application et sauvegardé dans un répertoire local, c’est la méthode asynchrone, LoadRemoteCardsAsync() qui sera en charge :
- De télécharger le fichier,
- De le sauvegarder dans le répertoire locale à l’application
- Et dans la foulée d’appeler la méthode CreateBlocks, pour parser et mapper le fichier au format JSON correspondantes.
Une fois que les données sont mappées, l’évènement CardsLoaded sera levé afin de prévenir les vues qui s’y sont abonnées.
Vous noterez également la présence des méthodes IsCardsOnCacheAsync et ReadJsonDataFromCacheAsync qui utilisées en conjonction permettront d’éviter de re-télécharger le fichier all.json, ce qui peut prendre un certain temps.
Création d’une page de démarrage
Comme je le disais précédemment, le téléchargement du fichier all.json, peut prendre un certain temps, il est donc de bon ton, d’indiquer à l’utilisateur que les données sont en cours de chargement.
Pour ce faire, nous allons créer une vue à base d’un UserControl, que l’on nommera ExtendedSplachScreen, et qui aura pour rôle :
- D’afficher un message d’attente
- D’instancier le modèle de données et de s’y abonner
- De vérifier la présence dans le cache du fichier all.json
- et de donner la main à la 1ere vue qui affichera les données
Code Snippet
- <UserControl
- x:Class="UrzaGatherer.Views.ExtendedSplachScreen"
- xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation"
- xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml"
- xmlns:local="using:UrzaGatherer.Views"
- xmlns:d="https://schemas.microsoft.com/expression/blend/2008"
- xmlns:mc="https://schemas.openxmlformats.org/markup-compatibility/2006"
- mc:Ignorable="d"
- d:DesignHeight="1366"
- d:DesignWidth="768">
- <Grid Background="White">
- <Grid.RowDefinitions>
- <RowDefinition/>
- <RowDefinition Height="180"/>
- </Grid.RowDefinitions>
- <Canvas Grid.Row="0">
- <Image x:Name="extendedSplashImage" Source="../Assets/splashscreen.png"/>
- </Canvas>
- <StackPanel HorizontalAlignment="Center" Grid.Row="1">
- <ProgressRing IsActive="True" FontSize="40" Foreground="Gray"/>
- <TextBlock Foreground="Gray" FontFamily="Segoe UI Light" FontSize="40" TextWrapping="Wrap" TextAlignment="Center" Padding="5" HorizontalAlignment="Center" Text="Loading data... please wait" />
- </StackPanel>
- </Grid>
- </UserControl>
Code Snippet
- namespace UrzaGatherer.Views
- {
- public sealed partial class ExtendedSplachScreen : UserControl
- {
- private Rect splashImageCoordinates; // Rect to store splash screen image coordinates.
- private SplashScreen splash; // Variable to hold the splash screen object.
- private bool dismissed = false; // Variable to track splash screen dismissal status.
- private CoreDispatcher _dispatcher;
- public ExtendedSplachScreen(SplashScreen splashScreen, bool dismissed)
- {
- this.InitializeComponent();
- this.splashImageCoordinates = splashScreen.ImageLocation;
- this.splash = splashScreen;
- this.dismissed = dismissed;
- // Position the extended splash screen image in the same location as the splash screen image.
- this.extendedSplashImage.SetValue(Canvas.LeftProperty, this.splashImageCoordinates.X);
- this.extendedSplashImage.SetValue(Canvas.TopProperty, this.splashImageCoordinates.Y);
- this.extendedSplashImage.Height = this.splashImageCoordinates.Height;
- this.extendedSplashImage.Width = this.splashImageCoordinates.Width;
- _dispatcher = Window.Current.Dispatcher;
- // LoadDataAsync();
- }
- internal void splashScreen_Dismissed(SplashScreen sender, object args)
- {
- this.dismissed = true;
- //We have to synchronize with the main thread
- _dispatcher.InvokeAsync(CoreDispatcherPriority.Normal,
- new InvokedHandler((Object aSender, InvokedHandlerArgs e) =>
- {
- LoadDataAsync();
- }), this, null);
- }
- async void LoadDataAsync()
- {
- UrzaGathererDataSource dataSource = new UrzaGathererDataSource();
- dataSource.CardsLoaded += dataSource_CardsLoaded;
- if (!await dataSource.IsCardsOnCache())
- {
- dataSource.LoadRemoteCardsAsync();
- }
- else
- {
- dataSource.ReadJsonDataFromCacheAsync();
- }
- }
- void dataSource_CardsLoaded(object sender, CardsLoadEnventArgs e)
- {
- var rootFrame = new Frame();
- rootFrame.Navigate(typeof(Home), e.Blocks);
- // Place the frame in the current Window and ensure that it is active
- Window.Current.Content = rootFrame;
- Window.Current.Activate();
- }
- }
- }
Une fois que l’évènement CardsLoaded est levée, la méthode dataSource_CardLoaded() se déclenche. C’est elle qui à l’aide de l’élément Frame, va naviguer vers la page Home, en lui passant en paramètre, e.Blocks, contenant les données à afficher.
Vous noterez que nous utilisons la classe Frame, qui contiendra tout l’historique de navigation. Pas la peine alors de redévelopper son propre système, si vous souhaitez faire au plus vite.
Création de la page d’accueil
La 1ere page que l’on nommera Home, aura pour but d’afficher , sous forme d’une collection groupée, les blocs et leurs logos , ainsi que les expansions associées à chaque bloc.
Ici nous allons ajouter une nouvelle page, basée sur le modèle Grouped Items Page, car il nous fournit un bon point de démarrage pour afficher les données sous forme d’une collection de données groupées.
Une fois créée, nous modifions la liaison de données des éléments par défaut, par nos propres éléments de type expansion contenus dans un bloc.
Modifions l’attribut ItemsPath=”items” du CollectionViewSource, par notre ItemsPath=”expansions” , comme illustré sur l’extrait de XAML suivant.
Code Snippet
- <UserControl.Resources>
- <!-- Collection of grouped items displayed by this page -->
- <CollectionViewSource
- x:Name="groupedItemsViewSource"
- Source="{Binding Groups}"
- IsSourceGrouped="true"
- ItemsPath="expansions"/>
- </UserControl.Resources>
Note : expansions, étant définie comme une propriété de type ObservableCollection<JSONExpansion> de la classe JSONBlock
Code Snippet
- public ObservableCollection<JSONExpansion> expansions { get; set; }
La liaison proprement dite Source=”{Binding Groups}” est assurée, sur l’évènement OnNavigated de la page.
Code Snippet
- protected override void OnNavigatedTo(NavigationEventArgs e)
- {
- this.DefaultViewModel["Groups"] = e.Parameter;
- (semanticZoom.ZoomedOutView as ListViewBase).ItemsSource = groupedItemsViewSource.View.CollectionGroups;
- }
Note : La page dérive de la classe LayoutAwarePage, qui entre autre offre un modèle de vue par défaut, nommé DefaultViewModel, et qui assure la liaison de données en liant les données avec le DataContext de la page. Le modèle s’appelle Groups, mais il peut bien évidement prendre le nom que vous souhaitez, pour peut que vous n’oubliez pas de le changer également dans le CollectionViewSource du fichier XAML.
L’affichage de nos données, se fait dans une GridView, qui est liée par défaut, avec le CollectionViewSource=”groupedItemsViewSource”. Cette GridView, sera constituée d’un “Header” liée aux blocs, et d’un panel d’éléments qui sera liées aux expansions. D’ou la présence de deux Templates SemanticZoomOutItemTemplate et SemanticZoomOutHeaderTemplate qui vont influer sur la manière dont les données seront affichées.
Code Snippet
- <GridView
- IsItemClickEnabled="True"
- ItemClick="ZoomedInGridView_ItemClick"
- SelectionMode="None"
- x:Name="ZoomedInGridView"
- Margin="0,0,0,46"
- ItemsSource="{Binding Source={StaticResource groupedItemsViewSource}}"
- ItemTemplate="{StaticResource SemanticZoomOutItemTemplate}"
- >
- <GridView.Transitions>
- <TransitionCollection>
- <AddDeleteThemeTransition></AddDeleteThemeTransition>
- </TransitionCollection>
- </GridView.Transitions>
- <GridView.ItemsPanel>
- <ItemsPanelTemplate>
- <VirtualizingStackPanel Orientation="Horizontal" Margin="116,0,0,0"/>
- </ItemsPanelTemplate>
- </GridView.ItemsPanel>
- <GridView.GroupStyle>
- <GroupStyle HeaderTemplate="{StaticResource SemanticZoomOutHeaderTemplate}">
- <GroupStyle.Panel>
- <ItemsPanelTemplate>
- <VariableSizedWrapGrid Orientation="Vertical" Margin="0,0,80,0"/>
- </ItemsPanelTemplate>
- </GroupStyle.Panel>
- </GroupStyle>
- </GridView.GroupStyle>
- </GridView>
Par défaut l’attribut ItemTemplate, est liée avec le DataTemplate “Standard250x250ItemTemplate” contenu dans le fichier StandardStyles.xaml.
Nous allons modifier ce modèle afin que la liaison de données ainsi que l’affichage se fasse avec les éléments JSONBlock et JSONExpansion, pour ce faire, il suffit d’ajouter aux styles standard le XAML suivant :
Code Snippet
- <Style x:Key="SemanticZoomOutControlContentStyle" TargetType="Button">
- <Setter Property="Template">
- <Setter.Value>
- <ControlTemplate TargetType="Button">
- <Border x:Name="Border" Background="Transparent" BorderThickness="2,2,2,2" BorderBrush="Black">
- <Grid HorizontalAlignment="Left" Width="250" Height="125">
- <Grid.RowDefinitions>
- <RowDefinition Height="95"/>
- <RowDefinition Height="30"/>
- </Grid.RowDefinitions>
- <Viewbox Stretch="Uniform" Width="Auto" Height="Auto" Grid.Row="0" VerticalAlignment="Center" HorizontalAlignment="Center">
- <Image Source="{Binding Path=logo}" Stretch="Fill"/>
- </Viewbox>
- <Canvas Background="#FF2A2A2A" Grid.Row="1" VerticalAlignment="Stretch" >
- <TextBlock Text="{Binding name}" Foreground="{StaticResource ListViewItemOverlayTextBrush}"
- VerticalAlignment="Center" Style="{StaticResource TitleTextStyle}" Margin="10,0,0,0"/>
- </Canvas>
- </Grid>
- <VisualStateManager.VisualStateGroups>
- <VisualStateGroup x:Name="CommonStates">
- <VisualState x:Name="Normal"/>
- <VisualState x:Name="PointerOver">
- <Storyboard>
- <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="BorderBrush" Storyboard.TargetName="Border">
- <DiscreteObjectKeyFrame KeyTime="0" Value="#FF7900FF"/>
- </ObjectAnimationUsingKeyFrames>
- <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="BorderThickness" Storyboard.TargetName="Border">
- <DiscreteObjectKeyFrame KeyTime="0" Value="4,4,4,4"/>
- </ObjectAnimationUsingKeyFrames>
- </Storyboard>
- </VisualState>
- </VisualStateGroup>
- </VisualStateManager.VisualStateGroups>
- </Border>
- </ControlTemplate>
- </Setter.Value>
- </Setter>
- </Style>
- <DataTemplate x:Key="SemanticZoomOutItemTemplate">
- <Button Style="{StaticResource SemanticZoomOutControlContentStyle}" />
- </DataTemplate>
- <DataTemplate x:Key="SemanticZoomOutHeaderTemplate">
- <Grid Margin="1,0,0,6">
- <Grid.ColumnDefinitions>
- <ColumnDefinition Width="150"/>
- <ColumnDefinition Width="100"/>
- </Grid.ColumnDefinitions>
- <TextBlock Height="50" HorizontalAlignment="Left" VerticalAlignment="Center" Margin="0,15,0,0" Text="{Binding name}" Style="{StaticResource HeaderHomePageTextStyle}" FontWeight="ExtraLight" Grid.Column="0" />
- <Image Source="{Binding logo}" Width="60" Height="60" Grid.Column="1" HorizontalAlignment="Right" VerticalAlignment="Top" Margin="0,0,0,0"></Image>
- </Grid>
- </DataTemplate>
- <DataTemplate x:Key="SemanticZoomInItemTemplate">
- <Grid HorizontalAlignment="Left" Width="300" Height="150" Background="#FF7900FF">
- <TextBlock Text="{Binding Group.name}" HorizontalAlignment="Center" FontFamily="Segoe UI Light" FontSize="26.667" />
- <Image Source="{Binding Group.logo}" Width="50" Height="50" Stretch="UniformToFill" />
- </Grid>
- </DataTemplate>
La liaison de données se faisant sur les champs logo (lié à un contrôle Image) et name ( lié à un contrôle TextBlock) , deux propriétés de nos blocs et de nos expansions. Vous noterez également la présence d’un VisualStateManager, qui permet de changer la bordure de l’élément (ici un bouton) à l’aide d’une animation, lorsque la souris passe au dessus de l’élément.
Pour les plus aguerrit, ils auront noté également la présence du style SemanticZoomInItemTemplate, que nous utiliseront lorsque nous mettrons en place le SemanticZoom.
Ajout du Zoom Sémantique
Comme nous venons de le voir, nos données sont structurées dans une GridView, organisée avec un entête constitué de nos blocs, et des expansions rattachées à chaque bloc. L’idée avec la vue sémantique, c’est de rajouter une autre GridView, qui affichera, comme on le voie sur l’image une vue filtrée par entête et qui permettra de naviguer rapidement sur le bloc sélectionné.
Cette fonctionnalité est appelée zoom sémantique (https://msdn.microsoft.com/en-us/library/windows/apps/hh465492.aspx) dans le jargon de développement d’une application Windows 8 Style Metro.
Pour ce faire nous allons utiliser tout simplement le contrôle XAML SemanticZoom, qui encapsulera dans la vue ZoomedInView, la 1ere GridView et dans la vue ZoomOutView la seconde GridView.
Cette seconde GridView, sera liée au modèle SemanticZoomInItemTemplate, que nous avons vu au préalablement.
Il est important de noter que la Liaison, utilise les chemins Group.name, et Group.logo, afin que l’affichage des données se fasse correctement.
Code Snippet
- <SemanticZoom IsZoomedInViewActive="True" Grid.Row="1" x:Name="semanticZoom">
- <SemanticZoom.ZoomedInView>
- <GridView
- IsItemClickEnabled="True"
- ItemClick="ZoomedInGridView_ItemClick"
- SelectionMode="None"
- x:Name="ZoomedInGridView"
- Margin="0,0,0,46"
- ItemsSource="{Binding Source={StaticResource groupedItemsViewSource}}"
- ItemTemplate="{StaticResource SemanticZoomOutItemTemplate}"
- >
- <GridView.Transitions>
- <TransitionCollection>
- <AddDeleteThemeTransition></AddDeleteThemeTransition>
- </TransitionCollection>
- </GridView.Transitions>
- <GridView.ItemsPanel>
- <ItemsPanelTemplate>
- <VirtualizingStackPanel Orientation="Horizontal" Margin="116,0,0,0"/>
- </ItemsPanelTemplate>
- </GridView.ItemsPanel>
- <GridView.GroupStyle>
- <GroupStyle HeaderTemplate="{StaticResource SemanticZoomOutHeaderTemplate}">
- <GroupStyle.Panel>
- <ItemsPanelTemplate>
- <VariableSizedWrapGrid Orientation="Vertical" Margin="0,0,80,0"/>
- </ItemsPanelTemplate>
- </GroupStyle.Panel>
- </GroupStyle>
- </GridView.GroupStyle>
- </GridView>
- </SemanticZoom.ZoomedInView>
- <SemanticZoom.ZoomedOutView>
- <GridView
- x:Name="ZoomedOutGridView" Margin="116,0,40,46"
- ItemTemplate="{StaticResource SemanticZoomInItemTemplate}" />
- </SemanticZoom.ZoomedOutView>
- </SemanticZoom>
Enfin par code, nous allons indiqué sur l’évènement OnNavigatedTo() de la page Home, que la vue ZoomedOutView, doit se lier à source de données, contenant les entêtes de la Collection, c’est à dire nos blocs. C’est ce qui permettra cette navigation rapide d’un bloc à un autre.
Code Snippet
- (semanticZoom.ZoomedOutView as ListViewBase).ItemsSource = groupedItemsViewSource.View.CollectionGroups;
Note : Avec la souris, le zoom sémantique, s’active par CTRL + MOLETTE.
Enfin, il faut mettre en musique tout ce petit monde, sur l’évènement OnLaunched du fichier App.xaml.cs, ajoutez le code suivant :
Code Snippet
- protected override void OnLaunched(LaunchActivatedEventArgs args)
- {
- if (args.PreviousExecutionState == ApplicationExecutionState.Terminated)
- {
- //TODO: Load state from previously suspended application
- }
- SplashScreen splashScreen = args.SplashScreen;
- ExtendedSplachScreen exSplash = new ExtendedSplachScreen(splashScreen, false);
- splashScreen.Dismissed += new TypedEventHandler<SplashScreen, object>(exSplash.splashScreen_Dismissed);
- Window.Current.Content = exSplash;
- Window.Current.Activate();
- }
A Suivre :
Le prochain article introduira les fonctionnalités suivantes :
•La page des cartes
•La page des extensions
•Gestion du mode offline
•Gestion des settings
Comments
Anonymous
May 22, 2012
A quand la suite !!! :)Anonymous
May 22, 2012
Bonjour Sylvain, Le Day 1 est bientot la !! d'autant plus que je viens de passer la nuit à me casser les dents sur un @"&`~~ de problème de *** ;-) Mais elle arriveAnonymous
May 23, 2012
Vivement la suite ^^ Y en aura t'il d'autre à venir ? dans le même format mais sur d'autre thème. Enfin toujours lié à Windows8 (en C# de préférence.) mais par exemple : sur l'utilisation combiné avec azure / partage de donnée entre périphériques. WP / W8, la realisation d'une application en utilisant des techno comme live sdk ou skydrive.Anonymous
May 24, 2012
The comment has been removedAnonymous
May 26, 2012
J'attends la mise off line avec impatience… as-tu un lien pour me permettre d'avancer ? Merci d'avanceAnonymous
May 03, 2013
Vraiment très intéressant! Pensez vous faire la suite?Anonymous
August 13, 2013
Super ca va m'etre vraiment utile ! Par contre, quelqu'un ne connaitrai pas un gestionnaire de VM pour Vista car comme je n'ai pas Windows 8 et que mes parents ne sont pas d'accord pour l'acheter (Je precise que je ne suis pas majeur) ? PS : A l'heure ou j'ecris ce message je suis sur un clavier QWERTY d'ou certaines erreurs d'accents.