Browsed by
Month: March 2013

Rendre vos pages dynamiques en injectant du XAML !

Rendre vos pages dynamiques en injectant du XAML !

Si vous utilisez mon application Wikipedia, vous avez surement remarqué que la partie supérieure de la page d’accueil varie avec le temps. Quelques exemples :

 

Invitation à lancer un webbrowser pour participer à un concours
wp_ss_20130330_0001

Ouvre le Windows Phone Store pour télécharger une application
wp_ss_20130330_0002

Pour cela la technique est assez simple, à chaque démarrage, l’application fait une requête sur mon serveur en passant sa culture (anglais, français, etc…), et récupère des variables : image url, texte descriptif et le lien.

Selon le type du lien, je lance alors : une page interne de l’application (pour faire découvrir un article bien précis, pour faire découvrir les settings, etc…), un lien internet via un WebBrowserTask ou une page du Windows Phone Store via un MarketplaceDetailTask.

Toutefois, la mise en forme reste assez statique et pour la prochaine version je veux aller en plus en injectant directement du XAML dans la page ! On pourra alors imaginer avoir des animations spécifiques, des sliders, etc…

Comment le faire ?

C’est très très simple, il suffit en fait d’utiliser un XamlReader, et notamment  sa méthode Load qui va permettre de lire du code xaml depuis une string et de générer les différents objets XAML.

Dans l’exemple suivant, nous allons appeler une page php qui va nous fournir du code xaml selon notre culture. Nous donnerons le résultat de cette requête au XamlReader et nous afficherons le résultat dans un panel XAML nommé CommentPanel.

WebClient web = new WebClient();
web.DownloadStringCompleted += (sender, e) =>{
try
{
var xaml = XamlReader.Load(e.Result);
CommentPanel.Content = xaml;
}
catch
{
}

};
web.DownloadStringAsync(new Uri(uri+"?culture="+CultureInfo.CurrentUICulture.TwoLetterISOLanguageName,UriKind.Absolute));

et du côté PHP :


<?php
$culture=$_GET['culture'];
if($culture=='fr'){
?>

<StackPanel xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<Button Content="Click Me" />
<Slider Minimum="0" Maximum="100" />
</StackPanel>
<?php }else
{
?>
<Grid xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" Background="Red" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<TextBox />
</Grid>
<?php } ?>

Et ça fonctionne !!

wp_ss_20130330_0003

A noter : pensez bien à définir les namespaces sans oublier le namespace par défaut, sinon vous recevrez une exception pendant le chargement du XAML.

 

Comment rendre ceci interactif ?

Il n’est pas possible en effet d’inclure du code-behind dans cette injection afin d’interagir avec les nouveaux éléments XAML (par exemple afficher une page web quand on clique sur un bouton ou lancer une animation), il y a plusieurs solutions.

Pour afficher une page web ou une page interne de votre application, vous pouvez vous reposer sur les spécificités du contrôle HyperlinkButton par exemple :


<HyperlinkButton NavigateUri="http://www.bing.com" />

Une autre solution est de binding vos contrôles à des commandes, en effet, il n’est pas possible de se lier à des event handlers directement comme :

<button Click="ClickAction" />

Binding ?

Les bindings fonctionnent évidemment ! Vous pouvez écrire sans soucis :

<button Content="{Binding MyTitle}" />

Toutefois, vous ne pourrez faire référence à des éléments de votre page dans vos bindings (mais vous pouvez référer des éléments de votre XAML dynamique.

Note

Evidemment, vous ne pouvez pas injecté tout et n’importe quoi, il va falloir faire attention à quelles assemblies vous faites appellent et les capabilities de vos applications. Par exemple, pour inclure dynamiquement une pub PubCenter dans votre app, il va falloir que celle-ci contienne déjà une référence à la librairie PubCenter et que vous disposez de toutes les capabilities nécessaire à l’exécution de ce contrôle.

[WP8] Binder une collection sur une Map ! 2 le retour

[WP8] Binder une collection sur une Map ! 2 le retour

Cet article fait suite non pas à un de mes articles mais à un article d’un copain Julien Lo Presti :  MSP, expert Windows Phone et breton, trois bons points ! (et accessoirement un bon copain et partenaire de hackathon). Il a notamment développé l’application Stinson Book

Sur son blog Julien propose une solution à un problème assez gênant : l’impossibilité de binder avec un MapItemsControl du toolkit. Pour parer à cela, Julien proposer une solution astucieuse avec un Behavior : classe !

Lire son article ici :

http://jlopresti.fr/post/2013/03/19/WP8-Binder-une-collection-sur-une-Map-!

Untitled-1

 

Entre bretons, on aime se chamailler, on considère que cela nous fait progresser et il se trouve que pour Wikipedia WP8 (qui va bientôt arriver dès que je trouverais quelques heures) j’avais rencontré la même problématique et j’avais contourné cette problématique non pas un behavior mais une propriété d’extensions.

Ma solution

    public class MapTools : DependencyObject
    {
        [SuppressMessage("Microsoft.Design", "CA1062:Validate arguments of public methods", MessageId = "0", Justification = "Standard pattern.")]
        public static bool GetMapItemsSource(DependencyObject source)
        {
            return (bool)source.GetValue(MapItemsSourceProperty);
        }


        [SuppressMessage("Microsoft.Design", "CA1062:Validate arguments of public methods", MessageId = "0", Justification = "Standard pattern.")]
        public static void SetMapItemsSource(DependencyObject source, IEnumerable value)
        {
            source.SetValue(MapItemsSourceProperty, value);
        }

        // Using a DependencyProperty as the backing store for MapItemsSource.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty MapItemsSourceProperty =
            DependencyProperty.Register("MapItemsSource", typeof(IEnumerable), typeof(Map), new PropertyMetadata(null, MapItemsSourceChanged));

        private static void MapItemsSourceChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
        {
            var map = ((Map)sender);
            var itemsControl = MapExtensions.GetChildren(map).OfType<mapItemsControl>().FirstOrDefault();
            if (itemsControl != null)
            {
                itemsControl.ItemsSource = (IEnumerable)e.NewValue;
            }
        }
    }

et pour l’utiliser :


<maps:Map x:Name="Map" huynmap:MapTools.MapItemsSource="{Binding MyPlaces}" HorizontalAlignment="Stretch" ZoomLevel="15">
<toolkit:MapExtensions.Children>
<toolkit:MapItemsControl >
<toolkit:MapItemsControl.ItemTemplate>
<DataTemplate>
<toolkit:Pushpin .../>
</DataTemplate>
</toolkit:MapItemsControl.ItemTemplate>
</toolkit:MapItemsControl>
</toolkit:MapExtensions.Children>
</maps:Map>

Conclusion

Les deux solutions sont au final équivalente et utilisent les mêmes principes : se brancher au FrameworkElement Map, qui est le dernier élément à avoir accès au DataContext et gérer le MapItemsControl depuis ce point.

La différence est surtout que le Behavior est plus facilement exploitable sous Blend en WYSIWYG alors que la propriété d’extension elle à l’avantage de ne pas tirer des dépendances de librairies liées à Blend. On pourra toutefois espérer avec de meilleur performance au chargement pour la propriété d’extension (pas de dll à charger, moins complexe).

 

Merci Likeman

 

Attention aux singletons statiques

Attention aux singletons statiques

Pour créer une classe singleton, il existe plusieurs méthodes, la plus simple étant d’avoir une propriété statique couplée à un constructeur statique dans la classe comme ceci :

public class Datas
{

static Datas()
{
Instance = new Datas();
}
public static Datas Instance { get; set; }
}

Simple et efficace ! Enfin presque, car couplé à l’IsolatedStorageSettings cela peut mener à une catastrophe.

Voici un exemple de code :

public class Datas
{
static Datas()
{
if (IsolatedStorageSettings.ApplicationSettings.Contains("Data"))
{
Instance = IsolatedStorageSettings.ApplicationSettings["Data"] as Datas;
if (Instance != null)
return;
}
Instance = new Datas();

}

public void Save()
{
IsolatedStorageSettings.ApplicationSettings["Data"] = this;
IsolatedStorageSettings.ApplicationSettings.Save();
}

public String Login { get; set; }
public DateTime LastUpdate { get; set; }

[XmlIgnore]
public static Datas Instance { get; set; }
}

 

De toute évidence ce code fonctionne et est fiable, vous le trouverez assez souvent dans des tutoriaux par exemple.

Or ceci n’est qu’un apparence, prenez 2 minutes et essayez de trouver ce qu’il ne va pas dedans….

Un indice chez vous : Data.Instance peut retourner null dans certains cas.

Allez voilà ce qu’il va se passer !

va-passer-L-mtt7lk

 

Imaginons qu’avant d’utiliser la classe Datas, j’ai quelque part dans monde code :

IsolatedStorageSettings.ApplicationSettings.Contains(“UneAutreDonnee”) par exemple

L’isolatedStorageSettings va commencer par désérialiser l’ensemble de ses données, incluant ma donnée [“Data”]. Comme on le sait, le constructeur statique de Datas va être appelé à la première utilisation, donc dans notre cas si cela n’a pas été le cas avant : maintenant.

Or, le constructeur statique accède aussi à l’isolatedStorageSettings, qui est toujours en cours de désérialisation et là ça fait :

divide3

Pourquoi ?


Car comme c’est l’initialisateur de IsolatedSettings qui a appelé le constructeur statique, il a “hérité” des droits sur le lock de l’IsolatedSettings et donc a ainsi les droits une donnée qui est en train d’être créée !

“Operation not permitted on IsolatedStorageFileStream.”

Encore plus vicieux, l’initialisateur de l’IsolatedSettings a son propre try catch, donc aucune exception, mais qd vous allez faire  Data.Instance, celui ci vaudra NULL malgré toutes les sécurités que vous avez misent ! Or comme vous êtiez sûr que Data.Instance ne pouvait jamais valoir NULL, vous allez appeler par exemple : Data.Instance.Login et créer une belle exception qui fera planter votre application.

Pourquoi ca n’arrive pas quand on n’a jamais appelé IsolatedStorageSettings auparavant ?

Car comme on l’a dit, un constructeur statique n’est appelé qu’une seule fois, donc comme on vient de l’appeler via Data.Instance, l’IsolatedStorageSettings ne va pas le rappeler de nouveau, pas de problème de lock, pas d’exception.

Pourtant je suis sûr de ne pas appeler IsolatedStorageSettings avant ?

Moi aussi c’était ce que je pensais… mais en réalité il ne faut pas se contenter d’analyser que son propre code, il faut regarder ce que fait les librairies tierces par exemple. Or, Flurry par exemple stocke ses données dans l’isolatedStorageSettings aussi et comme il faut initialiser Flurry dès le chargement de l’application, il y a de grande chance qu’elle appelle l’IsolatedStorageSettings avant vous. Soyez donc toujours prévoyant !

La solution ?

Tout simplement externaliser la gestion du singleton en utilisant une seconde class que l’on va appeler “Provider”, on sépare ainsi la gestion du singleton et la désérialisation.


public class DatasProvider
{
static DatasProvider()
{
if (IsolatedStorageSettings.ApplicationSettings.Contains("Data"))
{
Instance = IsolatedStorageSettings.ApplicationSettings["Data"] as Datas;
if (Instance != null)
return;
}
Instance = new Datas() { HasBeenCreated = true };

}

[XmlIgnore]
public static Datas Instance { get; set; }

}

public class Datas
{

public void Save()
{
IsolatedStorageSettings.ApplicationSettings["Data"] = this;
IsolatedStorageSettings.ApplicationSettings.Save();
}

public String Login { get; set; }

[XmlIgnore]
public bool HasBeenCreated { get; set; }
}