• Stéphanie Hertrich

    Et pendant ce temps…

    • 5 Comments

    20x30_2012-09-03_18-23-30_A5C1722-M

    Vous l’aurez peut-être noté, je ne suis plus très active sur mon blog, les évènements communautaires et les réseaux sociaux ces derniers temps.
    En effet, pendant que Microsoft peaufine Windows 8 pour le 26 octobre, je prépare l’arrivée d’un petit garçon pour…la même date à 2 jours près !

    Je vous donne donc rendez-vous dans quelques mois pour de nouveaux défis avec plein de nouveautés d’ici là !

    Soyez sages !

     

    Et un grand merci à ma photographe préférée !

  • Stéphanie Hertrich

    Windows 8 et async/await : Attention aux accès fichiers

    • 1 Comments

    Grâce à async/await, l’écriture de code asynchrone devient un jeu d’enfant…à tel point que l’on oublie parfois d’y protéger l’accès aux ressources critiques. Ce genre d’oubli provoque des erreurs d’exécution totalement aléatoires dans les applications mais la bonne nouvelle c’est que c’est très facile à corriger, surtout une fois que l’on a compris pourquoi Smile.

    Un exemple typique dans les applications Windows 8 : l’accès aux fichiers.

    On les utilise à plus forte raison dans les applications Metro, comme par-exemple pour :

    • le fichier servant de cache off-line
    • le fichier de sauvegarde du contexte courant de l’application
    • le fichier contenant les données applicatives

    Plusieurs clients m’ont remonté le fait que des exceptions sont déclenchées lors des écritures fichier, or le plus souvent, le problème provient de l’écriture du code asynchrone correspondant.

    Une démo vaut mieux que de longs discours, je vais donc illustrer ces propos pas à pas.

    D’abord, voyons le bout de code que je vais utiliser : un simple bouton dans l’appbar qui déclenche un accès fichier en écriture.

    image

            
        private void Save_Click(object sender, RoutedEventArgs e)
        {
           SaveAsync();
        }
    
        // Write data to a file
        async void SaveAsync()
        {
           StorageFolder localFolder = Windows.Storage.ApplicationData.Current.LocalFolder;
    
           var sampleFile = await localFolder.CreateFileAsync("dataFile.txt", CreationCollisionOption.OpenIfExists);
           var ws = await sampleFile.OpenStreamForWriteAsync();
    
     
           // Ecriture dans mon fichier, blablabla…
           ws.Dispose();
        }
     

    Au lancement de l’application, si l’on appuie plusieurs fois d’affilée sur le bouton “Save” en principe il n’y a pas de souci particulier car la méthode SaveAsync s’exécute dans un délai très court et a donc le temps de se terminer avant qu’on l’a réexécute.

    A présent, simulons un accès fichier plus long (5 secondes) entre l’ouverture du fichier en écriture et sa fermeture:       

           // Write data to a file
       async void SaveAsync()
       {
           StorageFolder localFolder = Windows.Storage.ApplicationData.Current.LocalFolder;
    
           var sampleFile = await localFolder.CreateFileAsync("dataFile.txt", CreationCollisionOption.OpenIfExists);
           var ws = await sampleFile.OpenStreamForWriteAsync();
    
           await Task.Delay(5000);
    
           ws.Dispose();
       }
     
     Cette fois, si je clique 2 fois sur le bouton en moins de 5 secondes, une exception sera levée:
    image
    Pourquoi ? Parce que je dois garantir un accès exclusif au fichier ! Et pour l’instant ce n’est pas le cas.
    1er réflexe : je désactive mon bouton le temps de réaliser la sauvegarde, sur l’evt click.  

    Protection par l’UI

    A votre avis, quelle est la différence entre ces 2 snippets : 

    Snippet 1 :

            private void Save_Click(object sender, RoutedEventArgs e)
            {
                btSave.IsEnabled = false;
                SaveAsync();
                btSave.IsEnabled = true;
            }
    
            // Write data to a file
            async void SaveAsync()
            {
    
                StorageFolder localFolder = Windows.Storage.ApplicationData.Current.LocalFolder;
    
                var sampleFile = await localFolder.CreateFileAsync("dataFile.txt", CreationCollisionOption.OpenIfExists);
                var ws = await sampleFile.OpenStreamForWriteAsync();
    
                await Task.Delay(5000);            
                ws.Dispose();
            }
    

    Snippet 2 :

            private void Save_Click(object sender, RoutedEventArgs e)
            {
                SaveAsync();
    
            }
    
            // Write data to a file
            async void SaveAsync()
            {
                btSave.IsEnabled = false;
    
               StorageFolder localFolder = Windows.Storage.ApplicationData.Current.LocalFolder;
    
                var sampleFile = await localFolder.CreateFileAsync("dataFile.txt", CreationCollisionOption.OpenIfExists);
                var ws = await sampleFile.OpenStreamForWriteAsync();
    
                await Task.Delay(5000);
    
                ws.Dispose();
    
            btSave.IsEnabled = true;
        }
    

     

    On pourrait penser que l’exécution de ces snippets sera équivalente, mais ce n’est pas le cas.

    La commande await permet de synchroniser un appel asynchrone avec les lignes de code qui la suivent, mais il ne faut pas oublier que la méthode appelante – elle – ne sera pas bloquée, reprendra la main et continuera à s’exécuter. C’est d’ailleurs ce qui permet au thread de l’UI de ne pas être bloqué pendant ce temps.

    Cela signifie que l’on pourra déclencher plusieurs sauvegardes simultanées en appuyant sur le bouton “Save” dans le cas du 1er snippet, alors que dans le second, le bouton de sauvegarde sera désactivé tant que l’appel asynchrone ne se sera pas terminé.

    Ainsi le premier snippet est totalement inefficace pour protéger l’accès au fichier !

    Donc, rappelez-vous que lors d’un appel asynchrone avec async/await, on retourne immédiatement dans la fonction appelante sans attendre la fin de l’appel asynchrone !

    Le snippet 2 peut être suffisant dans de nombreux cas, mais il ne faut pas oublier que vous empêchez ici les accès concurrents au bouton btSave, et non pas les accès concurrents au fichier !

    Très souvent, une sauvegarde intervient automatiquement sans que celle-ci soit commandée par l’UI : c’est d’autant plus le cas sur Windows 8 où le contexte courant est supposé être maintenu au suspend, à la fermeture de l’application ou tout simplement régulièrement au fil de l’utilisation de l’application.

     

    Protection par le code métier

    Dans notre exemple nous avons couplé le code métier à l’UI en accédant au bouton btSave dans la méthode SaveAsync.

    Il est plus propre de protéger l’accès au fichier au niveau du code métier, tout en restant indépendant de l’UI c’est à dire directement dans la méthode SaveAsync mais sans interagir avec la Vue.

    Justement, dans mon exemple j’ai besoin de déclencher une sauvegarde automatique lors du passage dans l’état “suspend” de mon application. Ce nouvel état - nouvellement introduit pour les applications Win8 Metro - intervient 10 secondes après qu’une application passe en arrière plan. Le système peut à tout moment arrêter une application qui se trouve dans l’état suspend, s’il est en manque de ressources. Pour le confort de l’utilisateur, il est conseillé de sauvegarder le contexte applicatif lors du passage en “suspend” d’une application Metro, ce qui permettra de rétablir ce contexte en cas de besoin quand l’application seront relancée.

    Dans notre cas, l’appel du SaveAsync sur le passage dans l’état suspend se fait à l’aide du code suivant:

            public MainPage()
            {
                this.InitializeComponent();
                App.Current.Suspending += Current_Suspending;
            }
    
            void Current_Suspending(object sender, Windows.ApplicationModel.SuspendingEventArgs e)
            {
                SaveAsync();
            }

    Si vous lancez votre application, que vous cliquez sur le bouton “Save” puis passez immédiatement en état suspend grâce au bouton associé en mode debug dans Visual Studio, vous obtiendrez une erreur d’exécution.

    image

     

    Pour y remédier je vais protéger l’accès à ma ressource critique à l’aide d’un sémaphore : l’objet SemaphoreSlim qui permet de mettre en place un verrou asynchrone (comme un objet Semaphore mais que l’on peut attendre avec un await).

    Snippet 3 :       

            SemaphoreSlim _semSave = new SemaphoreSlim(1);
    
            private void Save_Click(object sender, RoutedEventArgs e)
            {
                btSave.IsEnabled = false;
                SaveAsync();
                btSave.IsEnabled = true;
            }        
    
            // Write data to a file
            async void SaveAsync()
            {
    
                await _semSave.WaitAsync();
    
                StorageFolder localFolder = Windows.Storage.ApplicationData.Current.LocalFolder;
    
                var sampleFile = await localFolder.CreateFileAsync("dataFile.txt", CreationCollisionOption.OpenIfExists);
                var ws = await sampleFile.OpenStreamForWriteAsync();
    
                await Task.Delay(5000);
    
                ws.Dispose();
    
                _semSave.Release();
            }
    

    Avec ce snippet, l’accès au fichier est protégé, quel que soit l’origine de l’appel de la méthode SaveAsync.

    Le code de désactivation du bouton “Save” reste pertinent : il faut un feedback visuel permettant à l’utilisateur de savoir quand la sauvegarde est terminée. Mais elle n’est pas suffisante en tant que telle et demeure complémentaire à la protection de la ressource critique.

    Le hasard faisant bien les choses, Rudy Huyn (MVP Windows Phone) a publié au même moment un article sur ce thème à propos de Windows Phone. Je vous le recommande vivement !

     
     
  • Stéphanie Hertrich

    Mémo Win8 : Préparer la publication sur le Store

    • 0 Comments

    Voir le mémo complet

    [MAJ le 07/06/2012]

    Le Store permet de diffuser une application publique suivant différents modèles de tarification.

    Exemple de visualisation par l’utilisateur d’une application disponible sur le Store:

    Cut the Rope / Store > Games > Puzzle / ***** Average rating (1023) / $1.49 / Buttons: Buy / Try / Image and description of app

     

    Dans le cas d’applications métier destinées à un usage interne à l’entreprise, il est possible de faire du “side loading” c’est à dire un déploiement direct d’une application sans passer par le Store. Plus d’informations et mode d’emploi.

     

    Choisissez votre modèle de tarification

    - Application complète gratuite ou payante

    - Application payante avec trial mode gratuit - How to create a trial version of your app

    - Application avec in-app purchase : vous utilisez votre propre infrastructure de facturation plutôt que celle du Store - How to support in-app purchases from your app

    Plus d’information sur les modèles de tarification et comment faire son choix

     

    Testez l’interaction de votre application avec le Store

    Lors du développement de votre application, vous pouvez simuler son interaction avec le Store en utilisant l’objet CurrentAppSimulator plutôt que l’objet Store réel.

    Exemple d’interaction avec le Store: vous proposez d’acheter la version complète de votre application à l’aide d’un bouton dans les settings.

    Vous trouverez un exemple de code utilisant le simulateur ici.

     

    Préparez les images pour promouvoir votre application sur le Store

    Vous aurez besoin de copies d’écran de votre application, mais également d’images qui seront utilisées pour la promotion de votre application sur le Store.

    Prévoyez des images au format .png de très bonne qualité.

    Copies d’écran

    Prévoyez au moins une copie d’écran de votre application en cours de fonctionnement. Si votre application est disponible en plusieurs langues, il faudra une version d’image par langue avec le contenu traduit.

    Images promotionnelles

    Préparer des .png pour la promotion sur le store dans les formats suivants (le 414x180 est un minimum):

    image

     

    Les points importants à vérifier pour passer le processus de validation

    En plus des pré-requis officiels, vous trouverez ici des recommandations complémentaires qui correspondent aux principaux écueils rencontrés par les premiers développeurs ayant soumis leur application sur le Store.

    Les pré-requis officiels

    Voici la liste officielle des points à respecter pour permettre à votre application d’être acceptée sur le Store : Certification requirements for Windows apps

     

    Le kit de certification

    L’application doit passer les tests du kit de certification.

    Celui-ci peut être exécuté soit après la création du package pour le Store de votre application directement depuis Visual Studio, soit depuis l’écran d’accueil de Windows 8 :

    image

    Pour que l’application ne soit pas rejetée lors de la soumission, il faut obtenir un résultat PASSED ou  PASSED WITH WARNINGS

    image image

    Plus d’information: How to test your app with the Windows App Certification Kit

     

    Toutes les fonctionnalités proposées pour une version d’application doivent être disponibles dans cette même version

    Votre application ne doit pas proposer des fonctionnalités potentiellement disponibles dans de futures versions qui seraient désactivées dans la version courante (“coming soon”, “more to come”, “not available yet”, …).

    Chaque fonctionnalité doit être disponible dans toutes les langues supportées par l’application.

    Si votre application nécessite des informations particulières (comptes d’identité, ..) pour activer certaines fonctionnalités, n’oubliez pas de les mentionner dans les notes pour les testeurs.

     

    Votre application ne doit pas planter

    Vérifiez le bon fonctionnement de votre application sur la dernière version publiée de Windows 8.

    Utilisez la version Release de votre application pour faire vos tests : si vous faites tout le développement en mode Debug et que vous générez la version Release juste pour la publication, vous risquez d’avoir de mauvaises surprises.

     

    L’application doit fonctionner au doigt (et à l’œil !), au clavier et à la souris

    Support du touch, du clavier et de la souris indispensables.

     

    Politique de confidentialité

    Si votre application récolte ou diffuse des informations personnelles ou permet l’accès à des services Internet, vous devez informer l’utilisateur de votre politique de confidentialité.

    L’URL vers cette politique doit être mentionnée sur la page de publication de l’application et sera visible par les utilisateurs dans le Store.

    Si votre application utilise la géolocalisation, les SMS, la caméra ou le micro, vous devez également proposer un lien vers la politique de confidentialité dans les settings de votre application.

    Your privacy policy must (i) comply with applicable laws and regulations, (ii) inform users of the information collected by your app and how that information is used, stored, secured and disclosed, and (iii) describe the controls that users have over the use and sharing of their information, and how they may access their information

    Vous pouvez vous baser sur la document ci-dessous pour créer votre policy : n’oubliez pas de remplacer les références à « The application » par le nom de votre application.

    Fournir des informations de support technique

    Lors de la publication sur le Store, vous devez fournir un contact pour le support technique de votre application.

    Cela peut être une URL (vers un site qui n’est pas en construction !) ou une adresse mail valide.

     

    La langue par défaut

    Elle sera utilisée comme fallback dans le cas où l’utilisateur utilise une langue indisponible dans votre application.

    La langue par défaut de votre application est égale à la langue d’installation de Visual Studio.

    Si le contenu de votre application n’est proposé que dans une seule langue, la langue par défaut doit aussi être la même.

    C’est souvent une cause de rejet des applications sur le Store.

    Exemple:

    Pour une application française de type journal/magazine, le contenu n’est disponible qu’en Français. Si l’application est développée avec la version EN de Visual Studio, il faut donc modifier la langue par défaut pour qu’elle soit français au lieu de Anglais qui est la valeur par défaut.

    Cela ne vous empêche pas de la diffuser sur les stores de pays autres que la France : la langue par défaut et le pays de soumission ne sont pas liés.

    La langue par défaut se définit dans Visual Studio, dans les propriétés du projet (.csproj).

    - Pour une app XAML, il faut éditer le fichier à la main et modifier la section correspondante: 

      <DefaultLanguage>fr-FR</DefaultLanguage>

    - Pour une app Html, il suffit de modifier directement la propriété du projet dans VS

    image 

    Compte de test

    Une grande partie de la phase de validation se fait manuellement.

    Pour les applications dont le fonctionnement nécessite un compte d’identité, il faut fournir un compte de test utilisable par l’équipe de validation, sans quoi votre application ne pourra être certifiée.

    Si votre application propose un contenu en fonction de l’âge (3+ / 13+), pensez à fournir un compte supplémentaire qui met en évidence le bon fonctionnement de la restriction.

     

    Politique de restriction liée à l’âge

    Le tableau suivant peut vous servir d’aide à la décision:

    Information collected /Capability accessed

    Provide access to your privacy policy in the app’s settings as displayed in the Windows settings charm

    Can Age Rating be lower than 12+?

    SMS

    Required

    No

    Text messages

    Required

    No

    Location

    Required

    No

    Microphone

    Required

    No

    Webcam

    Required

    No

    Documents Library

    Required

    No

    Internet connection (incoming or outgoing)

    Not Required but encouraged

    Yes

    Requires a sign in account where the name on the account is not required to be an email address or user’s name (in real life) but any made-up name

    Not Required but encouraged

    Yes

    Collects or transmits personal information: User’s Name, User’s Account, Email address, Phone #, Contacts, User’s Address

    Not Required but encouraged

    No

    Collects or transmits: Images of Computer Desktop or Screen Shots

    Not Required but encouraged

    No

    Collects or transmits: Browsing History

    Not Required but encouraged

    No

     

    Nommage de votre application

    Voici quelques conseils pour bien choisir le nom de votre application.

    Vérifiez les différents noms associés à votre application qui seront utilisés sur l’écran d’accueil et sur le Store. La configuration du nom d’une application se fait à différents niveaux : projet VS, portail développeur du Store.

    - Le “Product Name” (Identity Name) du portail développeur correspond à celui défini dans le manifest. Vous pouvez réserver le nom de votre application avant que celle-ci soit prête à être publiée. Voici comment changer le nom de votre application, si nécessaire.

    Dropdown list from Store menu contains "Open Developer account..." and "Reserve App Name"

    - Pour le Store, vous pouvez proposer un “Display Name” localisé et attaché à chaque langue supportée par votre application. Ce paramètre est configurable dans la section “Name” de “Reserve another name”. Vous n’avez pas besoin de soumettre une nouvelle application pour le modifier.

    - Le nom de l’application qui apparait sur l’écran d’accueil de Windows 8 se configure dans le manifest, dans l’attribut “ShortName”. Vous pouvez proposer un nom différent selon la langue, en utilisant la notation permettant d’adresser les ressources dans le manifest (ms-resource:).

    image

    - Si le nom de votre application est trop long, vous pouvez renseigner un “nom complet” (Package/ Applications/ Application/VisualElements @DisplayName), qui sera affiché dans le tooltip.

    image

     image

    Vous trouverez un tableau récapitulatif des règles d’utilisation des différents noms sur cette page.

    - N’utilisez pas de terme de type trial, beta, … dans le nom de votre application

     

    Un petit aperçu

    Pour vous faire une idée, voici un exemple de page de description complétée.

    A long form containing the following fields: Description, Description bullet points, Keywords, Description of update, Keywords, Copyright and trademark info, Additionallicense terms, Screenshots, Promotional images, REcommended hardware bullets, App website, Support contact info, Privacy Policy.

    Voir le mémo complet

  • Stéphanie Hertrich

    Mémo Win8 : La navigation

    • 0 Comments

    Voir le mémo complet

     

    La page principale de votre application (“Hub”) doit être conçue comme une couverture de magazine. Elle est généralement composée d’une ListView horizontale avec divers groupes représentées sous forme de colonnes mais peut aussi être totalement libre pour les applications non hiérarchiques comme des jeux.

    Tout comme pour un magazine, ces contenus doivent attirer l’oeil et donc être présentés de manière concise et claire, sous des formats divers et attrayants.

    An image showing an example hub page  Hh868271.flat_1(en-us,WIN.10).png

    La navigation se fera le plus souvent par une gesture sur un élément de la page : touch d’un header de section, pinch out pour zoom sémantique, touch d’un élément d’une grille, …

    Modes de navigation

    Hiérarchique

    Les mode de navigation conseillé et le plus utilisé est hiérarchique (Hub/Section/Detail) avec un maximum de 3 niveaux:

    Hub, section, and details pages in a Metro style app

     

    A plat

    La navigation à plat peut s’appliquer quand il y a peu d’informations et d’écrans à présenter (comme dans les jeux par-exemple).

    Flat navigation system in a Metro style app

     

    Dans ce cas, l’AppBar du haut (réservée dans tous les cas à la navigation) est accessible depuis toutes les pages et permet de passer facilement d’un contexte à un autre comme le propose Internet Explorer.

    Navigation bar and switching between multiple contexts

    Certains contrôles types vous aideront à mettre en place une navigation “à la Metro”:

    - le zoom sémantique

    - les menus flyout pour les headers de sections et les menus en général

    - le bouton Back

    - les ListView/Gridview

    - la FlipView couplée à une ListView pour la navigation maitre/détail

    - l’AppBar du haut, réservée à la navigation

    Pour  plus de détails sur les principes de navigation en général, je vous recommande cet article : Navigation design for Metro style apps.

     

    Conseils complémentaires

    Les sections

    Navigation entre sections

    Utilisez des menus flyout sur les headers de sections pour permettre la navigation entre sections et revenir à la page d’accueil.

    image

    Vous pouvez également utiliser l’AppBar du haut pour naviguer entre les différentes sections de l’application.

     

    Nombre d’éléments visibles sur le hub

    Pour informer l’utilisateur qu’il y a plus d’éléments dans une section que ceux affichés dans le Hub, vous pouvez:

    - Afficher le nombre d’élements à droite du header de section (recommandé)

    - Utiliser le dernier élément de la liste comme un lien vers la liste de tous les éléments : ex: “Voir +”

     

    Le zoom sémantique

    Le zoom sémantique permet de visualiser toutes les sections du niveau hiérarchique courant en 1 seule page en les regroupant selon des critères signicatifs.

    Navigating between levels in a hierarchy with semantic zoom

    Le zoom sémantique est conseillé à partir de 5 sections et obligatoire à partir de 10.

    N’hésitez pas à être créatif et à proposer certaines informations détaillées comme dans le titre du regroupement ou dans la taille de la zone pour refléter le nombre d’éléments par-exemple.

    Example of a zoomed-in layoutExample of a zoomed-out layout

    Example of a zoomed-in layoutExample of a zoomed-out layout

    Plus d’informations sur le zoom sémantique : Guidelines for Semantic Zoom

     

    Les filtres/tris/regroupements

    Ils doivent être proposés à partir des commandes de l’AppBar du bas à l’aide de menus flyout.

    Filtering in an app bar

    Dans le cas où ils sont la principale fonction de la page, il est possible de les faire apparaitre directement au-dessus des résultats, un peu comme dans un résultat de recherche.

    Filtering in a store app

    Sorting in a store app

     

    La FlipView

    La FlipView permet de naviguer aisément entre les pages de détail d’une collection d’éléments en fournissant un moyen de passer à l’élement suivant/précédent, comme pour les pages d’un catalogue.

    Image of a FlipView control

    Elle est conseillée quand le nombre d’éléments à présenter est inférieur à 25.

    Au-delà, vous pouvez coupler une ListView à une FlipView au sens maitre/détail : la ListView représente les éléments de manière sommaire et la FlipView permet d’accéder au détail d’un élément et de naviguer entre ses voisins directement.

     

    Les grid, scrollviewers, …

    Vérifiez que les actions de navigation sur la grille ne déclenchent pas accidentellement d’action sur les éléments contenus par celle-ci.

     

    Le bouton Back

    Il doit toujous être placé en haut à gauche de la page et doit permettre de revenir à la page précédente.

    Sur les vues plein écran, il peut être déplacé dans l’AppBar du haut de la page.

    S’il est désactivé, il doit être invisible comme pour la première page de l’application par-exemple.

     

    Lors d’un changement de page

    Une page ne doit jamais mettre plus de 5 secondes pour s’afficher.

    Voir le mémo complet

  • Stéphanie Hertrich

    Mémo Win8 : Où placer les commandes dans mon application ?

    • 1 Comments

    Voir le mémo complet

     

    Voici une règle qui résume bien la philosophie du positionnement des commandes dans les applications Windows 8 Metro :

    Une commande ne peut apparaitre directement sur une page que si l’action associée est indispensable au fonctionnement de la page ou qu’elle correspond à la fonction principale de cette page. Ex: un bouton “Envoyer” sur la page de création d’un email.

    Dans ce cas, préférez un positionnement accessible facilement sur une tablette.

    Interaction areas

     

    De manière générale, les autres commandes se retrouveront dans :

    - l’AppBar située en haut de la page pour la commandes de navigation

    - l’AppBar située en bas de la page pour les autres commandes

    Navigating with the edge swipe

    Il existe néanmoins des exceptions pour:

    - le bouton Back pour revenir à la page précédente

    - les commandes associées à des éléments non sélectionnables ou au Presse-Papier (copier/coller, …)

    - les commandes de gestion de compte (connexion/déconnexion)

    - les commandes de paramétrage, la recherche et le partage d’information

     

    L’AppBar du haut pour la commandes de navigation

    Dans les applications Metro, la plupart des actions explicites de navigation disparaissent au profit d’une gesture sur un élément de la page:

    - touch/click d’un élément d’une grid

    - touch/clic d’un header de section

    - pich-out pour zoom sémantique

    -  ….

    L’AppBar située en haut de la page permet de rassembler les autres fonctions de navigation (si besoin).

    Navigation bar and switching between multiple contexts

    La navigation fera l’objet d’un mémo complet à paraitre prochainement.

     

    L’AppBar du bas pour les autres commandes

    Toutes les autres commandes qui sont secondaires au fonctionnement de la page et dont l’utilisation sera facultative pour l’utilisateur se retrouveront dans l’App Bar du bas.

    Si vous hésitez ou que vous avez beaucoup de commandes importantes, une solution intermédiaire serait de placer vos commandes dans l’AppBar du bas et de forcer l’apparition de celle-ci à l’affichage de la page.

    Chaque commande de l’AppBar doit être représentée par un glyph (voir table des caractères). Je vous conseille l’article de Jonathan Antoine qui permet de les avoir tous sous les yeux dans ce document.

     

    Règles d’utilisation de l’AppBar du bas

    Groupez vos commandes de manière fonctionnelle

    Si vous avez 2 groupes, placez-en un aligné à gauche, l’autre à droite. Si vous avez plus de 2 groupes, utilisez des séparateurs et alignez-les à droite.

    Hh761499.com_ico_appbar2(en-us,WIN.10).png

    Placez toujours vos commandes de la même manière sur les différentes pages de votre application.

    Il est recommandé d’afficher les commandes Supprimer | Nouveau,  Retirer | Ajouter, ou « Clear » sur le côté droit.

     

    Commandes contextuelles à un élément sélectionné

    Il est conseillé de faire apparaitre l’App Bar automatiquement pour visualiser les commandes contextuelles à l’élément sélectionné. Placez ces commandes contextuelles sur la gauche en décalant les autres commandes déjà présentes dans l’AppBar et en ajoutant un séparateur.

    Hh761499.com_ico_appbar4(en-us,WIN.10).png

     

    Trop de commandes ?

    Si vous n’avez pas assez de place dans l’AppBar, regroupez des commandes entre elles suivant leurs fonctionnalités et affichez-les dans un menu flyout piloté par un bouton de l’AppBar.

    Filtering in an app bar

     

    Gestion du mode snap et du mode Portrait

    Vérifiez le bon comportement de l’AppBar en mode Snap et Portrait où le nombre de commandes visibles est plus limité.

    Vous pouvez modifier le nombre de commandes dans l’AppBar en fonction du mode d’affichage ; quitte à en cacher certaines moins pertinentes ou à en regrouper dans des menus flyout.

     

    Les exceptions

    Le bouton Back

    Il sert à revenir à la page précédente et apparait à gauche du titre de la page.

    image

     

    Les commandes associées à des élements non sélectionnables ou au Presse-Papier

    Les commandes d’édition en général comme le copier/coller doivent apparaitre sous la forme d’un menu flyout et ne pas se retrouver dans l’AppBar. Vous pouvez également utiliser un menu contextuel flyout pour toutes les commandes associées à des éléments non sélectionnables.

     

    Les commandes de gestion de compte de l’utilisateur

    Si vous avez une notion de compte utilisateur dans votre application, les commandes associées devront se retrouver dans les settings (accessible par le charm associé). Vous pouvez néanmoins proposer une commande de connexion directement sur la page principale si la connexion est indispensable à l’utilisation de votre application.

     

    Les commandes de paramétrage, la recherche et le partage d’information

    Toutes ces fonctionnalités se retrouvent dans les charms Settings, Search et Share et sont accessibles par ce biais.

    Ne dupliquez pas une commande de charm dans l’AppBar.

    Si une de ces fonctionnalités est primordiale au fonctionnement d’une page, vous pouvez ajouter un élément directement dans la page qui permet d’accéder au charm associé.

     image

    image

     

     

    Pour approfondir le sujet:

    Commanding design for Metro style apps

    Navigation design for Metro style apps

    Guidelines and checklist for app bars

     

    Voir le mémo complet

  • Stéphanie Hertrich

    Mémo Win8 : Splash screen et temps de chargement

    • 0 Comments

    Voir le mémo complet

     

    Votre application doit avoir un splah screen qui lui est propre (elle n’utilise pas le splash screen défini par défaut par Visual Studio). Celui-ci est composé d’une couleur de fond et d’une image. Le définition du splash screen s’effectue dans le manifest de l’application.

    image

     

    Couleur de fond

    La couleur de fond est optionnelle et si elle n’est pas renseignée, c’est la couleur de la tile qui sera utilisée. Vous pouvez utiliser le nom d’une couleur ou une valeur hexa précédée de ‘#’.

    Veillez à avoir un bon contraste entre l’image et la couleur de fond.

     

    Image

    Il est conseillé d’utiliser une image de type .png avec un fond transparent.

    A 50% scaled image depicting the transparency of the Sample splash screen image. 

    L’image du splash screen peut être définie comme un chemin vers une image ou vers une ressource. Dans ce dernier cas, il est possible de fournir différents formats d’images en fonction de l’échelle qui sera appliquée en fonction du device et de la résolution de l’écran:

    image

    Ce principe peut être appliqué de la même manière pour fournir des images différentes en fonction de la culture ou du contraste pour l’accessibilité.

    Voir How to structure your app package for globalization

     

    Le choix de la couleur et de l’image

    Utilisez une couleur et une image qui identifient clairement votre application.

    Ne faites pas de publicité dans votre splash screen.

    Ne mettez pas de numéro de version, ou autre information du genre “About”.

     

    Temps de chargement

    Au lancement de votre application, vérifiez que le splash screen ne reste pas visible plus de 5 secondes.

    Votre application doit pouvoir se lancer même sans connectivité Internet. Si celle-ci est nécessaire, indiquez à l’utilisateur l’absence de connectivité sur la page principale.

    Si le temps de chargement est plus long et que cela est justifié, il est possible d’utiliser la technique du splash screen étendu. La page d’accueil est alors un relai du splashscreen : une page indiquant que l’application est en cours de chargement.

    Vous trouverez ici les instructions pour mettre en place un splash screen étendu : How to display a splash screen for longer

    Dans tous les cas, l’application doit être réactive aux actions de l’utilisateur dans les 20 secondes après son lancement.

     

    Voir le mémo complet

  • Stéphanie Hertrich

    Mémo Win8 : Le dimensionnement des contrôles cibles de touch

    • 2 Comments

    Voir le mémo complet

     

    Les applications Metro sont conçues pour être “Touch” et donc être majoritairement pilotées au(x) doigt(s).

    La dimension des contrôles qui seront touchés doit donc être adaptée à des doigts : c’est à dire être plus gros que s’ils étaient pilotés par un pointeur de souris et plus espacés les uns des autres pour éviter les erreurs de manipulation.

    Plus une action est importante/critique, plus le contrôle doit être grand. Au contraire, un contrôle associé à une action mineure ou peu utilisée peut être de taille plus réduite.

     

    Voici les préconisations de dimensionnement et d’espacement pour les contrôles intégrés dans vos applications Windows 8 Metro.

    Chart that shows how touch errors decrease for larger targets.

     

    Pour les actions standards

    7x7 mm (40 pixels) = Taille minimale recommandée pour une zone touch

    2 mm (10 pixels) = espacement des contrôles

    7x7 recommended minimum size

    C’est la taille minimale pour une action qui peut être annulée en 1 ou 2 gestures ou dans un intervalle de 5 secondes.

    L’espacement des contrôles est aussi important que la taille de ceux-ci et doit être de 10 pixels minimum (2mm)

     

    Pour les actions importantes

    9x9 mm (50 pixels) = Taille minimale recommandée pour une zone touch entrainant une action importante

    2 mm (10 pixels) = espacement des contrôles

    9x9 recommended size for accuracy

    Les commandes associés à des actions importantes ou critiques telles qu’une suppression, une fermeture, … tolèrent moins les erreurs de manipulation.

    Aussi il est conseillé d’utiliser des contrôles de taille plus importante dans ce cas.

    Cette règle s’applique également aux actions dont l’annulation nécessite plus de 2 actions/gestures ou plus de 5 secondes ou un changement de contexte.

     

    Si vous avez des contraintes de place

    5x5 mm (30 pixels) = Taille minimale recommandée pour une zone touch dans un espace avec des contraintes de place

    2 mm (10 pixels) = espacement des contrôles

    5x5 minimum size

    Si vous manquez de place à l’écran pour placer vos éléments, vous pouvez descendre jusqu’à une zone de 5x5 mm mais l’action doit pouvoir être annulée en 1 seule gesture.

    L’espacement des contrôles est d’autant plus important et reste à un minimum de 2 mm.

     

    Pas de pitié pour les gauchers ;o)

    La majorité de la population étant droitière, il est conseillé de placer les commandes plutôt sur la droite de l’écran. Cela les rend plus accessibles et évite de cacher l’écran avec la main droite pour atteindre des commandes situées à l’opposé.

    Right hand occlusion

     

    Retrouvez tous ces conseils ainsi que beaucoup d’autres sur la conception des interfaces “touch ready” !

    Voir le mémo complet

  • Stéphanie Hertrich

    Le mémo des points à surveiller pour réussir votre application Windows 8 Metro

    • 0 Comments

    [MAJ le 15/06/2012 : version PDF dispo] 

    Cela fait quelques mois que je suis l’avancement d’applications Windows 8 en cours de développement. Nous proposons à ces “early” développeurs une liste de préconisations pour aboutir à une application réussie, c’est à dire bien intégrée à la plateforme et agréable à utiliser.

    Je profite de ce blog pour vous faire partager celles qui me semblent les plus utiles à rappeler et qui correspondent aux principaux écueils rencontrés par ces pionniers du développement sur Windows 8.

    Suivre ces conseils maximisera aussi vos chances de passer avec succès la certification pour le Store Windows 8.

    Si vous projetez de développer une application Metro, ce mémo vous sera donc très utile:

     

    Télécharger le mémo en version PDF, mis à jour régulièrement au fil des nouveaux posts.

     

    • Stéphanie Hertrich

      Partager du code entre plate-formes : l’application Silverlight 5 (4/4)

      • 0 Comments

      Voici le dernier article de la série :

      - Partager du code entre plate-formes grâce à la Portable Library (1/4)

      - Partager du code entre plate-formes : l’application Windows 8 Metro (2/4)

      - Partager du code entre plate-formes : l’application Windows Phone (3/4)

      - Partager du code entre plate-formes : l’application Silverlight 5 (4/4)

       

      Retrouvez le code source des démos :

       

      L’application Silverlight 5

      Nous profitons du contrôle Pivot Viewer qui est très facile à utiliser depuis Silverlight 5 puisque tout se passe côté client avec du binding.

      Ajoutons un nouveau projet Silverlight 5 dans la solution CaveAVins:

      image

      Ajoutons une référence au projet CaveAVins.Portable.

       

      La couche DAL

      L’accès aux données est similaire à celui effectué sur Windows Phone. WCF Data Services nous permet de simplifier l’accès au service OData. La même implémentation de ICaveAVinsDataProvider  peut être utilisée dans cette application Silverlight, malgré tout elle ne peut pas être partagée dans la Portable Lib car WCF Data Services est implémenté par des assemblies différentes sur chaque plate-forme.

      Tout comme pour Windows Phone, ajoutez une référence au service http://caveavins.cloudapp.net/CaveAVinsDataService.svc/.

      Puis ajoutez la même implémentation de la classe CaveAVinsDataProvider que tout à l’heure pour Windows Phone:

      using System;
      using System.Collections.Generic;
      using System.Data.Services.Client;
      using System.Linq;
      using CaveAVins.PivotViewer.ServiceReference1;
      using CaveAVins.Portable.DAL;
      
      namespace CaveAVins.PivotViewer.DAL
      {
          public class CaveAVinsDataProvider : ICaveAVinsDataProvider
          {
              public event Action<IEnumerable<IWine>> WinesLoaded;
              DataServiceCollection<Wine> _results;
      
              public void LoadDataAsync()
              {
                  CaveAVinsContext ct = new CaveAVinsContext(new Uri("http://caveavins.cloudapp.net/CaveAVinsDataService.svc/"));
                  _results = new DataServiceCollection<Wine>(ct);
                  _results.LoadCompleted += (o, e) =>
                      {
                          if (e.Error == null)
                          {
                              // Handle a paged data feed.
                              if (_results.Continuation != null)
                              {
                                  // Automatically load the next page.
                                  _results.LoadNextPartialSetAsync();
                              }
                              else
                              {
                                 WinesLoaded(_results.Cast<IWine>());
                              }
                          }
                      };
      
                  _results.LoadAsync(ct.Wines.Where(w => w.PictureUrl != null));
              }
      
          }
      }
      
      
      Ce code étant identique en dehors du nommage des namespaces locaux, il serait possible de partager directement ces fichiers entre les deux projets.

      La couche ViewModel

      Cette fois c’est le ViewModel de l’application Windows 8 que l’on peut reprendre, toujours pour obtenir une image de l’étiquette de la bouteille de vin:

      namespace CaveAVins.PivotViewer.ViewModel
      {
          public class SlWineVM : WineVM
          {
              public SlWineVM(IWine wine)
                  : base(wine)
              {
              }
      
              ImageSource _picture;
              public ImageSource Picture
              {
                  get
                  {
                      if (_picture == null)
                      {
                          _picture = new BitmapImage(this.PictureUrl);
                      }
                      return _picture;
                  }
              }
          }
      }

      Voici la solution incluant le projet Silverlight:

      image

      La couche Vue

      Depuis la nouvelle version de Pivot Viewer disponible avec Silverlight 5, il est très facile de l’utiliser pour afficher, trier et filtrer une collection de données.

      Nous allons le mettre en oeuvre de manière très basique pour cette démo. Ajoutez une nouvel élément de type Pivot Viewer dans la page de démarrage.

      Associez le binding du Datacontext à la propriété Wines comme nous l’avons fait sur les plate-formes WP et Win8 dans les ListViews.

      <UserControl xmlns:toolkit="http://schemas.microsoft.com/winfx/2006/xaml/presentation/toolkit"
                   x:Class="CaveAVins.PivotViewer.MainPage"
                   xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                   xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                   xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
                   xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
                   xmlns:pivot="clr-namespace:System.Windows.Controls.Pivot;assembly=System.Windows.Controls.Pivot"
                   xmlns:cnv="clr-namespace:CaveAVins.PivotViewer.Converters"
                   mc:Ignorable="d"
                   d:DesignHeight="300"
                   d:DesignWidth="400">
      
          <Grid x:Name="LayoutRoot"
                Background="White">
              <pivot:PivotViewer ItemsSource="{Binding Wines}">
      
                  <pivot:PivotViewer.PivotProperties>
                      <pivot:PivotViewerStringProperty Id="Name"
                                                       DisplayName="Name"
                                                       Binding="{Binding Name}" />
      
                      <pivot:PivotViewerNumericProperty Id="Year"
                                                        Options="CanFilter"
                                                        DisplayName="Year"
                                                        Binding="{Binding Year}" />
      
                  </pivot:PivotViewer.PivotProperties>
      
                  <pivot:PivotViewer.ItemTemplates>
                      
                      <!-- Small-->
                      <pivot:PivotViewerItemTemplate MaxWidth="100">
                          <Border BorderBrush="Black"
                                  BorderThickness="1">
                              <Image Source="{Binding Path=Picture}"
                                     Width="65"
                                     Height="89" />
                          </Border>
                      </pivot:PivotViewerItemTemplate>
      
                      <!-- Medium-->
                      <pivot:PivotViewerItemTemplate>
                          <Border BorderBrush="Black"
                                  BorderThickness="0">
                              <StackPanel Orientation="Vertical">
                                  <TextBlock Text="{Binding Name}"
                                             Width="176"
                                             FontSize="14"
                                             FontWeight="Bold"
                                             TextWrapping="Wrap"
                                             HorizontalAlignment="Center" />
                                  <Image Source="{Binding Path=Picture}"
                                         Width="176"
                                         Height="240" />
                              </StackPanel>
                          </Border>
                      </pivot:PivotViewerItemTemplate>
      
                  </pivot:PivotViewer.ItemTemplates>
      
              </pivot:PivotViewer>
          </Grid>
      </UserControl>
      

      Nous définissons 2 templates qui correspondent à 2 manières d’afficher un vin, en fonction du niveau de zoom. Le premier est une simple image, le second est une image ainsi que le nom du vin. Il est ainsi possible d’afficher de plus en plus de détails sur l’éléments au fur et à mesure que l’on zoome sur celui-ci.

      On utilise le binding vers les propriétés Name, Picture et Year de manière tout à fait classique, ce qui est une nouveauté depuis Silverlight 5 et la nouvelle version de Pivot Viewer.

      La propriété Year est définie comme “CanFilter” et permet ainsi de filtrer les vins en fonction de leur année de production.

      Pour plus d’informations sur Pivot Viewer et voir une version plus évoluée de cette application (plus de niveaux de zoom, chargement par lots, commandes, personnalisation de Pivot Viewer), je vous conseille la vidéo des Techdays 2012 à ce sujet : De A à Z : Utiliser PivotViewer dans une application orientée données

       

      Initialisation de l’application

      Le chargement des données se fait au démarrage de l’application:

      private void Application_Startup(object sender, StartupEventArgs e)
      {
         CaveAVinsDataProvider dp = new CaveAVinsDataProvider();
            dp.WinesLoaded += (wines =>
            {
               var vm = new CaveAVinsVM(wines.Select(w => new SlWineVM(w)));
                  (this.RootVisual as UserControl).DataContext = vm;
            });
      
            dp.LoadDataAsync();
      
            this.RootVisual = new MainPage();
      }

      Go !

      Et voici une version très basique d’application Silverlight 5 avec Pivot Viewer qui permet la manipulation de notre collection de vins.

      image

      image

      Conclusion

      Pour aller plus loin et profiter de ce partage de code, il faudrait implémenter les autres méthodes de la couche DAL, qui permettent d’éditer les données :

      public interface ICaveAVinsDataProvider
      {
          event Action<IEnumerable<IWine>> WinesLoaded;
          void LoadDataAsync();
      
          //void UpdateItem(IWine item);
          //void AddItem(IWine item);
          //void RemoveItem(IWine item);
      }

      Cela se fait comme pour le chargement : en fonction de la plate-forme. Dans le cas de Windows 8, il faut créer la requête REST à la main, suivant le formalisme OData. Pour Windows Phone et Silverlight, il suffit d’utiliser Linq et WCF Data Services.

       

      Au final, cela peut paraitre contraignant de passer par la Portable Lib - et ça l’est effectivement – pour parfois peu de bénéfices. Mais en fonction du projet que vous développez cela peut être une vraie valeur ajoutée. Ce projet de démo n’est peut être pas le meilleur candidat au partage de code, mais il permet d’en montrer les principes et la mise en oeuvre de manière très simple.

      Les principaux critères à prendre en compte pour décider de partir sur une base commune ou de développer des versions entièrement différentes sur chaque plate-forme sont les suivants:

      - la taille des équipes de développement (il est plus simple pour les développeurs de la couche de présentation d’utiliser toujours les mêmes propriétés des mêmes classes/interfaces quelle que soit la plate-forme)

      - le nombre de plate-formes à supporter (économie d’échelle)

      - l’évolutivité potentielle de votre application (plus facile à maintenir une fois la base commune mise en place)

      - la structure et le contenu du code existant (si tout est différent sur chaque plate-forme, ça ne vaut pas forcément le coup)

      - le besoin de documentation d’architecture, de preuve de portabilité et de propreté de conception (si vous faites une petite appli tout seul dans votre coin, ce n’est pas comme si vous étiez en charge d’un projet, d’une vision à moyen/long terme de votre développement, et de la meilleure manière de répartir la charge entre les développeurs)

       

      A vous de jouer !

    • Stéphanie Hertrich

      Partager du code entre plate-formes : l’application Windows Phone (3/4)

      • 0 Comments

      [Edit 31/05/2012] Le sujet du portage d’application depuis Windows Phone vers Windows 8 (et donc indirectement du partage de code entre plates-formes) a aussi été traité de manière très détaillée par Pierre Cauchois dans sa suite d’articles Stratégies et techniques de partage de code C# et XAML entre Windows Phone et Windows 8

       

      Voici le troisième article de la série:

      - Partager du code entre plate-formes grâce à la Portable Library (1/4)

      - Partager du code entre plate-formes : l’application Windows 8 Metro (2/4)

      - Partager du code entre plate-formes : l’application Windows Phone (3/4)

      - Partager du code entre plate-formes : l’application Silverlight 5 (4/4)

       

      Retrouvez le code source des démos:

       

      L’application Windows Phone

      La version actuelle de VS11 ne permet pas de créer des applications Windows Phone et il faut donc se résoudre à créer une solution sous Visual Studio 2010 pour la version WP7 de notre démo.

      Le projet commun est déjà codé et prêt à être utilisé dans notre application WP, reste à créer le projet de type Windows Phone 7. Encore une fois, je choisis le template le plus simple pour ne pas surcharger la démo :

      image_thumb19

      On ajoute le projet CaveAVins.PortableLib dans notre solution ainsi qu’une référence à celui-ci dans le projet WP7.

      image_thumb21

       

      La couche d’accès aux données (DAL)

      Cette fois, c’est plus simple puisqu’il suffit d’ajouter une référence au service OData dans notre projet WP7 :

      image_thumb24

      Les classes proxy correspondantes seront créées automatiquement, et nous aurons donc une implémentation toute prête de notre classe Wine. Vous pouvez accéder au fichier  References.cs contenant les classes proxy en cliquant sur le bouton “Show All Files” permettant d’afficher les fichiers caché de la solution :

      image_thumb29

      Vous y trouverez une classe Wine avec les propriétés qui vont bien, dont Name, PictureUrl et Year:

      image_thumb28 

      Le souci c’est qu’elle n’impléménte pas notre interface inter-plateforme IWine. En fait ce n’est pas vraiment un souci puisque cette classe est définie en partial et peut donc être complétée facilement comme ceci dans un fichier Wine.cs que je place dans un nouveau répertoire “Model”:

      // Même namespace que les classes proxy générées en faisant le Add Service reference vers mon service OData
      namespace CaveAVins.WP.ServiceReference1
      {
          public partial class Wine : IWine
          {
              // Mes classes proxy servent d'implémentation à mon interface portable IWine
          }
      }

      Le chargement de la collection de vins (notre implémentation côté DAL pour Windows Phone) sera faite grâce à WCF Data Services, de manière très classique :

      using System;
      using System.Collections.Generic;
      using System.Data.Services.Client;
      using System.Linq;
      using CaveAVins.Portable.DAL;
      using CaveAVins.WP.ServiceReference1;
      
      namespace CaveAVins.WP.DAL
      {
          public class CaveAVinsDataProvider : ICaveAVinsDataProvider
          {
              public event Action<IEnumerable<IWine>> WinesLoaded;
              DataServiceCollection<Wine> _results;
      
              public void LoadDataAsync()
              {
                  CaveAVinsContext ct = new CaveAVinsContext(new Uri("http://caveavins.cloudapp.net/CaveAVinsDataService.svc/"));
                  _results = new DataServiceCollection<Wine>(ct);
                  _results.LoadCompleted += (o, e) =>
                      {
                          if (e.Error == null)
                          {
                              // Handle a paged data feed.
                              if (_results.Continuation != null)
                              {
                                  // Automatically load the next page.
                                  _results.LoadNextPartialSetAsync();
                              }
                              else
                              {
                                 WinesLoaded(_results.Cast<IWine>());
                              }
                          }
                      };
      
                  _results.LoadAsync(ct.Wines);
              }
      
          }
      }

      On n’oublie pas de déclencher l’évènement de fin de chargement.

      La couche ViewModel

      Rien à faire ici : notre implémentation disponible dans la Portable Lib nous suffit.

       

      La couche Vue

      Encore une fois, on utilise une page fournie dans le template : MainPage.xaml que l’on complète de la manière suivante.

      Chaque vin est représenté par le DataTemplate suivant dont les éléments sont bindés aux propriétés Name, Year et PictureUrl de notre classe WineVM:

      <Grid Margin="5">
         <Grid.ColumnDefinitions>
            <ColumnDefinition Width="Auto"></ColumnDefinition>
            <ColumnDefinition Width="*"></ColumnDefinition>
         </Grid.ColumnDefinitions>
         <Image Width="120" Height="120" Grid.Column="0" Source="{Binding PictureUrl}" Stretch="Uniform"></Image>
         <StackPanel Grid.Column="1">
            <TextBlock Text="{Binding Name}" TextWrapping="Wrap" Style="{StaticResource PhoneTextLargeStyle}"/>
            <TextBlock Text="{Binding Year}" TextWrapping="Wrap" Margin="12,-6,12,0" Style="{StaticResource PhoneTextSubtleStyle}"/>
         </StackPanel>
      </Grid>
      

      Comme le téléphone a un écran plus long que large, on choisit plutôt un défilement vertical. C’est le fonctionnement par défaut de la ListBox:

      <ListBox x:Name="MainListBox" Margin="0,0,-12,0" ItemsSource="{Binding Wines}">

       

      Remarquez que les bindings sont similaires à ceux de l’application Windows 8 : en effet, les ViewModels sont communs (sauf pour l’image)!

       

      Voici le code complet de la page:

      <phone:PhoneApplicationPage 
          x:Class="CaveAVins.WP.MainPage"
          xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
          xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
          xmlns:phone="clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone"
          xmlns:shell="clr-namespace:Microsoft.Phone.Shell;assembly=Microsoft.Phone"
          xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
          xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
          mc:Ignorable="d" d:DesignWidth="480" d:DesignHeight="768"
          FontFamily="{StaticResource PhoneFontFamilyNormal}"
          FontSize="{StaticResource PhoneFontSizeNormal}"
          Foreground="{StaticResource PhoneForegroundBrush}"
          SupportedOrientations="Portrait" Orientation="Portrait"
          shell:SystemTray.IsVisible="True"
          >
      
          <!--LayoutRoot is the root grid where all page content is placed-->
          <Grid x:Name="LayoutRoot" Background="Transparent">
              <Grid.RowDefinitions>
                  <RowDefinition Height="Auto"/>
                  <RowDefinition Height="*"/>
              </Grid.RowDefinitions>
      
              <!--TitlePanel contains the name of the application and page title-->
              <StackPanel x:Name="TitlePanel" Grid.Row="0" Margin="12,17,0,28">
                  <TextBlock x:Name="ApplicationTitle" Text="PORTABLE LIBRARY DEMO" Style="{StaticResource PhoneTextNormalStyle}"/>
                  <TextBlock x:Name="PageTitle" Text="Wines" Margin="9,-7,0,0" Style="{StaticResource PhoneTextTitle1Style}"/>
              </StackPanel>
      
              <!--ContentPanel - place additional content here-->
              <Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
                  <ListBox x:Name="MainListBox" Margin="0,0,-12,0" ItemsSource="{Binding Wines}">
                      <ListBox.ItemTemplate>
                          <DataTemplate>
                              <Grid Margin="5">
                                  <Grid.ColumnDefinitions>
                                      <ColumnDefinition Width="Auto"></ColumnDefinition>
                                      <ColumnDefinition Width="*"></ColumnDefinition>
                                  </Grid.ColumnDefinitions>
                                  <Image Width="120" Height="120" Grid.Column="0" Source="{Binding PictureUrl}" Stretch="Uniform"></Image>
                                  <StackPanel Grid.Column="1">
                                      <TextBlock Text="{Binding Name}" TextWrapping="Wrap" Style="{StaticResource PhoneTextLargeStyle}"/>
                                      <TextBlock Text="{Binding Year}" TextWrapping="Wrap" Margin="12,-6,12,0" Style="{StaticResource PhoneTextSubtleStyle}"/>
                                  </StackPanel>
                              </Grid>
                          </DataTemplate>
                      </ListBox.ItemTemplate>
                  </ListBox>
              </Grid>
          </Grid>
      
      </phone:PhoneApplicationPage>
      

      Initialisation de l’application

      La chargement des données se fera au démarrage de l’application, dans l’évènement Application_Launching.

      // Code to execute when the application is launching (eg, from Start)
      // This code will not execute when the application is reactivated
      private void Application_Launching(object sender, LaunchingEventArgs e)
      {
          CaveAVinsDataProvider dp = new CaveAVinsDataProvider();
          dp.WinesLoaded += (wines =>
          {
              _vm = new CaveAVinsVM(wines);
              RootFrame.DataContext = _vm;
          });
      
          dp.LoadDataAsync();
      }

      Cette fois, nous pouvons utiliser le ViewModel par défaut pour les vins (celui défini dans la Portable Lib) et donc passer directement la collection wines récupérée dans l’évènement WinesLoaded.
      Tout comme dans l’application Windows 8, on affecte le viewModel principal (CaveAVinsVM) au DataContext de notre page principale dont tous les contrôles hériteront.

      Go !

      Au démarrage de l’application, nous obtenons le résultat suivant :

      image_thumb36

       

      La suite : Partager du code entre plate-formes : l’application Silverlight 5 (4/4)

    • Stéphanie Hertrich

      Partager du code entre plate-formes : l’application Windows 8 Metro (2/4)

      • 0 Comments

      Voici le deuxième article de la série :

      - Partager du code entre plate-formes grâce à la Portable Library (1/4)

      - Partager du code entre plate-formes : l’application Windows 8 Metro (2/4)

      - Partager du code entre plate-formes : l’application Windows Phone (3/4)

      - Partager du code entre plate-formes : l’application Silverlight 5 (4/4)

       

      Retrouvez le code source des démos:

       

      L’application Windows 8 Metro

      Je commence par créer un nouveau projet de type Portable Class Library sous Visual Studio 11. Celui-ci sera commun et utilisé par mes 3 applications à venir.

      image

      Le projet de type Portable Class Library

      Dans les propriétés du projet, je sélectionne les 3 plate-formes dans ma Portable Lib.

      image

      Je prends soin de sélectionner Windows Phone 7.5 sinon je n’aurais pas accès à l’assembly System.Windows.dll qui me permet d’utiliser tout ce qui est associé au INotifyxxx et les éléments observables. En effet, j’en aurai besoin pour la couche ViewModel.

      On me prévient gentiment que d’autres plateformes seront aussi compatibles avec celles que j’ai sélectionnées, à savoir .Net 4.5 et Silverlight 4 et >.

      Dans mon projet, je crée les répertoires correspondant à chacune des couches supportées par la portable lib: DAL, Model, ViewModel

      image_thumb8

      Dans cette application simpliste, je n’aurai qu’un type de données à gérer : la classe Wine que je place dans la couche Model :

      namespace CaveAVins.Portable.Model
      {
          public partial class Wine
          {
              public string Name { get; set; }
              public int Year { get; set; }
              public string PictureUrl { get; set; }
          }
      }

      La couche d’accès aux données (DAL)

      Pour récupérer une collection de vins à partir de mon service OData, je vais procéder de manière différente selon que je travaille sur Win8 ou WP.

      C’est WCF Data Services (une implémentation pour un client OData) qui va permettre d’ajouter une référence à mon service OData dans Windows Phone (tout comme on le ferait dans .Net ou Silverlight). Dans ce cas, les classes proxy (comprenant la classe Wine) sont générées automatiquement.

      Avec Visual Studio 11, il n’y a pas encore de client OData disponible et nous devrons donc parser manuellement le flux Atom renvoyé par le service pour le mapper dans une collection d’éléments de type Wine.

      J’aurai donc plusieurs implémentations de mon modèle matérialisé par la classe Wine : l’une manuelle comme mentionné plus haut, dans Windows 8 et l’autre générée automatiquement par Visual Studio.

      Je rajoute donc une interface qui définira la signature de ce qu’est un vin dans notre application : un nom, une année de production et un lien vers la photo de l’étiquette de la bouteille.

      namespace CaveAVins.Portable.DAL
      {
          public interface IWine
          {
              string Name { get; set; }
              int Year { get; set; }
              string PictureUrl { get; set; }
          }
      }
      Les classes Wine implémenteront cette interface
      namespace CaveAVins.Portable.Model
      {
          // Implémentation par défaut
          public partial class Wine : IWine
          {
              public string Name { get; set; }
              public int Year { get; set; }
              public string PictureUrl { get; set; }
          }
      }

      J’ajoute également l’interface du data provider qui me permettra de manipuler les données :

      namespace CaveAVins.Portable.DAL
      {
          public interface ICaveAVinsDataProvider
          {
              event Action<IEnumerable<IWine>> WinesLoaded;
              void LoadDataAsync();
      
              //void UpdateItem(Wine item);
              //void AddItem(Wine item);
              //void RemoveItem(Wine item);
          }
      }

      Je commence par m’occuper du chargement asynchrone des données, dont la fin est signalée par l’évènement WinesLoaded. Remarquez que c’est un simple event, j’ai choisi de ne faire intervenir les éléments notifiables qu’à partir de la couche ViewModel.

       

      La couche ViewModel

      La couche ViewModel va permettre à la couche View d’interagir avec le modèle et la DAL par les mécanismes de binding, en utilisant des éléments “notifiables”.

      Nous créons une classe de base ViewModelBase qui implémente INotifyPropertyChanged pour en faire bénéficier automatiquement tous les ViewModels.

          public abstract class ViewModelBase : INotifyPropertyChanged
          {
              public event PropertyChangedEventHandler PropertyChanged;
      
              protected virtual void OnPropertyChanged(string propName)
              {
                  if (PropertyChanged != null)
                  {
                      PropertyChanged(this, new PropertyChangedEventArgs(propName));
                  }
              }
          }

      Nous créons ensuite un ViewModel associé à un vin : WineVM

      namespace CaveAVins.Portable.ViewModel
      {
          public partial class WineVM : ViewModelBase
          {
              
              public WineVM(IWine wine)
              {
                  if (!string.IsNullOrEmpty(wine.PictureUrl))
                  {
                      _pictureUrl = new Uri(wine.PictureUrl);
                  }
                  _name = wine.Name;
                  _year = wine.Year;
              }
      
              Uri _pictureUrl = null;
              public Uri PictureUrl
              {
                  get
                  {
                      return _pictureUrl;
                  }           
              }
      
              private string _name;
              public string Name
              {
                  get { return _name; }
                  set 
                  {
                      if (_name != value)
                      {
                          _name = value;
                          OnPropertyChanged("Name");
                      }
                  }
              }
      
              private int _year;
              public int Year
              {
                  get { return _year; }
                  set
                  {
                      if (_year != value)
                      {
                          _year = value;
                          OnPropertyChanged("Year");
                      }
                  }
              }        
          }
      }

      Ainsi qu’un ViewModel permettant de manipuler la collection de vins renvoyée par la DAL:

      namespace CaveAVins.Portable.ViewModel
      {
          public partial class CaveAVinsVM : ViewModelBase
          {
              public CaveAVinsVM(IEnumerable<IWine> wines)
              {
                  _wines = new ObservableCollection<WineVM>(wines.Select(w => new WineVM(w)));
              }
      
              public CaveAVinsVM(IEnumerable<WineVM> wines)
              {
                  _wines = new ObservableCollection<WineVM>(wines);
              }
      
              ObservableCollection<WineVM> _wines;
              public ObservableCollection<WineVM> Wines
              {
                  get
                  {
                      return _wines;
                  }
                  private set
                  {
                      _wines = value;
                  }
              }
      
          }
      }

      Voici la structure de notre projet de type Portable Class Library, qui sera commun aux deux solutions Windows 8 Metro et Windows Phone:

      image_thumb9[1]

       

      Le projet Windows 8 Metro

      Créons un nouveau projet de type Metro: vous pouvez choisir un template qui inclut une navigation et une présentation de collection.

      image_thumb121

      Moi j’ai choisi une application vide pour me focaliser sur le sujet de cet article:

      image_thumb15

      J’ajoute une référence au projet CaveAVins.Portable pour pouvoir l’utiliser dans mon application.

       

      La couche d’accès aux données (DAL)

      Je crée un répertoire DAL ainsi qu’un fichier CaveAVinsDataProvider.cs et sa classe CaveAVinsDataProvider qui implémente IDaveAVinsDataProvider et permet ainsi l’accès au service OData et à la collection de vins.

      image_thumb4[1]

      A ce jour, il n’existe pas de version de WCF Data Services pour Windows 8 et il faudra donc accéder à mon service OData et parser le flux de données manuellement.

      Voici à quoi il ressemble:

      image

      J’utilise le SyndicationClient qui me permet de récupérer simplement un flux Atom.

      Je le parse manuellement pour n’en conserver que les propriétés Name, Year et PictureUrl. J’instancie pour chaque vin, la classe Wine que j’ai créée dans la Portable Lib, puis je déclenche l’évènement WinesLoaded pour signaler que le chargement des données est terminé.

      using System;
      using System.Collections.Generic;
      using System.Linq;
      using System.Text;
      using System.Threading.Tasks;
      using CaveAVins.Portable.DAL;
      using CaveAVins.Portable.Model;
      using Windows.Web.Syndication;
      
      namespace CaveAVins.Win8Metro.DAL
      {
          public class CaveAVinsDataProvider : ICaveAVinsDataProvider
          {        
              public event Action<IEnumerable<IWine>> WinesLoaded;
      
              public async void LoadDataAsync()
              {
                  SyndicationClient client = new SyndicationClient();
                  Uri feedUri = new Uri("http://caveavins.cloudapp.net/CaveAVinsDataService.svc/Wines");
                  IList<Wine> wines = new List<Wine>();
      
                  SyndicationFeed feed = await client.RetrieveFeedAsync(feedUri);
      
      
                  foreach (SyndicationItem item in feed.Items)
                  {
                      try
                      {
                          Wine newWine = new Wine();
                          var attributes = item.Content.Xml.FirstChild.ChildNodes.AsEnumerable();
                          Func<string, string> parse = (an) =>
                          {
                              var attr = attributes.FirstOrDefault(a => a.LocalName.ToString() == an);
                              if ((attr != null) && (attr.HasChildNodes()))
                              {
                                  return attr.FirstChild.NodeValue.ToString();
                              }
                              return string.Empty;
                          };
      
                          newWine.Name = parse("Name");
                          newWine.Year = int.Parse(parse("Year"));
                          newWine.PictureUrl = parse("PictureUrl");
                          wines.Add(newWine);
                      }
                      catch
                      {
                          // On ajoute que les items valides
                      }
                  }
                  WinesLoaded(wines);
              }
          }
      }
      

      La couche ViewModel

      Elle est déjà créée dans la portable lib. Il reste à complémenter celle-ci avec les certaines spécificités de la plate-forme. Dans notre cas, cela revient à compléter la classe WineVM.

      image_thumb9

      Il faut créer une BitmapImage à partir d’une Uri, pour pouvoir afficher l’image de la bouteille. Je crée donc une classe dérivée de WineVM qui comporte une propriété supplémentaire qui sera bindée à une image dans la vue.

       

      using System;
      using System.Collections.Generic;
      using System.Linq;
      using System.Text;
      using System.Threading.Tasks;
      using CaveAVins.Portable.DAL;
      using CaveAVins.Portable.ViewModel;
      using Windows.UI.Xaml.Media;
      using Windows.UI.Xaml.Media.Imaging;
      
      namespace CaveAVins.Win8Metro.ViewModel
      {
          public class Win8WineVM : WineVM
          {
              public Win8WineVM(IWine wine) : base(wine)
              {
              }
      
              ImageSource _picture;
              public ImageSource Picture
              {
                  get
                  {
                      if (_picture == null)
                      {
                          _picture = new BitmapImage(this.PictureUrl);
                      }
                      return _picture;
                  }
              }
          }
      }
      

       

      La couche Vue

      La couche Vue est belle et bien spécifique à la plate-forme et n’apparait donc pas dans la Portable Lib. Nous pourrions créer un répertoire View et y glisser les vues mais dans notre application, nous n’avons qu’une seule page qui est déjà présente dans le template d’application que nous avons choisi d’utiliser. C’est le fichier BlankPage.xaml.

      image_thumb11

      Voici le code que j’utilise pour afficher mes éléments sous la forme d’une grille, avec un scrolling horizontal:

      <Page
          x:Class="CaveAVins.Win8Metro.BlankPage"
          xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
          xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
          xmlns:local="using:CaveAVins.Win8Metro"
          xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
          xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
          mc:Ignorable="d">
      
          <Grid Background="{StaticResource ApplicationPageBackgroundBrush}">
              <Grid.RowDefinitions>
                  <RowDefinition Height="140"/>
                  <RowDefinition Height="*"/>
              </Grid.RowDefinitions>
                  
              <TextBlock x:Name="pageTitle" Text="Portable Library Demo" Grid.Column="1" Style="{StaticResource PageHeaderTextStyle}"
                          Margin="116,0,40,46"/>
      
              <!-- Horizontal scrolling grid used in most view states -->
              <ScrollViewer
                  x:Name="itemGridScrollViewer"
                  AutomationProperties.AutomationId="ItemGridScrollViewer"
                  Grid.Row="1"
                  Margin="0,-3,0,0"
                  Style="{StaticResource HorizontalScrollViewerStyle}">
      
                  <GridView Grid.Row="1" Grid.Column="1" ItemsSource="{Binding Wines}"
                                Margin="116,0,40,46">
                      <GridView.ItemTemplate>
                          <DataTemplate>
                              <Grid HorizontalAlignment="Left" Width="250" Height="250">
                                  <Image Source="{Binding Picture}" Stretch="Uniform"/>
                                  <StackPanel VerticalAlignment="Bottom" Background="{StaticResource ListViewItemOverlayBackgroundBrush}">
                                      <TextBlock Text="{Binding Name}" Foreground="{StaticResource ListViewItemOverlayTextBrush}" Style="{StaticResource TitleTextStyle}" Height="60" Margin="15,0,15,0"/>
                                      <TextBlock Text="{Binding Year}" Foreground="{StaticResource ListViewItemOverlaySecondaryTextBrush}" Style="{StaticResource CaptionTextStyle}" TextWrapping="NoWrap" Margin="15,0,15,10"/>
                                  </StackPanel>
                              </Grid>
                          </DataTemplate>
                      </GridView.ItemTemplate>
      
                  </GridView>
              </ScrollViewer>
          </Grid>
      </Page>
      

       

      Remarquez que chaque élément de ma grille sera bindé aux propriétés Name, Year et Picture de mes vins projetés dans une collection de type WineVM.

      <Grid HorizontalAlignment="Left" Width="250" Height="250">
         <Image Source="{Binding Picture}" Stretch="Uniform"/>
         <StackPanel VerticalAlignment="Bottom" Background="{StaticResource ListViewItemOverlayBackgroundBrush}">
            <TextBlock Text="{Binding Name}" Foreground="{StaticResource ListViewItemOverlayTextBrush}" Style="{StaticResource TitleTextStyle}" Height="60" Margin="15,0,15,0"/>
            <TextBlock Text="{Binding Year}" Foreground="{StaticResource ListViewItemOverlaySecondaryTextBrush}" Style="{StaticResource CaptionTextStyle}" TextWrapping="NoWrap" Margin="15,0,15,10"/>
         </StackPanel>
      </Grid>


      La propriété ItemsSource de ma GridView sera bindée à la propriété Wines du viewModel CaveAVinsVM.

      <GridView Grid.Row="1" Grid.Column="1" ItemsSource="{Binding Wines}"

      La couche vue est prête, il faut maintenant charger les données !

       

      Initalisation de l’application

      Cela se passe dans le fichier app.xaml.cs, dans l’évènement OnLaunched qui correspond au lancement de l’application. Nous ajoutons le code qui nous permet de charger les données:

                  CaveAVinsDataProvider dp = new CaveAVinsDataProvider();
                  dp.WinesLoaded += dp_WinesLoaded;
                  dp.LoadDataAsync();

      Ainsi que l’association des données chargées avec la vue sur déclenchement de l’évènement :

       

              void dp_WinesLoaded(IEnumerable<IWine> wines)
              {
                  CaveAVinsVM vm = new CaveAVinsVM(wines.Select(w => new Win8WineVM(w)));
                  (Window.Current.Content as Frame).DataContext = vm;
              }

      Pour chaque vin, on instancie un Win8WineVM et la collection résultante est passée en paramètre dans le constructeur de la classe CaveAVinsVM. Celle-ci est affectée au DataContext de notre page principale pour permettre aux bindings créés dans la vue de fonctionner.

       

      Voici le code complet correspondant:

              /// <summary>
              /// Invoked when the application is launched normally by the end user.  Other entry points
              /// will be used when the application is launched to open a specific file, to display
              /// search results, and so forth.
              /// </summary>
              /// <param name="args">Details about the launch request and process.</param>
              protected override void OnLaunched(LaunchActivatedEventArgs args)
              {
                  if (args.PreviousExecutionState == ApplicationExecutionState.Terminated)
                  {
                      //TODO: Load state from previously suspended application
                  }
      
                  CaveAVinsDataProvider dp = new CaveAVinsDataProvider();
                  dp.WinesLoaded += dp_WinesLoaded;
                  dp.LoadDataAsync();
      
                  // Create a Frame to act navigation context and navigate to the first page
                  var rootFrame = new Frame();
                  rootFrame.Navigate(typeof(BlankPage));
      
                  // Place the frame in the current Window and ensure that it is active
                  Window.Current.Content = rootFrame;
                  Window.Current.Activate();
              }
      
              void dp_WinesLoaded(IEnumerable<IWine> wines)
              {
                  CaveAVinsVM vm = new CaveAVinsVM(wines.Select(w => new Win8WineVM(w)));
                  (Window.Current.Content as Frame).DataContext = vm;
              }

      Go !

      Nous pouvons démarre l’application qui affichera les bouteilles au bout de quelques secondes :

      image

       

      La suite : Partager du code entre plate-formes : l’application Windows Phone (3/4)

    • Stéphanie Hertrich

      Partager du code entre plate-formes grâce à la Portable Class Library (1/4)

      • 1 Comments

      Avec la multiplication des plate-formes, les besoins en terme de partage de code sont de plus en plus importants. L’arrivée de Windows 8 et les applications Metro va générer beaucoup de portage d’applications existantes et la réutilisation du code devient alors capitale. Le sujet est assez complexe puisque les frameworks ne sont pas équivalents sur les différentes plateformes mais conservent malgré tout un sous-ensemble commun.

      Le type de projet « Portable Library » permet justement de factoriser du code commun à plusieurs plateformes en donnant accès uniquement aux assemblies de base communes à celles-ci. On met tout le code commun dans cette librarie de classes qui est référencée par les projets spécifiques à chaque plate-forme et hop le tour est joué.

      Rhhha mais c’est génial ça ! Ca va me sauver la vie ? Oui et non…

      On sous-estime souvent les spécificités de la plateforme dans notre code et nous verrons qu’il peut être plus compliqué que prévu de conserver un code commun, au fil des 4 articles de la série:

      - Partager du code entre plate-formes grâce à la Portable Library (1/4)

      - Partager du code entre plate-formes : l’application Windows 8 Metro (2/4)

      - Partager du code entre plate-formes : l’application Windows Phone (3/4)

      - Partager du code entre plate-formes : l’application Silverlight 5 (4/4)

      Retrouvez le code source des démos:

      [Edit 31/05/2012] Le sujet du portage d’application depuis Windows Phone vers Windows 8 (et donc indirectement du partage de code entre plates-formes) a aussi été traité de manière très détaillée par Pierre Cauchois dans sa suite d’articles Stratégies et techniques de partage de code C# et XAML entre Windows Phone et Windows 8

       

      La Portable Class Library

      La Portable Class Library existe déjà pour Visual Studio 2010 sous forme d’extension. Visual Studio 11 Ultimate intègre directement une version de la Portable Library supportant les applications Windows 8 Metro.

      Pour l’utiliser, il faut créer un nouveau projet de type « Portable Class Library ».

      image_thumb4

      Dans les propriétés du projet, vous pourrez choisir les plateformes sur lesquelles votre application doit pouvoir tourner.

      image_thumb71

      image_thumb10

      En fonction des plateformes et des versions de plateformes sélectionnées, Visual Studio vous donnera accès aux librairies de base qui leur sont communes. Ainsi, plus vous sélectionnez de plate-formes, plus ce sous-ensemble sera restreint.

      Voici un tableau qui détaille les fonctionnalités disponibles dans la Portable Library, sur les différentes plateformes :

      image

      Que peut-on mettre en commun dans un projet ?

      Classiquement, on va retrouver

        • Les mêmes données
        • L’accès aux mêmes services côté serveur
        • Le même métier
        • Certains cas d’utilisation identiques

      Les pages, le layout ainsi que cetains cas d’utilisation seront différents car les dimensions et l’usage de chaque device diffère :

        • Usage du touch contre clavier/souris
        • Grand écran/petit écran
        • Device nomade/fixe

      L’idéal serait de partir vers une structure de ce type avec les couches communes en bleu :

      image_thumb7

      En terme de structure de projets dans Visual Studio, on procéderait de la manière suivante avec un projet par plate-forme et un projet commun de type Portable Library:

      clip_image012_thumb

      Et les couches relatives (DAL/Model/ViewModel) seraient associées à des répertoires sur chaque plate-forme comme sur le schéma ci-dessus :

      clip_image014_thumb2

      Pas si vite…

      Malheureusement, la couche d’accès aux données (DAL) contient souvent du code spécifique à la plate-forme. Par-exemple, l’accès à un service OData nécessite un client WCF Data Service différent sur WP7, sur Silverlight et sur Win8 (où il n’existe pas encore), bien que le service soit le même côté serveur.

      Dans ce cas, la DAL pourrait se retrouver dans la portable library sous la forme d’une interface, qui serait implémentée de manière différente sur chaque plate-forme:

      clip_image016_thumb

      namespace MyApp.Portable.DAL
      {
          public interface IDataProvider
          {
              event EventHandler<IEnumerable<DataItem>> DataLoaded;
              void LoadDataAsync();
      
              void UpdateItem(DataItem item);
              void AddItem(DataItem item);
              void RemoveItem(DataItem item);
          }
      }

       

      Comme on peut le voir dans le tableau plus haut, MEF est disponible pour certaines plate-formes dans la Portable Lib et peut donc nous aider à faire une gestion propre des règles d’implémentation.

       

      Autre problématique: à ce jour les projets Windows Phone sont développés avec Visual Studio 2010 et il faudra donc 2 solutions : l’une sur VS2010 avec le projet Windows Phone, l’autre sur VS11 avec le projet Win8 Metro et Silverlight. La librairie commune – créée dans VS11 pour choisir la plate-forme Win8 - se retrouvera dans les 2 solutions.

      Il est néanmoins possible d’utiliser la Portable Class Library dès aujourd’hui, ce qui peut être intéressant si vous souhaitez conserver une structure commune et surtout pouvoir assurer une maintenance homogène de votre application sur les différentes plate-formes.

       

      Démonstration

      Je vais partir sur une application toute simple qui récupère des données depuis un service OData et les affiche à l’écran. Le but est de montrer comment réutiliser du code dans ce contexte et il faut extrapoler ce principe pour une application plus conséquente, sinon cela n’a pas vraiment d’intérêt car très peu de code sera finalement partagé dans mon exemple.

       

      Pré-requis

       

      Présentation de l’application

      L’application démo va présenter une liste de vins avec leurs photos. Je récupère ces données depuis un service OData qui se trouve à cette adresse : http://caveavins.cloudapp.net/CaveAVinsDataService.svc/Wines et je les affiche à l’écran.

      Pour la démo je mettrai en oeuvre l’application sur 3 plate-formes:

      - Windows 8

      image

      - Windows Phone

      image

      - Silverlight 5 avec Pivot viewer

      image_thumb[6] 

      image_thumb[7]

      Je vais donc créer 2 solutions : l’une sous Visual Studio 11 beta qui contiendra 3 projets:

      - La portable lib

      - L’application Windows 8 Metro

      - L’application Silverlight 5

      L’autre sous Visual Studio 2010 qui contiendra 2 projets:

      - La portable lib

      - L’application Windows Phone

       

      Voici le code source complet des applications:

       

       

      La suite : Partager du code entre plate-formes : l’application Windows 8 Metro (2/4)

    • Stéphanie Hertrich

      E-camp : Proposez une authentification fédérée (FB, Google, LiveId,...) avec ACS dans votre application Windows Phone

      • 0 Comments

      Un petit mot pour vous inviter à un E-camp (un live meeting) que j’animerai vendredi 24 février à 13h30.

      Je vous ferai une présentation dirigée d'un sample Windows Phone 7 qui utilise une authentification par Access Control Service.

      Votre application proposera ainsi une connexion FB, Live, Google, … et les utilisateurs se connecteront avec un de leurs comptes existants parmi ces fournisseurs d’identité.

      Les avantages sont multiples:

      - Vous n’aurez pas à développer votre propre gestion de comptes et de profils

      - L’utilisateur n’aura pas besoin de créer un nouveau compte spécifique à votre application

      - Vous choisissez quels providers utiliser par simple configuration, sans modifier votre code

      Nous installerons le package NuGet permettant d’utiliser ACS sur Windows Phone, puis nous mettrons en place les différents éléments pour passer le jeton d’authentification à un web service.

      En espérant vous voir vendredi  !!!

      ae4dfa93-de07-4e91-ba0c-f94896c7dddf

    • Stéphanie Hertrich

      TechDays 2012 : les slides et vidéos du parcours “De A à Z” sont en ligne !

      • 4 Comments

      [Edit du 15/03/2012: Ajout des vidéos]

      Après une journée J1 marathon mais passionnante, voici un début de contenu du parcours “De A à Z : concevoir une solution applicative”.

      Merci à tous ceux qui ont suivi les sessions que j’ai animées avec grand plaisir avec des co-speakers de choc !

      Désolée pour ceux qui n’ont pas eu de place. Pour vous consoler, vous trouverez les vidéos et les slides des sessions ici, sur le site des techdays 2012 et sur le showcase Microsoft.

      De A à Z : Choisir une architecture pour sa solution applicative (ARC201)

      De A à Z: Accès aux données avec Entity Framework 4.2 et publication en OData (LAN204)

      De A à Z: Pourquoi et comment utiliser Azure dans son application ? (CLO207)

      De A à Z: Développer une application Windows Phone 7.5 connectée à un service OData (WP7203)

      De A à Z: Utiliser PivotViewer dans une application orientée données (RDA206)

      A très bientôt !

    • Stéphanie Hertrich

      TechDays 2012 : En 1 journée, découvrez comment développer une solution applicative de A à Z

      • 3 Comments

      Vous souhaitez repartir avec une vision complète du développement d’application ?
      Vous souhaitez comprendre quelles technologies mettre en œuvre pour un résultat moderne, sexy et interopérable ?
      Vous souhaitez actualiser vos compétences dans le développement ?
      Vous souhaitez développer une application sans partir dans le mur ?
      Vous souhaitez profiter des techdays pour acquérir les bons réflexes ?
      Vous hésitez quant aux sessions à suivre ?

      Ce parcours est fait pour vous ! 

      image

      Pendant une journée, il vous sert de guide pour comprendre comment construire une solution applicative de bout en bout.

      A partir d’un cahier des charges, nous définirons l’architecture de la solution en justifiant le choix des technologies mises en œuvre . Nous aborderons ensuite chacun des aspects du développement de la solution, depuis le back-end jusqu’aux applications clientes, en passant par l’hébergement de ce petit monde.

      Au sommaire, 5 sessions:

      image

      image

      image

      image

      image

      Les sessions peuvent aussi être suivies de manière indépendante, le besoin métier (notre fil rouge) servant simplement de support aux démos.

      Chaque session se déroulera en 2 phases et conviendra aussi bien au public débutant que confirmé

      1. Mise en œuvre basique de la techno pour l’application visée
      2. Intervention d’un expert qui proposera des améliorations fonctionnelles, techniques, UX, optimisations, trucs et astuces et présentation d’outils et toolkits complémentaires.

      Des spoilers sur le contenu des sessions bientôt Nyah-Nyah

       

      Le parcours “De A à Z : Développer une solution applicative” c’est le 7 février aux TechDays 2012…

      Faites passer !

    • Stéphanie Hertrich

      Utiliser ACS pour l’authentification dans une application WP7 et un WCF Data Service

      • 0 Comments

      Le toolkit Azure pour Windows Phone simplifie grandement l’intégration d’Azure dans les applications. Depuis peu, il a été divisé en plusieurs packages NuGet permettant de cibler l’intégration d’une fonctionnalité précise (stockage, ACS, notifications Push, …)

      L’un des packages permet d’intégrer les fonctionnalités d’une authentification auprès de providers d’identités existants (Facebook, Windows Live, Google,…) grâce à Access Control Services.

      Dans mon application, l’utilisateur pourra s’authentifier auprès de ces providers et le jeton d’authentification sera réinjecté dans les requêtes OData envoyées à mon WCF Data Service. Au niveau de mon service, les données ajoutées par un utilisateur authentifié seront  rattachées à celui-ci et lui seul y aura accès par la suite.

      Revenons à notre Toolkit Azure : notre application utilisera donc ACS et proposera une mire de connexion en fonction du provider choisi. Voici le package NuGet à installer dans votre projet WP7.

      image[52]

      Le mode d’emploi est explicite et il est donc relativement aisé d’intégrer une authentification OAuth 2.0 dans une application WP7.

      image[31]

      Par contre, la procédure ne détaille pas comment mettre en place le service web pour lequel pour utilisera précisément cette authentification, en réinjectant le jeton dans la requête http OData.

      En fait, la solution est un mix entre le tutoriel créé pour l’ancien toolkit et le mode d’emploi installé avec le package NuGet.

      Voici comment faire, en quelques étapes…

       

      Le but

      Notre application WP7 manipule des données fournies par un service OData. Mais seul un utilisateur authentifié sera autorisé récupérer ces données, et seules les données le concernant lui seront renvoyées.

      image[47]

       

      Dans l’application Windows Phone

       

      Ajouter le package NuGet package au projet

      image[3]

      Ce package mettra en place tout ce qu’il faut dans votre projet pour qu’il puisse utiliser ACS et stocker le jeton qui sera réinjecté dans les requêtes OData, par la suite.

      Le mode d’emploi ([Your WP7 Project]/App_Readme/Phone.Identity.Controls.BasePage.Readme.htm) s’affiche dès l’installation du package NuGet. Il suffit de suivre les étapes pour le faire fonctionner.

      Vous aurez néanmoins besoin d’un ACS, que vous pouvez configurer en suivant les étapes mentionnées ici dans la Task 2.

       

      Ajouter Web Browser Capability dans le manifest de l’application

      La page de login s’affiche dans un web browser control, il faut donc le déclarer au niveau du manifest : WMAppManifest.xml

            <Capability Name="ID_CAP_WEBBROWSERCOMPONENT"/>
      

       

      La navigation

      Ajoutez la page de login en tant que page de démarrage de votre application, comme indiqué dans le mode d’emploi.

      Lorsque vous démarrez l’application, si votre jeton est encore valide vous naviguerez directement à la page d’accueil de votre application. Dans ce cas, il faut prévoir de sortir de l’application sur le bouton Back. Une manière de réaliser cette opération est de vider l’historique de navigation lorsque l’on arrive sur la page d’accueil.

      protected override void OnNavigatedTo(System.Windows.Navigation.NavigationEventArgs e)
      {
          base.OnNavigatedTo(e);

          while(this.NavigationService.BackStack.Any())
          {
              NavigationService.RemoveBackEntry();
          }
      }

       

      On essaye !

      A ce niveau, vous devriez être capable de vous logger en vous identifiant auprès d’un de vos providers préférés, par-exemple avec un Live Id:

      image[8]image[12]image[17]

       

      Où est passé le jeton d’authentification ?

      Une fois que l’authentification a réussi, le jeton est stocké comme un SimpleWebTokenStore dans

      Application.Current.Resources["swtStore"]

       

      Comment je l’utilise dans mes requêtes OData ?

      Le jeton doit être placé dans l’en-tête de la requête Http. Il faut s’abonner à l’évènement SendingRequest de votre contexte WCF Data Service et y mettre à jour l’en-tête en ajoutant l’”authorization”.

      _dc = new YourDataServiceContext(new Uri("http://YourDataService.svc/"));
              
      _dc.SendingRequest += new EventHandler<SendingRequestEventArgs>(SendingRequest);
       
      
      void SendingRequest(object sender, SendingRequestEventArgs e)
      {
         var simpleWebTokenStore = Application.Current.Resources["swtStore"] as SimpleWebTokenStore;
         if (simpleWebTokenStore != null)
         {
            e.RequestHeaders["Authorization"] = "OAuth " + simpleWebTokenStore.SimpleWebToken.RawToken;
         }
      }

      AmpouleAttention, l’accès aux Resources ne peut se faire en dehors du thread de l’UI. Si c’est votre cas, il faut stocker le jeton ailleurs pour l’utiliser sans problème depuis l’événement SendingRequest.

       

      Dans votre WCF Data Service

       

      Comment je récupère le jeton depuis mon service ?

      Si votre service n’est pas configuré pour fonctionner avec OAuth 2.0, suivez l’étape Task 3 – Securing an OData Service with OAuth2 and Windows Identity Foundation du tutoriel mentionné précédemment.

      Votre service utilisera une librairie développée par Microsoft DPE, qui étend le mécanisme de WIF pour supporter OAuth 2.0.

      A ce niveau, vous devriez avoir:

      • ajouté une référence à DPE.OAuth2.dll (il faut télécharger le lab pour l’obtenir)
      • ajouté une référence à Microsoft.Identity.Model.dll
      • ajouté les différentes entrées au web.config

       

      Contrôle de l’identité

      L’identité de l’appelant (que l’on récupère depuis le header) va nous servir dans l’application des règles métier de notre service.

      [System.ServiceModel.ServiceBehavior(IncludeExceptionDetailInFaults = true)]
      public class YourDataService : DataService<[YourContext]>
      {
          // This method is called only once to initialize service-wide policies.
          public static void InitializeService(DataServiceConfiguration config)
          {
              config.SetEntitySetAccessRule("[YourEntity]", EntitySetRights.All);
              config.DataServiceBehavior.MaxProtocolVersion = DataServiceProtocolVersion.V3;
          }
      
          string GetUserIdentity()
          {
              string userIdName = null;
              var claim = HttpContext.Current.User.Identity as IClaimsIdentity;
              if (HttpContext.Current.Request.IsAuthenticated)
              {
                  userIdName = HttpContext.Current.User.Identity.Name;
              }
              return userIdName;
          }

      Vous pouvez utiliser des QueryInterceptor et/ou ChangeInterceptor pour intercepter les requêtes, et ainsi modifier leur comportement par défaut suivant l’identité de l’appelant. Dans mon cas, je stocke l’identifiant de l’appelant dans chaque enregistrement qu’il ajoute dans la base.

      [ChangeInterceptor("[YourEntity]")]
      public void OnChange([YourEntityType] updatedRecord, UpdateOperations operations)
      {
          if (operations == UpdateOperations.Add)
          {
              var userIdName = GetUserIdentity();
              if (userIdName == null)
              {
                  throw new DataServiceException(401, "Permission Denied you must be an authenticated user");
              }
              updatedRecord.UserId = userIdName;
          }
      }

      Les requêtes de sélection ne revoient que les enregistrements associés à l’identité de l’appelant. Seul le créateur des enregistrements pourra y accéder.

      [QueryInterceptor("[YourEntity]")]
      public Expression<Func<[YourEntityType], bool>> OnQuery()
      {
          var userIdName = GetUserIdentity();
          if (userIdName == null)
          {
              throw new DataServiceException(401, "Permission Denied you must be an authenticated user");
          }
          return (b => b.UserId == userIdName);
      }

       

      C’est parti…

      Placez un point d’arrêt dans le ChangeInterceptor ou le QueryInterceptor pour vérifier si tout ce petit monde fonctionne et si votre identité est récupérée correctement côté serveur dans votre service. Si ce n’est pas le cas, essayez d’utiliser IIS plutôt que le Visual Studio Development Server (merci à Benjamin Guinebertière pour l’astuce !)

      image[36]

       

      On récapitule

      Chaque requête OData effectuée depuis notre application Windows Phone inclut un jeton d’authentification, renseigné par le provider d’identité. Cette opération se fait par l’intermédiaire d’ACS. Cette identité peut être utilisée dans des règles métier au niveau du WCF Data Service. Ces règles seront appliquées quel que soit le client duquel provient la requête. Il est ainsi possible de contrôler l’accès et l’édition des données effectués depuis un client aussi simpliste qu’un navigateur web. Sans jeton d’authentification, les requêtes déclencheront un erreur de sécurité (pour ceux qui ne suivent pas : c’est parce que l’on a codé en déclenchant explicitement une exception dans le service).

      image[41]

      Vous pourrez manipuler les données publiées en OData depuis votre application WP7 mais également depuis toute application tournant sur n’importe quelle plateforme supportant une authentification OAuth 2.0.

      Elle est pas belle la vie ? C'est la fête

    • Stéphanie Hertrich

      Using ACS to provide authentication for a WP7 application and a WCF Data Service

      • 0 Comments

      The Windows Azure Toolkit for Windows Phone is helping a lot to integrate all kind of Azure features in the device. Recently, Azure Toolkit for Windows Phone has been split in separate parts (storage, ACS, Push notification, …), so that each can be used separately in a project (via NuGet), which is a VERY good thing.

      Though, it makes it easy to deal with ACS authentication and external identity providers which is what we need in our WP7 application. We want to get an authenticated user to use our app since the OData service will associate data to this user. The service will also ensure that each user will have access to his own data only.

      Back to the Azure Toolkit : we are interested in the ACS part so that our WP7 application provides a log in page and an authenticated user. The NuGet package for this is here:

      image

      The How To is very explicit, though, it is very easy to handle an OAuth 2 authentication in a windows phone app.

      image

      But the procedure doesn’t explain how to set up your remote service so that it can handle the token you just received, and also doesn’t detail how to send it to the server. Actually the answer is a mix of the tutorial dedicated to the inital toolkit, and the new “How To” procedure.

      Here is how to do it in a few steps…

       

      Where we will go

      Our WP7 application needs to access data from the OData service, but only an authenticated user will be allowed to. We also want to filter resulting information according to the user identity.

      image

       

      In your Windows Phone application

       

      Add the NuGet package to your WP7 project

      image

      This package will install all your project needs to use ACS and store the token that will be later reinjected in the OData Http request.

      The “HowTo” ([Your WP7 Project]/App_Readme/Phone.Identity.Controls.BasePage.Readme.htm) is very explicit and you just need to follow the steps to make it work. by the way, you need to have set up an ACS like explained here in Task 2.

       

      Add the Web Browser Capability in the application manifest

      The log in page will be provided from a web browser control, so you should make it available in the manifest file WMAppManifest.xml

            <Capability Name="ID_CAP_WEBBROWSERCOMPONENT"/>
      

       

      Handling Navigation

      Add the log in page as the welcome page in your application, as explained in the How To.

      If your token is valid when you start the app, you will skip the log in page and navigate directly to your application home page. So you will have to handle the Back button from there so that you exit the application. You can do it by clearing the navigation history when you navigate to your home page:

      protected override void OnNavigatedTo(System.Windows.Navigation.NavigationEventArgs e)
      {
          base.OnNavigatedTo(e);
      
          while (this.NavigationService.BackStack.Any())
          {
              NavigationService.RemoveBackEntry();
          }
      }

       

      Try it !

      At this point, you should be able to log into your app with any of your identity provider credentials, for example with a Live ID:

      imageimageimage

       

      Where is my token ?

      Once the log in procedure succeeds, your token is stored as a SimpleWebTokenStore in

      Application.Current.Resources["swtStore"]

       

      How do I use the token in my OData request ?

      The token will be placed in the header of the http request.You should register to the SendingRequest event of your WCF Data Service context and update the header with the authorization.

      _dc = new YourDataServiceContext(new Uri("http://YourDataService.svc/"));
              
      _dc.SendingRequest += new EventHandler<SendingRequestEventArgs>(SendingRequest);
       
      
      void SendingRequest(object sender, SendingRequestEventArgs e)
      {
         var simpleWebTokenStore = Application.Current.Resources["swtStore"] as SimpleWebTokenStore;
         if (simpleWebTokenStore != null)
         {
            e.RequestHeaders["Authorization"] = "OAuth " + simpleWebTokenStore.SimpleWebToken.RawToken;
         }
      }

      AmpouleThe access to the Resources is not allowed from a thread different from the IU thread, so if that may happen in your case, you should save the token in some place from a BeginInvoke so that you can reuse it safely in the SendingRequest event.

       

      In your WCF Data Service

       

      How do I get it back in my WCF Data Service ?

      If your WCF Data Service has not been setup to handle OAuth 2, you should follow the step Task 3 – Securing an OData Service with OAuth2 and Windows Identity Foundation of the great tutorial mentioned earlier.

      You will use an assembly developped by Microsoft DPE guys, that extends the WIF mechanism to handle OAuth.

      At this point you should have:

      • Added a reference to DPE.OAuth2.dll (you should download the lab to get this one)
      • Added a reference to Microsoft.Identity.Model.dll
      • Added entries to the web.config

       

      Checking the identity

      You would probably check the identity on some actions made on your data.

      [System.ServiceModel.ServiceBehavior(IncludeExceptionDetailInFaults = true)]
      public class YourDataService : DataService<[YourContext]>
      {
          // This method is called only once to initialize service-wide policies.
          public static void InitializeService(DataServiceConfiguration config)
          {
              config.SetEntitySetAccessRule("[YourEntity]", EntitySetRights.All);
              config.DataServiceBehavior.MaxProtocolVersion = DataServiceProtocolVersion.V3;
          }
      
          string GetUserIdentity()
          {
              string userIdName = null;
              var claim = HttpContext.Current.User.Identity as IClaimsIdentity;
              if (HttpContext.Current.Request.IsAuthenticated)
              {
                  userIdName = HttpContext.Current.User.Identity.Name;
              }
              return userIdName;
          }

      You can use interceptors (QueryInterceptor or ChangeInterceptor) to do some identity validation and relative tasks, according to your business rules. In my case, I store the user identity name in each new record.

      [ChangeInterceptor("[YourEntity]")]
      public void OnChange([YourEntityType] updatedRecord, UpdateOperations operations)
      {
          if (operations == UpdateOperations.Add)
          {
              var userIdName = GetUserIdentity();
              if (userIdName == null)
              {
                  throw new DataServiceException(401, "Permission Denied you must be an authenticated user");
              }
              updatedRecord.UserId = userIdName;
          }
      }

      On queries, I return only records that are associated to the identity of the request initiator.

      [QueryInterceptor("[YourEntity]")]
      public Expression<Func<[YourEntityType], bool>> OnQuery()
      {
          var userIdName = GetUserIdentity();
          if (userIdName == null)
          {
              throw new DataServiceException(401, "Permission Denied you must be an authenticated user");
          }
          return (b => b.UserId == userIdName);
      }

       

      Let’s give it a try

      You can put a breakpoint on the ChangeInterceptor and QueryInterceptor to check if everything is going fine and if your identity is retrieved properly on the service side. If not, try to use IIS instead of Visual Studio Development Server (thanks to Benjamin Guinebertière for that tip !)

      image

       

      What do I finally have ?

      Each OData request made from your WP application is now including an identity token, allowed by the identity provider through ACS. You can use this identity for business rules purpose in your service, and this will be efficient wherever the request comes from. Though, you can protect your data from being accessed and updated from a simple web browser which will return a security exception.

      image

      You can still use these data from any application running on any platform that is able to send an OAuth http authorization header.

    • Stéphanie Hertrich

      WP7 : Real-time video scan a barcode/QR code in your app using ZXing lib

      • 17 Comments

      Now that we can scan a barcode from a picture thanks to ZXing lib, let’s try to improve the user experience.

      It would be much better to scan a barcode in real time, just like WP7 does out of the box. I wanted to provide this kind of experience in my own application. You can do the same by downloading the following lib and use the code below.

      Get Microsoft Silverlight

       

      Usage

      Download the lib

      Download the binaries and add a reference to both dlls in your own project.

      Write the code

      Use the video scan lib by calling the static StartScan method of the BarCodeManager static class.

      /// <summary>
      /// Starts the scan : navigates to the scan page and starts reading video stream
      /// Note : Scan will auto-stop if navigation occurs
      /// </summary>
      /// <param name="onBarCodeFound">Delegate Action on a barcode found</param>
      /// <param name="onError">Delegate Action on error</param>
      /// <param name="zxingReader">(optional) A specific reader format, Default will be EAN13Reader </param>
      public static void StartScan(Action<string> onBarCodeFound, Action<Exception> onError, BarcodeFormat barcodeFormat = null)

      Parameters are:

      - Action<string> onBarCodeFound : a delegate called on a background thread as soon as a barcode/QR code is detected. The parameter is the associated barcode/QR string 
      - Action<Exception> onError : a delegate on error. The parameter is the associated exception that generated the error - BarcodeFormat barcodeFormat = null : the type of code that should be scanned (default = UPC_EAN): ALL_1D;
      CODE_128;
      CODE_39;
      DATAMATRIX;
      EAN_13;
      EAN_8;
      ITF;
      PDF417;
      QR_CODE;
      UPC_A;
      UPC_E;
      // Auto detect
      UPC_EAN;

      Calling StartScan will launch the WP7 camera, and start the scanning process which consists in:

      1. start focusing
      2. once focused, try scanning during 1.5 sec

      If unsuccessful, it will retry 15 times after what it will call the onError callback with a VideoScanException exception as a parameter. You can update the trial count by setting BarCodeManager.MaxTry (default is 15).

      If a code was found, the onBarCodeFound callback will be called with the string code found as a parameter.

      Example

      image

      Scan a bar code

      private void Button_Click(object sender, RoutedEventArgs e)
      {
          WP7.ScanBarCode.BarCodeManager.StartScan(
              // on success
              (b) => Dispatcher.BeginInvoke(() => 
                  {
                      tbScanResultBarCode.Text = b;
                      NavigationService.GoBack();
                  }),
              // on error
              (ex) => Dispatcher.BeginInvoke(() => 
                  {
                      tbScanResultBarCode.Text = ex.Message;
                      NavigationService.GoBack();
                  })
              // Default : please, decode any bar-code
              );
                                       
      }

      Scan a QR code

      private void Button_Click_1(object sender, RoutedEventArgs e)
      {
          WP7.ScanBarCode.BarCodeManager.StartScan(
              // on success
              (b) => Dispatcher.BeginInvoke(() => 
                  {
                      tbScanResultQR.Text = b;
                      NavigationService.GoBack();
                  }),
              // on error
              (ex) => Dispatcher.BeginInvoke(() => 
                  {
                      tbScanResultQR.Text = ex.Message;
                      NavigationService.GoBack();
                  }),
              // Please, decode a QR Code
              BarcodeFormat.QR_CODE);
      }
       

      Get the source code and test the sample application

      You can test the sample application ang get the whole code here.

      The lib uses the video capabilities of WP7.1 SDK with the PhotoCamera class. The code is inspired from the original idea of Pierre Cauchois and includes some snippets from SLAR toolkit.

      [This is an early version made for my own purpose and was not tested for production]

    • Stéphanie Hertrich

      Evènement communauté Silverlight mercredi après-midi, 26 octobre

      • 0 Comments

      La communauté Silverlight qui s’est montée récemment sur Facebook (http://on.fb.me/pVH80X) organise son premier évènement dans nos locaux ce mercredi après-midi (le 26 octobre).

       

      N’hésitez pas à venir assister aux sessions inédites :

       

      La communauté Silverlight France se lance, venez participer à sa naissance !


      - Session “Présentation de la Communauté” (15mn)

      Découvrez la communauté, son site web, son mode de fonctionnement et comment contribuer à celle-ci. Vous aimez Silverlight et vous souhaitez partager ? Alors venez aider et contribuer ! Vous aimez Silverlight mais vous ne savez pas par quel bout commencer ? Venez comprendre comment apprendre auprès d’experts reconnus sur le meilleur des ressources francophones. Vous ne connaissez pas encore Silverlight ? Venez découvrir et mesurer tout le potentiel de cette technologie qui a déjà fait ses preuves aujourd’hui et qui réserve bien des surprises à l’avenir.

      - Session “Silverlight 5 : Nouveautés et 3D” (1h20)

      Présenté par David Catuhe (Microsoft France), Cyril Cathala (So@t) et Nathanael Marchand (So@t)

      Silverlight 5 arrive et apporte son lot de nouveautés. A travers cette session, vous allez pouvoir découvrir, comprendre et analyser toutes les nouveautés de cette version qui repousse encore plus loin les limites des RIA. Au menu, fenêtres natives, intégration encore plus poussée avec Windows, capacités multimédia améliorées tout ceci en proposant des outils encore plus performants pour le développement et le débogage.

      Mais la plus grosse innovation de Silverlight reste l’intégration du moteur XNA: la 3D dans Silverlight. Aidés de la dernière version du Silverlight Toolkit, nous découvrirons comment profiter d’une des plateformes de référence utilisée pour les jeux vidéo (sur XBOX et Windows Phone 7) afin que vos visiteurs soient époustouflés par des visites virtuelles, des catalogues de produits animés et d’autres raffinements qu’aucune autre technologie web ne permet si simplement.

      - Session “Rx Framework” (40mn)

      Présenté par Aymeric Lagier (SUPINFO/MSP Expert Silverlight), Christophe Argento (Ineat Conseil) et Kévin Alexandre (Wygwam)

      Le challenge des interfaces d’aujourd’hui est de proposer toujours plus de fluidité tout en poussant la complexité tant au niveau des sources de données que du volume de données. Venez découvrir cette

      bibliothèque de Microsoft qui permet de simplifier le développement, gagner en fluidité tout en maitrisant les pièges de l’asynchrone.

      - Session “Communication en Silverlight” (40mn)

      Présenté par John Thiriet (MCNext) et Matthieu Mezil (Infinite Square/MVP)

      Communiquer efficacement en Silverlight est un challenge. Vous pourrez découvrir lors de cette session des méthodes et astuces pour que votre application soit efficace face aux scénarios les plus complexes. La nouvelle couche Low Latency de Silverlight 5, la comparaison entre la pile de communication navigateur et client, WCF pour Silverlight seront notamment les sujets abordés lors de cet après-midi.

      - Session “Retour d’expérience MVVM” (40mn)

      Animé par Stéphanie Hertrich (Microsoft France) et Jonathan Antoine (Infinite Square/MVP)

      Session spéciale, interactive et inédite : nous discuterons ensemble de MVVM afin de bénéficier du retour d'expérience de chacun. La session se voudra agile et sera construite d'après vos retours, en abordant de nombreux thèmes comme les difficultés rencontrées fréquemment avec ce pattern, les frameworks du marché, etc.


      A la suite de ces sessions, un petit cocktail sera proposé afin de partager en toute convivialité avec les acteurs de la communauté ainsi que l’équipe de développement du Silverlight 5 Toolkit présente en exclusivité pour vous.

      Heure
      mercredi 26 octobre · 14:00 - 18:00

      Lieu
      Campus Microsoft
      41 Quai du Président Roosevelt
      Issy-les-Moulineau, France

       

      A mercredi ! Verre à cocktail

    • Stéphanie Hertrich

      Application CaveAVins pour Windows Phone : Scanner un code barre

      • 0 Comments

      Edit 14/11/2011 : Mieux que de prendre le code en photo : Scannez un code barre/QR en live dans un flux vidéo en 1 ligne de code ! http://bit.ly/ud2rFn 

      Cet article complète la première maquette de l’application CaveAVins pour Windows Phone 7.5, en ajoutant une fonction d’aide à l’achat grâce au scan du code-barre de la bouteille.

      L’application initiale permet d’ajouter, modifier, noter, localiser géographiquement un vin. Pour simplifier la création d’un vin et surtout obtenir une aide à l’achat d’un vin en boutique, il sera possible de prendre le code-barre de la bouteille en photo, de visualiser les notes qui lui ont été données par les autre utilisateurs et d’obtenir la fiche complète du vin. Si cette bouteille n’a pas encore été évaluée, la fiche du vin devra être créée manuellement et sera ainsi prête pour les prochains acheteurs.

      image

      Nous complèterons l’application existante, en mettant en place les éléments suivants:

      • Utilisation de la librairie de scan de code-barres et QR code ZXing, sur CodePlex
      • Ajout d’une tuile secondaire permettant d’accéder au scan en 1 clic, depuis l’écran d’accueil
      • Ajout de la note moyenne des utilisateurs sur la fiche de vin

      Résumé des épisodes précédents

      Articles:

      Tutoriels:

      Le fonctionnement dans l’application

      1 - Le scan de code-barre est accessible à partir de la tuile secondaire ou de la page d’ajout/édition d’un vin

      imageimage

      2 - Le code-barre d’une bouteille est pris en photo

      WP_000243

      3 – Ce vin existe déjà dans le catalogue : sa fiche est complétée automatiquement, ou une nouvelle fiche de vin sera créée. La note moyenne donnée par les utilisateurs s’affiche au bas de la photo.

      image

      Utilisation de la librairie ZXing

      Téléchargez la librairie ZXing sur codeplex. Elle permet de scanner différents types de code-barres ainsi que des code QR dont voici la liste:

      UPC-A and UPC-E
      EAN-8 and EAN-13
      Code 39
      Code 128
      QR Code
      ITF
      Data Matrix (Not tested)
      PDF417 (Not tested)

      Dans notre cas, nous utiliserons le type Code 128.

      La librairie est sous licence Apache 2.0 et peut donc être utilisée sur le marketplace.
      Plus d’information concernant l’utilisation de librairies open-source sur le marketplace:

      Pour utiliser la librairie, référencez-la dans votre application Windows Phone

      image

      Le scan de code barre est déclenché par le code ci-dessous où l’on récupère le résultat de manière asynchrone:

      /// <summary> /// Button click handler to initiate scanning. This should be ted to the click event for a button in your application /// </summary> public void ScanBarCode()
      {
          try {
              WP7BarcodeManager.ScanMode = com.google.zxing.BarcodeFormat.UPC_EAN;
              WP7BarcodeManager.ScanBarcode(BarcodeResults_Finished); //Provide callback method     }
          catch (Exception ex)
          {
              Debug.WriteLine("Error processing image.", ex);
          }
      }
      
      /// <summary> /// Callback method that processes results returned by the WP7BarcodeManager. Results are also stored at WP7BarcodeManager.LastCaptureResults. /// </summary> /// <param name="BCResults">Object that holds all the results of processing the barcode. Results are also stored at WP7BarcodeManager.LastCaptureResults.</param> public void BarcodeResults_Finished(BarcodeCaptureResult BCResults)
      {
        Deployment.Current.Dispatcher.BeginInvoke(() =>
        {
          try {
              if (BCResults.State == CaptureState.Success)
              {
                  BarCode = BCResults.BarcodeText; //Use results FindWineFromBarCode(_BarCode);
              }
              else {
                  MessageBox.Show(BCResults.ErrorMessage);
              }
          }
          catch (Exception ex)
          {
              MessageBox.Show(String.Format("Barcode Processing Error: {0}", ex.Message));
          }
        });
      }

      En suivant l’exemple de code plus complet fourni dans la page de documentation, vous pourrez même faire fonctionner votre application dans l’émulateur : il suffit dans ce cas de choisir une image sur votre disque plutôt que de prendre une photo.

      On recherche ensuite un vin ayant le code-barre détecté et on charge sa fiche s’il existe déjà dans la base, la fiche reste vierge en dehors du code-barre et devra être remplie manuellement.

      private void FindWineFromBarCode(string barCode)
      {
          // On cherche d'abord dans notre propre cave à vins var existingWine = CaveAVinsModel.Instance.Bottles.FirstOrDefault(b => b.WineInfos.BarCode == barCode);
          if (existingWine != null)
          {
              MessageBox.Show(Resources.Strings.Wine_AlreadyExistsInYourCave);
              SelectedBottle = existingWine;
          }
          else {
              // sinon on recherche le vin dans le catalogue complet CaveAVinsModel.Instance.FindWineFromBarCode(SelectedBottle, barCode,
                   () => RefreshWineInfos(SelectedBottle));
          }
      }

      Ajout d’une tuile secondaire pour scanner une bouteille en 1 clic depuis l’écran d’accueil

      Pour faciliter l’ajout d’un vin, il est possible d’épingler la fonction de scan d’un code-barre directement sur la page d’accueil du téléphone.

      image

      La tuile secondaire est créée en cliquant sur le bouton image visible sur la page d’édition du vin.

      Voici le code utilisé pour créer la tuile :

      void CreateApplicationSecondaryTile()
      {
          var appTile = ShellTile.ActiveTiles
              .FirstOrDefault(t => t.NavigationUri.ToString().Contains("Scan"));
          if (appTile == null)
          {
              StandardTileData secTileData = new StandardTileData()
              {
                  BackgroundImage = new Uri("/icons/barcode.png", UriKind.Relative),
                  Title = Strings.secTileBarCode_Title,
              };
      
              ShellTile.Create(new Uri("/View/EditWineView.xaml?Scan=1", UriKind.Relative), secTileData);
          }
      }

      Un paramètre “Scan” est passé à la page EditWineView pour différencier les sources d’appel : depuis la tuile ou depuis l’application. Si c’est la tuile qui a déclenché la navigation sur la page, on lance directement la fonction de scan, pour minimiser le nombre de clics. Cette opération est réalisée en surchargeant la méthode OnNavigatedTo:

      protected override void OnNavigatedTo(System.Windows.Navigation.NavigationEventArgs e)
      {
          if (e.NavigationMode == System.Windows.Navigation.NavigationMode.New)
          {
              if (NavigationContext.QueryString.ContainsKey("Scan"))
              {
                  App.ViewModel.EditWineViewModel.ScanBarCode();
              }
          }
          else {
              base.OnNavigatedTo(e);
          }
      }

      Ajout de la note moyenne des utilisateurs sur la fiche de vin

      Il faut pouvoir différencier la note que l’on donne au vin, de la moyenne de celles données par les autres utilisateurs. C’est pourquoi l’affichage de la note moyenne du vin est ajouté au bas de la photo, sur la fiche du vin.

      image

      Pour l’instant, celle-ci est calculée en temps réel lors de l’ouverture de la fiche : on récupère toutes les fiches de caves associées à ce vin qui comportent une note et on en fait une moyenne. Mais on pourrait imaginer la stocker dans la fiche et la mettre à jour à partir de notre WCF Data Service à l’aide d’un intercepteur sur les requêtes d’ajout/modification sur l’entite MyWine. Ceci permettrait d’optimiser la communication entre l’application et la base de données. Ce principe d’intercepteurs a déjà été utilisé dans l’article précédent, pour stocker la latitude et la longitude associés à une adresse géographique et vous pouvez vous y référer pour comprendre comment cela fonctionne.

      Conclusion

      Cette fois-ci , l’application est réellement collaborative ! On retrouve une bouteille de vin déjà présente dans le catalogue, on peut voir la moyenne des notes qui lui ont été attribuées et également la noter soi-même. De là à ce qu’elle soit vraiment fonctionnelle, il n’y a qu’un pas…ou peut-être plusieurs, soit. Il manque encore plein de petits plus très pratiques comme pouvoir saisir le code-barre à la main ou retrouver une bouteille à partir de son nom. Il faudrait également pouvoir classer les vins dans des catégories et cépages.

      Mais vous l’aurez compris, le principe pour réaliser ces fonctions sont du même ordre que celles qui ont déjà été réalisées et ne justifient pas l’écriture d’un tutoriel dédié. D’autres fonctions collaboratives seraient néanmoins intéressantes comme le partage sur Facebook ou la possibilité de faire partager un vin à un autre utilisateur de l’application.

      D’autres plateformes peuvent aussi être très intéressantes pour ce genre d’applications avec notamment des possibilités avancées pour le partage d’information et les notifications.

      A suivre  Tire la langue !

    • Stéphanie Hertrich

      Nouvelle saison 2011 des MsDays

      • 0 Comments

      ban-GENERIQUE_480x252

      Plus que quelques jours avant la nouvelle saison 2011 des MsDays.

      J’aurai le plaisir de participer à 2 sessions très différentes.

      Concevoir une application : mode d’emploi – les dessous de l’application CaveAVins

      En partant du besoin fonctionnel de départ, nous choisirons les technologies adéquates pour implémenter notre application. D'Entity Framework, à WCF Data Services, en passant par la migration dans le Cloud, SQL Azure, WP7, Bing Map. Le but étant de vous en montrer les points clés.

      Une session co-présentée avec Eric Vernie, qui reprend le fil conducteur de la série d’articles sur la cave à vins.

      Comment développer une application avec les technologies actuelles, en respectant l’évolution des usages. Un bon résumé de la conception de bout en bout, depuis le stockage des données jusqu’à l’application Windows Phone et à l’ouverture vers d’autres technologies clientes. Une session dense, avec du code, qui vous permettra d’avoir un bon tour d’horizon des technologies de développement.

      Office 365 : Industrialisation du dev Sharepoint Online

      Office 365 vient d’être lancé avec son offre de plateforme collaborative adaptée à toutes les tailles d’Entreprise, de la messagerie aux solutions synchrones, en passant par le portail collaboratif.
      Vous connaissez déjà les bonnes pratiques de développement sur SharePoint Online : sandbox, intégration Silverlight, Linq to SharePoint, WCF, etc.
      Venez découvrir dans cette session comment passer à la vitesse supérieure en industrialisant vos développements de bout en bout, des spécifications fonctionnelles au déploiement et à la maintenance applicative dans le cloud.
      Un témoignage exclusif de Calinda Software, une Start-up française éditrice de solution sociale autour de SharePoint, qui viendra illustrer cette vision à travers leur propre expérience sur la Marketplace Office 365 pour le portage de leur solution dans le Cloud.

      Une session co-présentée avec Na-Young Kwon (responsable produit Sharepoint) et la société Calinda.

      Après un rappel des différences entre Sharepoint et Sharepoint Online par Na-Young, Calinda vous parlera des principales difficultés rencontrées lors du passage sur Sharepoint Online te sur la Marketplace. J’entrerai ensuite plus dans le code en vous montrant quelques astuces pour le développement sur la version Online de Sharepoint.

      A très bientôt !

    • Stéphanie Hertrich

      Développer un projet aujourd’hui : L’application CaveAVins pour Windows Phone 7.1

      • 6 Comments

      Update 18/10/2011: l’url du service OData devient http://stephecaveavins.cloudapp.net/CaveAVinsDataService.svc

      Cet article décrit le fonctionnement de la première maquette de l’application Cave A Vins pour Windows Phone. Il explique les points sensibles de l’application et détaille leur réalisation, sans rentrer autant dans le détail qu’un tutoriel.

      Pas si basique que cela, notre application va mettre en œuvre les concepts suivants:

      Nous allons également modifier le service WCF Data Services pour le rendre plus intelligent et effectuer automatiquement la transformation d’une adresse en coordonnées latitude/longitude que l’on ajoutera aux informations sur le vin.

      Ampoule L’application est développée pour Windows Phone 7.1. Pour découvrir les nouveautés par-rapport à la version 7.0, c’est ici.
      Prérequis :
      Windows Phone SDK 7.1 Release Candidate

      Un petit aperçu du résultat:

      Get Microsoft Silverlight

       

       

      Résumé des épisodes précédents

      Articles:

      Tutoriels:

      Présentation des écrans

      Voilà à quoi ressemble notre application de gestion de Cave A Vins.
      L’application utilise un contrôle Panorama, avec un en-tête déroulant affichant autant d’images de bouteilles que vous avez de vins différents dans votre cave. Si vous avez des yeux de lynx (‘achement dur ne pas écrire Linq ou Lync Extra-terrestre), vous remarquerez qu’elles portent le nom du vin sur leur étiquette. Bon c’est pas fait pour être lisible, juste décoratif : les bouteilles défilent quand vous passez d’un item panorama à un autre Tire la langue (cf vidéo).

      1 image 2 image 3 image

      1 - La première page affiche la liste des vins présents dans la cave triés par date décroissante d’achat.

      2 - La seconde utilise le contrôle LongListSelector du toolkit Silverlight pour WP7 pour afficher la même liste, groupée par année. On pourrait imaginer ajouter les informations sur les cépages et proposer le même type de vue groupée ce qui serait très pratique.

      3 - La troisième page utilise le contrôle Bing Maps pour afficher l’origine des vins et le nombre de bouteilles associées. Cela permet de repérer facilement ce qu’il manque à la cave pour être suffisamment variée géographiquement parlant.

      4 image 5 image

      4 – le menu contextuel permet de décrémenter le nombre de bouteilles du vin sélectionné, d’ajouter/éditer/supprimer un vin de sa cave.

      5 – l’édition d’un vin permet de saisir les informations qui lui sont relatives ainsi que de prendre la photo de son étiquette

      Architecture de la solution

      Rappel de l’architecture existante après la migration dans Azure:

      image48

      Et l’architecture qui sera mise en place dans le contexte de cet article :

      image

      Ampoule Le service proxy d’authentification “CaveAVins Blob Authentifier” est facultatif mais conseillé pour ne pas divulguer les clés d’accès au storage Azure.

      A ce stade, plusieurs questions restent encore en suspens :

      • Quelle architecture vais-je mettre en place dans mon application WP ?
      • Comment identifier le propriétaire de la cave pour retrouver quelles sont ses bouteilles dans la base de données ?
      • Comment situer géographiquement mes vins sur un contrôle Bing Map à partir d’une simple adresse ?
      • Comment accéder aux blobs Azure depuis le téléphone, pour y stocker les photos ?
      • Comment modifier la structure des données créée avec Entity Framework Code First pour y ajouter de nouvelles informations ?
      • Existe-t-il des contrôles gratuits pour faciliter le développement ? Liste groupée, contrôle rating pour la note associée à la bouteille, menu contextuel, animations …

      L’architecture logique de mon application WP

      De mon point vue, la question est moins triviale pour une application Silverlight WP7 que pour du Silverlight classique.

      Pour ma part, en Silverlight je pars toujours sur du “MVVM-like” plus ou moins strict, selon l’importance et la durée de vie de mon application. Je pars soit de mes classes de base et méthodes d’extension qui constituent ma boite à outils que j’ai créé au fil des projets, soit d’un framework ou partie de framework existant type MVVMLight, Caliburn, Prism…

      AmpouleEn 3 mots, si vous ne savez pas ce qu’est MVVM (Model – View – ViewModel) : c’est une architecture 3-tiers avec

      • Model = le modèle (les données)
      • View = une vue (une page ou morceau de page, contrôle, …bref, un truc qui s’affiche à l’écran et se décrit en xaml Tire la langue)
      • ViewModel = un cas d’utilisation associé à une ou plusieurs vues. Un ViewModel est une classe qui présente des propriétés “notifiables” et bindables à la vue. En principe il n’y a pas ou peu de code-behind dans les vues et ainsi tout le code lié au cas d’utilisation exprimé par la vue est concentré dans la classe ViewModel qui lui est associé.

      La bible de ce qu’est l’architecture MVVM, avec un petit exemple et le code associé qui va bien : http://msdn.microsoft.com/en-us/magazine/dd419663.aspx

      Le choix est moins immédiat sur WP7 qu’en Silverlight classique ou WPF, car beaucoup de contrôles standards “metro styled” n’intègrent pas complètement les bindings (ex : les boutons de la barres de menu). Heureusement, plusieurs variantes de frameworks MVVM ont été portées pour WP7, comme MVVMLight, Caliburn, …

      Dans mon cas, je n’utiliserai pas de framework prêt à l’emploi – ce n’est pas le sujet de cet article -, mais je reste sur une architecture de type MVVM, sans être trop stricte, en conservant du code behind, notamment pour la navigation, et la gestion des boutons de la barre de menus.

      image

      La couche View est composée de vues : MainView qui contient le panorama à 3 items et EditWineView qui permet d’éditer un vin.

      La couche ViewModel contient les 2 ViewModels  associés aux vues : MainViewModel et EditWineViewModel.

      La couche Model est composée de la classe CaveAVinsModel qui est générée automatiquement lors de l’ajout du service CaveAVins, à partir des metadata, ce qui fait gagner un temps fou. D’autant plus que ces classes proxy implémentent déjà les mécanismes de notification (INotifyPropertyChanged). Du coup, mon MainviewModel va pouvoir directement exposer ces collections à la vue, sans passer par une classe WineViewModel intermédiaire. C’est un premier raccourci par-rapport à du MVVM.strict, mais dans ce cas là, c’est nettement plus productif.

      La classe métier UploadPhoto prend en charge la sauvegarde des photos dans l’Azure Blob Storage, permettant ainsi que les couches Viewmodel et View ne soient pas couplées au mécanisme des blobs. On pourrait ainsi facilement stocker les photos ailleurs sans retoucher aux couches de présentation.

      La couche DAL correspond à l’accès aux données : aux blobs Azure et à notre service WCF Data Services hébergé lui aussi dans Azure.

      Identifier le propriétaire de la cave

      Pour pouvoir associer des bouteilles à un propriétaire, plusieurs options s’offrent à nous et en l’occurrence, c’est la plus simple qui a été retenue.

      Le téléphone stocke un numéro associé au Live Id du propriétaire (renseigné dès le téléchargement de la première application sur le MarketPlace). Nous pouvons donc utiliser cet identifiant pour rattacher un vin à son propriétaire, sans pour autant lui demander d’informations d’authentification ou de connexion. Et si vous changez de téléphone, vous ne perdez pas pour autant votre cave à vins.

      Voici un exemple de code permettant d’accéder au numéro de l’utilisateur:

       public string GetUserAnId()
              {
                  string myId = string.Empty;
                  string anid = UserExtendedProperties.GetValue("ANID") as string;
                  if (anid != null)
                  { 
                      myId = anid.Substring(2, 32);
                  }
      
                  return myId;
              }

      Une autre solution serait d’utiliser la fédération d’identité, en utilisant Azure Access Control Services (ACS) et un compte provenant d’un fournisseur d’identité comme Hotmail, Facebook, Google,… Cela évite à l’utilisateur de créer un énième compte dédié à l’application de cave à vins et ça permet au développeur de ne pas réinventer la roue en créant un mécanisme de gestion de comptes qui lui est propre.

      Le Toolkit Azure pour Windows Phone permet de simplifier cette opération en fournissant un template tout prêt pour ce genre d’applications. Benjamin a réalisé une vidéo de quelques minutes pour vous montrer comment utiliser le toolkit pour la gestion des authentifications.

      Localiser les vins sur un contrôle Bing Map

      Une des vues de mon panorama est une carte Bing Map matérialisant les quantités restantes de chacun des vins de ma cave. C’est une bonne aide à l’achat pour garantir une cave variée et cela me permet de vérifier rapidement si je ne manque pas de vin d’Alsace !

      Le contrôle Bing Map fait cela très bien en affichant des punaises correspondant à une liste de coordonnées géographiques (latitude, longitude). Dans notre cas, la fiche d’un vin contient une adresse et il faut donc passer par une étape intermédiaire avant de pouvoir afficher la punaise : la transformation de l’adresse en latitude et longitude. Bing Map fournit un service de résolution utilisable avec un compte développeur gratuit. Pour plus d’information sur les conditions d’utilisation : http://www.microsoft.com/maps/product/licensing.aspx.

      L’application Windows Phone pourrait réaliser cette résolution dynamiquement lors du lancement de l’application, mais cela signifie que je ferais autant de résolutions que de vins dans ma cave, à chaque chargement des données. Une meilleure idée serait de modifier directement la fiche du vin lors de l’ajout ou de la modification d’un vin. Mais si c’est mon application WP7 qui s’en charge, cela signifie que tout autre client potentiel qui ajouterait des vins dans la cave ne disposerait pas de la latitude et longitude. L’idéal serait que quiconque utilise le service bénéficie de cette résolution d’adresse, même un simple client http.

      Nous allons donc déporter cette résolution au niveau du service WCF Data Services à l’aide d’”intercepteurs”'. Voici comment cela se met place dans notre solution, dans la classe CaveAVinsDataService :

       public class CaveAVinsDataService : DataService<CaveAVins.Db.CaveAVinsContext>
          {
              // This method is called only once to initialize service-wide policies. public static void InitializeService(DataServiceConfiguration config)
              {
                  config.SetEntitySetAccessRule("Wines", EntitySetRights.All);
                  config.SetEntitySetAccessRule("Bottles", EntitySetRights.All);
                  config.DataServiceBehavior.MaxProtocolVersion = DataServiceProtocolVersion.V3;
              }
      
      
              // Define a change interceptor for the Products entity set. [ChangeInterceptor("Wines")]
              public void OnChangeProducts(Wine updatedWine, UpdateOperations operations)
              {
                  CurrentDataSource.ChangeTracker.DetectChanges();
                  foreach (var uw in CurrentDataSource.ChangeTracker.Entries<Wine>())
                  {
                      string oldValue = null;
                      if (operations == UpdateOperations.Change)
                      {
                          oldValue = CurrentDataSource.ChangeTracker.Entries<Wine>().First().OriginalValues.GetValue<string>("Address");
                      }
      
                      if (oldValue != updatedWine.Address)
                      {
                          // Update Latitude & longitude var geoloc = MakeGeocodeRequest(updatedWine.Address);
                          if (geoloc != null)
                          {
                              uw.Entity.Latitude = geoloc.Latitude;
                              uw.Entity.Longitude = geoloc.Longitude;
                          }
                      }
                  }
      
              }

      On ajoute une méthode préfixée d’un custom attribute ChangeInterceptor("Wines")qui permet d’intercepter les requêtes de modification et d’ajout sur la table Wine. Si cette opération concerne un ajout ou qu’une modification de la propriété Address a été détectée, on appelle le service BingMap qui nous renverra une latitude et une longitude à partir de l’adresse. Ces informations sont alors répercutées directement dans l’entité Wine.

      C’est tout côté serveur.

      Dans l’application, il suffit de binder la collection de vins au contrôle Bing Map.

           <my:Map CredentialsProvider="Your credentials"
                   Center="46.642000079154968,2.3379997909069061"
                   ZoomLevel="5">
                <my:MapItemsControl ItemsSource="{Binding Model.Bottles}">
                     <my:MapItemsControl.ItemTemplate>
                          <DataTemplate>
                               <my:Pushpin Location="{Binding Converter={StaticResource BottlesToLocationConv}}"                                             
                                           Tap="Image_Tap"
                                           Background="DarkRed" FontWeight="Bold">
                                          
                                   <TextBlock HorizontalAlignment="Center" VerticalAlignment="Center" 
                                              Text="{Binding Count}"/>
          
                                   <my:Pushpin.Template>
                                        …
                                   </my:Pushpin.Template>
                               </my:Pushpin>
                          </DataTemplate>
                    </my:MapItemsControl.ItemTemplate>
                </my:MapItemsControl>
            </my:Map>
      
      

      Pour cela, j’utilise un converter qui me renvoit une instance de Geocoordinates (latitude, longitude) à partir d’une instance de Wine. Puis je personnalise le PushPin pour qu’il affiche le nombre de bouteilles, c’est à dire la propriété Count de la classe MyWine.

      public class BottlesToLocationConverter : IValueConverter
      {
          public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
          {
              MyWine wine = value as MyWine;
              GeoCoordinate result = null;
      
              if (wine != null)
              {
                  result = new GeoCoordinate(wine.WineInfos.Latitude.GetValueOrDefault(), 
                      wine.WineInfos.Longitude.GetValueOrDefault());
              }
      
              return result;
          }
      
          public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
          {
              return null;
          }
      }

      Le tour est joué !

      Accéder au service OData

      Le SDK Windows Phone 7.1 fournit un client WCF Data Services (vive Linq) ainsi que la possibilité de générer les classes proxy d’un service directement dans Visual Studio : bref, c’est tout pareil qu’en Silverlight classique.

      On commencer par ajouter la référence du service de cave à vins : http://stephecaveavins.cloudapp.net/CaveAVinsDataService.svc. Puis on instancie la classe de contexte qui vient d’être générée. Ensuite on construit sa requête de sélection ou on effectue les opérations CUD et on exécute tout cela de manière asynchrone.

      Reste la gestion du tombstoning du contexte WCF Data Services qui permettra de retrouver un état cohérent de nos entités : How to persist the state of an OData client for Windows Phone. En effet, depuis Windows Phone 7.1, la persistence des données est facilitée par la nouvelle méthode “Serialize” au niveau du contexte WCF Data Services.

      Tous les détails dans le tutoriel associé à paraitre.

      Modification de la structure de la base de données

      Pour permettre de réaliser ces fonctionnalités, nous avons besoin d’ajouter des informations dans la base de données et donc de modifier sa structure. Rappelons que celle-ci a été créée automatiquement à partir de nos classes POCOs, grâce à Entity Framework Code First.

      Il est également possible d’associer des classes POCOs à une base de données existante, et c’est ce qui a été fait dans notre cas, pour ajouter de nouvelles colonnes/propriétés. La structure de la base a été modifiée directement à partir du portail SQL Azure et les nouvelles propriétés de nos POCOS sont associées aux nouvelles colonnes de nos tables grâce à l’API Fluent pour Entity Framework Code First.

      Avant :

      image

      Maintenant:

      image

      L’entité MyWine est complétée par

      • la date d’achat du vin : pour le tri dans l’application et pouvoir visualiser facilement la dernière bouteille achetée

      L’entité Wine est complétée par:

      • Latitude: pour effectuer 1 seule fois la transformation Addresse/Coordonnées pour chaque vin affiché sur contrôle BingMap
      • Longitude : idem latitude
      • BarCode : pour identifier une bouteille à partir de la photo de son code barre – à venir

      Avec Fluent, cela se fait en quelques lignes dans le contructeur du contexte, après avoir complété les POCOs par les nouvelles propriétés:

      public class CaveAVinsContext : DbContext {
          public DbSet<MyWine> Bottles { get; set; }
          public DbSet<Wine> Wines { get; set; }
      
          public CaveAVinsContext()
              : base()
          {
              Database.SetInitializer<CaveAVinsContext>(null);
          }
      
          protected override void OnModelCreating(DbModelBuilder modelBuilder)
          {
              modelBuilder.Entity<MyWine>().Property(p => p.UserId);
              modelBuilder.Entity<MyWine>().Property(p => p.AddedDate);
      
              modelBuilder.Entity<Wine>().Property(p => p.BarCode);
      
              modelBuilder.Entity<Wine>().Property(p => p.Latitude);
              modelBuilder.Entity<Wine>().Property(p => p.Longitude);
              base.OnModelCreating(modelBuilder);
          }
      }

      Prendre la bouteille en photo

      C’est très facile grâce à l’API WP7 dédiée et cela se fait en quelques lignes, du genre :

      void TakeAPic()
      {
          CameraCaptureTask task = new CameraCaptureTask();
          task.Completed += (sender, photoResult) =>
          {
              if (photoResult.TaskResult == TaskResult.OK)
              {
                  BitmapImage bmp = new BitmapImage();
                  bmp.SetSource(photoResult.ChosenPhoto);
                  PhotoSource = bmp;                                
              }
          };
          task.Show();
      }

      Attention de pas oublier de :

      • faire une rotation de l’image pour la stocker en mode portrait. On pourrait le faire à l’affichage par un RenderTransform mais du coup il faudrait l’effectuer dans chaque application cliente => factorisation métier côté serveur quand on peut !
      • réduire la taille de la photo avant de la stocker dans le blob storage, puisque nous n’utiliserons que des vignettes.

      Stocker les photos dans le blob Azure

      Une fois la photo prise et la fiche enregistrée, il faut :

      • soumettre la requête de modification au service WCF Data Services de notre Cave à Vins
      • uploader la photo (nommée avec un nouveau Guid pour pouvoir l’identifier de manière unique) dans les blobs Azure.

      Une fois de plus, le Toolkit Azure pour Windows Phone va nous aider en facilitant l’utilisation des blobs à partir de notre smartphone grâce à la librairie WindowsPhoneCloud.StorageClient.dll

      Pour commencer simplement, vous pouvez partir d’un sample de code issu du toolkit qui ne cible que la partie storage (merci Wade). Les credentials sont placés dans les ressources de l’application et sont donc visibles par tout un chacun, ce qui n’est pas recommandé. Mais dans un premier temps, on s’en contentera pour simplifier.

      Grâce à la classe CloudBlobClient fournie dans WindowsPhoneCloud.StorageClient.dll, l’upload de la photo (propriété PhotoStream de type Stream) s’effectue ainsi :

          public void UploadPhoto(Action callback = null)
          {
              this.IsUploading = true;
      
              this.blobClient.Upload(
                  this.BlobName,
                  this.PhotoStream,
                  r => this.dispatcher.BeginInvoke(
                          () =>
                          {
                              this.IsUploading = false;
      
                              if (r.Exception == null)
                              {
                                  MessageBox.Show(
                                      string.Format(CultureInfo.InvariantCulture, 
                                      "Image file {0} successfully uploaded!", this.BlobName),
                                      "Upload Photo Result",
                                      MessageBoxButton.OK);
      
                                  if (callback != null)
                                  {
                                      callback.Invoke();
                                  }
                              }
                              else
                              {
                                  MessageBox.Show(
                                      string.Format(
                                          CultureInfo.InvariantCulture,
                                          "Error: {0}",
                                          r.Exception.Message),
                                      "Upload Photo Result",
                                      MessageBoxButton.OK);
                              }
                          }));
          }
      }

      AmpouleLa version complète du toolkit vous permet de réaliser la même opération en version sécurisée, en ajoutant un service qui fait office de proxy d’authentification. C’est lui qui contiendra les credentials plutôt que votre application WP7 !

      Plus de contrôles Silverlight pour Windows Phone

      Le toolkit le plus utilisé est sans conteste le Silverlight Toolkit pour WP disponible sur codeplex.

      J’ai utilisé :

      • le LongListSelector permettant d’afficher une liste découpée selon un group by
      • une animation sur les listes :le TiltEffect, qui permet d’avoir un retour visuel sur sélection d’un élément
      • le menu contextuel : indispensable pour une bonne expérience utilisateur et éviter d’avoir trop de boutons partout

      J’avais également besoin d’un contrôle Rating et j’ai réutilisé celui-ci (sur codeplex) qui n’est pas prévu pour WP7, mais qui fonctionne bien du moment que l’on désactive le contrôle pendant une gesture.
      Un Rating control est également disponible dans le
      Silverlight Toolkit, mais je ne l’ai pas essayé dans le cadre d’une application WP.

      Le Coding4fun Tools est également très sympa (surtout qu’un de mes propres contrôles y a été intégré Clignement d'œil). Vous y trouverez un menu about, un colorpicker, etc, …

      Faites votre marché Rire

      Les tuiles dynamiques de l’écran d’accueil

      Avec WP7.1, il est ultra simple de rendre vos tuiles dynamiques.

      Un simple appel de méthode permet de mettre à jour une ou les deux faces de la tuile associée à votre application. Pour la Cave à Vins, la 1ère face affiche le nom de l’application, un petit dessin ainsi que le nombre total de bouteilles de votre cave. La 2ème face affiche le nom de la dernière bouteille achetée. Et c’est aussi simple que :

      public void CreateApplicationTile(IEnumerable<MyWine> bottles = null)
      {            
          var appTile = ShellTile.ActiveTiles.First();
      
          if (appTile != null)
          {
              var standardTile = new StandardTileData {
                  Title = "Cave A Vins",
                  BackgroundImage = new Uri("/icons/tileBackground2.png", UriKind.Relative),                    
                  BackTitle = "Dernier achat",
              };
      
              if (bottles != null)
              {
                  standardTile.Count = bottles.Sum(b => b.Count);
                  if (bottles.Any())
                  {
                      standardTile.BackContent = 
                          bottles.OrderByDescending(w => w.AddedDate)
                                  .First()
                                  .WineInfos.Name;
                  }
              }
      
              appTile.Update(standardTile);
          }
      }  

      Côté pile, un MAGNIFIQUE petit dessin ainsi que le nombre total de bouteilles de votre cave :

      image

      Côté face : la dernière bouteille achetée

      image

       

       

      Voilà, tous les points sensibles de l’application sont maintenant exposés et détaillés. Je ne ferai pas de tutoriel pas à pas pour chacun des points, mais si vous rencontrez des difficultés avec l’un d’entre eux, je peux tout à fait développer le sujet dans un autre article.

      Reste à peaufiner l’application, à la localiser et à gérer le tombstoning correctement (notamment en ce qui concerne WCF Data Services).

      Dans le prochain article, nous verrons comment retrouver une bouteille à partir de la photo de son code barre et profiter ainsi des bouteilles déjà saisies par la communauté des utilisateur de l’application CaveAVins.

    • Stéphanie Hertrich

      Développer un projet aujourd’hui : La migration dans Azure

      • 0 Comments

      Update 18/10/2011: l’url du service OData devient http://stephecaveavins.cloudapp.net/CaveAVinsDataService.svc

      Un petit mot pour vous présenter les deux tutoriels qui vous permettront de faire migrer la base de données ainsi que le service de publication dans Azure.

      Résumé des épisodes précédents

      Tutoriels:

      Architecture On Premises :

      image

      Après la mise en pratique des deux tutoriels:

      - CaveAVins Tutoriel 3 : Migration d’une base SQL Server vers SQL Azure
      - CaveAVins Tutoriel 4 : Hébergement du service WCF Data Services dans Azure

      On obtient l’architecture suivante:

      image

      Le service OData publiant les données de la cave à vins est maintenant en ligne : http://stephecaveavins.cloudapp.net/CaveAVinsDataService.svc

      Vous pouvez l’utiliser très simplement dans un projet Visual Studio en faisant un “Add Service Reference”, ou sur d’autres plateformes clientes grâces aux différents helpers : http://www.odata.org/developers/odata-sdk.

      Le prochain article sera consacré à l’application CaveAVins pour Windows Phone 7.1. : avec Mango, c’est encore plus simple d’utiliser un service OData !

    • Stéphanie Hertrich

      Liste des nouvelles API pour WP7.1 Mango

      • 0 Comments

      Petit rappel vite fait pour vous attaquer aux nouveautés de Windows Phone 7.1 (Mango)…

      Et aussi pour les apps existantes :

    • Stéphanie Hertrich

      CaveAVins Tutoriel 4 : Hébergement du service WCF Data Services dans Azure

      • 0 Comments

      Update 18/10/2011: l’url du service OData devient http://stephecaveavins.cloudapp.net/CaveAVinsDataService.svc

      Après la migration des données dans SQL Azure effectuée dans le précédent tutoriel, c’est au tour du service WCF Data Services de publication des données d’être hébergé sur la plateforme Cloud. Il sera ainsi accessible facilement depuis l’extérieur, y compris par un smartphone.

      Les étapes décrites dans ce tutoriel sont valables pour n’importe quelle ASP.Net Web Application.

      Nous irons “de là”

      image

      à “de là”:

      image

      Résumé des épisodes précédents

      Tutoriels:

      - CaveAVins Tutoriel 1 : L’accès aux données avec Sql Server et EF Code First
      - CaveAVins Tutoriel 2 : La publication des données en OData avec WCF Data Services
      - CaveAVins Tutoriel 3 : Migration d’une base SQL Server vers SQL Azure
      - CaveAVins Tutoriel 4 : Hébergement du service WCF Data Services dans Azure (vous êtes ici Star)

      Pré-requis:

      1. Il vous faut un compte Azure que vous obtiendrez gratuitement pour une période de 30 jours: Azure Pass
      2. Vous pouvez utiliser n’importe quel service pour suivre ce tutoriel. Pour l’illustration nous partons de la solution construite depuis les 2 précédents tutoriels, que vous pouvez télécharger ici:

      3. Dans cette solution, il vous faudra modifier le fichier de configuration pour pointer sur votre base de données SQL Azure, comme expliqué dans le précédent tutoriel.
        Cela revient à ajouter la chaine de connexion appropriée dans le web.config du projet web comme par-exemple:

      <connectionStrings>
      <add name="CaveAVinsContext"     connectionString="Server=tcp:oy7122pvo6.database.windows.net,1433;Database=CaveAVins.Db.Azure; User ID=stephe@oy7122pvo6;Password=myPassword;Trusted_Connection=False;Encrypt=True;"
      providerName="System.Data.SqlClient"/>
      </connectionStrings>

      Les étapes

      1. Pour héberger un service sur Azure, il suffit d’ajouter un projet de type Azure dans la solution Visual Studio.
      2. Ce projet Azure permettra d’associer le projet web à un web rôle dans Azure et de le configurer.
      3. Il est ensuite possible d’exécuter le service dans l’émulateur Azure directement sur votre poste de travail, ce qui vous permet de rendre votre solution fonctionnelle et de la débugger sans utiliser de compte Azure.
      4. Quelques modifications de configuration seront nécessaires pour permettre l’hébergement dans Azure.
      5. Vous pourrez ensuite déployer le service dans Azure directement depuis Visual Studio, en pré-production
      6. Et la passer en production pour la rendre disponible publiquement avec une URL du type http://stephecaveavins.cloudapp.net/CaveAVinsDataService.svc

      C’est parti !

      Ouvrez la solution CaveAVins dans Visual Studio.

      Modifiez les propriétés de l’application Web de manière à ce qu’elle se positionne directement sur le service OData au démarrage:

      image

      Ajoutez un nouveau projet de type Windows Azure.

      image

      Appelez-le CaveAVins.Azure et cliquez sur OK.

      image

      Visual Studio nous propose d’ajouter de nouveaux projets pour les associer à des rôles, mais comme c’est un projet Web existant que nous souhaitons héberger dans Azure, nous avons déjà ce qu’il nous faut.

      image

      Le nouveau projet Azure est ajouté à la solution. Remarquez que le répertoire “Roles” est vide. Nous allons donc associer un web role au projet web existant : CaveAVins.WebApplication.

      image

      Faites un clic droit sur “Roles” et ajoutez un nouveau web role pour un projet de la solution

      image

      Notre projet CaveAVins.WebApplication apparait, et nous le sélectionnons.

      image

      Notre web role apparait dans le projet Azure : il sera hébergé sur la plateforme lors du déploiement.

      Exécution dans l’émulateur Azure

      En exécutant votre application (en veillant bien à ce que le projet de démarrage soit CaveAVins.Azure), l’émulateur Azure hébergera votre service. Vous avez accès à l’instance à travers la console de l’émulateur. Vous la trouverez dans les icones en bas à droite de votre écran:

      image

      image

      Le service apparait dans la page lors du lancement :

      image

      Maintenant que le service fonctionne dans l’émulateur, déployons le réellement dans Azure.

      Déploiement du service dans Azure

      Avant de déployer, modifions le nombre d’instances configurés pour le service et passons-le à 2.

      En effet, les caractéristiques de haute disponibilité d’Azure ne peuvent être garanties si vous ne déployez qu’une seule instance, et d’ailleurs la console d’administration vous préviendra de ce risque, comme ci-dessous :

      image

      Ampoule Pour vous familiariser avec le portail Azure, je vous conseille les fiches pratiques : Premiers Pas dans l’administration de Windows Azure.

      Nous modifions également la taille de la VM pour choisir le minimum correspondant à nos besoins et faire ainsi baisser le cout de mon hébergement.

      Puis nous désactivons les options de diagnostique.  Pour les utiliser sur Azure, il faudra modifier la valeur par défaut pour l’associer à votre compte de stockage azure.

      image

      Pour que les librairies spécifiques à notre projet soient incluses dans le package Azure, il faut activer la copie locale, dans les propriétés. Dans notre, cas il faudra l’appliquer à CaveAVins.Db et à EntityFramework.

      image

      Compilez (F6), faites un clic droit sur le projet Azure et choisissez “Publish”

      image

      Vous allez pouvoir travailler directement dans dans Visual Studio pour vos projets Azure, que ce soit pour le développement ou pour le déploiement. Mais pour cela il faut commencer par associer votre projet à votre compte Azure et environnement de travail (stockage, service host).

      Pour un premier déploiement, il vous faut donc accéder au portail Azure pour créer un Storage…

      image

      …ainsi qu’un nouveau Hosted Service.

      image

      Lorsque vous créerez le hosted service qui servira à héberger votre projet, il faudra décocher l’option de publication par défaut, puisque c’est à partir de Visual Studio que nous orchestrerons tout cela:

      image

      Si vous êtes perdus, suivez les étapes qui sont plus détaillées dans la fiche pratique de création d’un nouveau service

      Revenons à présent à notre déploiement depuis Visual Studio et créons les informations d’authentification nécessaires pour que l’utilisation du compte Azure puisse se faire.

      image

      image

      Ensuite suivez les étapes 2 et 3 indiquées sur la page, à savoir copier le chemin vers le certificat qui a été créé localement sur la machine.

      image

      L’ajouter dans le portail Azure

      image

      Copier le “subscription ID” comme demandé à l’étape 3, que vous trouverez ici:

      image

      On y arrive !

      image

      Par défaut, le déploiement est proposé dans l’environnement de staging (pré-production), ce qui nous convient bien pour le moment.

      Le déploiement sur Azure prendra un peu de temps, et vous pouvez évaluer l’avancement dans l’”Azure Activity Log” de Visual Studio.

      image 

      Ou dans l’explorateur de serveur toujours dans Visual Studio.

      image

      Ou encore directement dans le portail Azure.

      image

      Vos efforts sont payants : la prochaine fois que vous modifierez votre service et que vous voudrez le déployer, cela se fera en un clic…(bon disons deux : ”Publish” et “Ok”).

      Ampoule Vous pouvez aussi utiliser Web Deploy pour le déploiement dans Azure, grâce à Windows Azure Accelerator for Web Role.

      Votre service est maintenant prêt à être utilisé à l’adresse de staging : http://db4da25d018242349e92ad4b830b162b.cloudapp.net/CaveAVinsDataService.svc

      Vous pouvez passer votre service en production en publiant sur l’environnement de production dans Visual Studio ou en passant le service de pré-prod en production par le portail Azure :

      image

      Une fois l’opération réalisée, le service est disponible en production, et donc disponible à travers l’URL fixe http://stephecaveavins.cloudapp.net/CaveAVinsDataService.svc

      image 

      Cliquez sur http://stephecaveavins.cloudapp.net/CaveAVinsDataService.svc pour accéder au service.

      image

      Et voilà !

      Notre architecture devient:

      image

      Nous pouvons maintenant accéder aux données à partir d’une application Windows Phone : c’est le sujet de nos prochains tutoriels.

    Page 1 of 3 (75 items) 123