ItemTemplate Windows 8

by Nicolas Calvi 29. avril 2013 16:00

Un petit post pour vous partager un petit soucis que j'ai rencontrer. Je me suis créer des "ProjectTemplates" et "ItemTemplates" pour mes projets MVVM sous Windows 8. Là où la création du "ProjectTemplate" se passe sans soucis, je me suis aperçu que mes "ItemTemplates" ne s'affichais pas quand je faisais "Nouvel élément".

En fait, Visual Studio 2012 exporte ceci dans son fichier "MyTemplate.vstemplate" :

<VSTemplate Version="3.0.0" xmlns="http://schemas.microsoft.com/developer/vstemplate/2005" Type="Item">
  <TemplateData>
    <DefaultName>ItemView.xaml</DefaultName>
    <Name>Black Windows 8 Empty View</Name>
    <Description>Vue pour être utilisée dans une pattern MVVM Black Blog</Description>
    <ProjectType>CSharp</ProjectType>
    <SortOrder>10</SortOrder>
    <Icon>__TemplateIcon.jpg</Icon>
    <PreviewImage>__PreviewImage.jpg</PreviewImage>
  </TemplateData>
  <TemplateContent>
    <References />
    <ProjectItem SubType="Designer" TargetFileName="$fileinputname$.xaml" ReplaceParameters="true">HomeView.xaml</ProjectItem>
    <ProjectItem SubType="Code" TargetFileName="$fileinputname$.xaml.cs" ReplaceParameters="true">HomeView.xaml.cs</ProjectItem>
  </TemplateContent>
</VSTemplate>

Or avec cette définition, l'ItemTemplate ne s'affiche pas dans ma liste. En cherchant un peu je me suis rendy compte qu'il manquait en fait un élément de définition dans le fichier. Si j'ajoute cette ligne dans le fichier de définition :

<TemplateGroupID>WinRT-Managed</TemplateGroupID>

Mon "ItemTemplate" s'affiche bien, il faut juste le savoir.

Localisation Dynamique

by Nicolas Calvi 17. avril 2013 16:32

Une de mes problématiques en ce moment est liée à la localisation de mes applications. En effet, sous Windows Store App, si l'on veut switcher d'une langue à l'autre, il faut soit changer la langue de son OS et relancer l'application (si celle-ci utilise la localisation à partir des ressources) ou alors créer son système custom et tout ça doit être bien intégré dans votre pattern MVVM, or moi je voudrais faire ça à la volé. Bref un vrai casse-tête.

Personnellement j'ai planché un peu sur le problème et j'ai donc trouvé une première solution, certes ce n'est pas spécialement la meilleur ou la plus performante, mais elle offre je pense un bon compromis entre Resources / Binding / MVVM. Voilà en substance ma solution personnelle :

Le principe est de conserver le système de ressource (ici en rouge avec les fichiers "Resources.resw" et "Resources.lang-fr-FR.resw"), de créer une classe de gestion des ressources (ici en rouge avec la classe "ResourcesSwitcher") et pour faire le lien entre les deux par du Binding, un Converter (ici en rouge avec la classe "LanguageConverter"). Je vous laisse regarder en détail le code source (lien du projet en fin d'article), je vais juste faire un focus sur les éléments importants.

Tout d'abord il faut récupérer le contenu des ressources via la classe du Framework "ResourceManager", cela permet d'avoir l'arbre des différents dictionnaires dans les différentes langues :

// Récupération de la map
ResourceMap map = ResourceManager.Current.MainResourceMap.GetSubtree("Resources");

Ensuite, il faut créer les contextes, cela représente au final les langues que l'ont veut traiter, cela sera utile plus tard quand on voudra récupérer des informations dans la "map" des ressources, en spécifiant la langue le Framework pourra allez chercher la bonne valeur dans la bonne langue.

ResourceContext english = new ResourceContext() { Languages = new List<string>() { "en-Us" } };
ResourceContext french = new ResourceContext() { Languages = new List<string>() { "fr-FR" } };

Pour finir, pour récupérer une valeur dans la "map" des ressources il suffit de faire ceci :

map.GetValue("CleDictionnaire", french).ValueAsString

Ça c'est pour la récupération d'un élément localisé dans les ressources par le code. Vous comprenez donc que pour passer d'une langue à l'autre il suffit de changer le deuxième paramètre de "GetValue()", par exemple on peut remplacer le "ResouceContext" "french" par "english".

Ensuite il faut faire le lien entre le XAML et ce mécanisme. Moi j'ai choisi de créer une classe ("ResourcesSwitcher") qui sera une classe avec propriété notifiée qui exposera la "map". Pour créer un singleton de cette classe, je la déclare dans mon "App.xaml" :

<Application x:Class="BlackBlog.DynamicLocalization.App"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:conv="using:BlackBlog.DynamicLocalization.Converters"
             xmlns:vcore="using:BlackBlog.DynamicLocalization.Views.Core">

    <Application.Resources>
        <ResourceDictionary>

            <!--  Converteur pour récupérer les conversions  -->

            <conv:LanguageConverter x:Key="LanguageConverter" />

            <!--  Singleton pour le moteur de conversion  -->

            <vcore:ResourcesSwitcher x:Key="ResourcesSwitcher" />

        </ResourceDictionary>
    </Application.Resources>
</Application>

Au passage vous remarquerez que j'ai déclaré aussi un Converter. Celui-ci va servir a extraire une valeur (avec le "GetValue()" de la "map") depuis le XAML en utilisant le "ConverterParameter". En gros ça donne ca si on essaye de binder une ressource sur une propriété "Text" d'un "TextBlock" :

<TextBlock FontSize="16"
                Foreground="White"
                Text="{Binding Source={StaticResource ResourcesSwitcher},
                                      Path=Map,
                                      Converter={StaticResource LanguageConverter},
                                      ConverterParameter='Sentence'}" />

Avec ce système on peut donc binder nos ressources sur nos contrôles. Mais à quoi ça sert tout ça me direz-vous ! Et bien justement, si vous y regardez de plus prês, vous remarquez que nous sommes en binding, qui dit binding dit notification. Comme je l'ai souligné plus haut, dans ma classe "ResourcesSwitcher" j'ai exposé la "Map" via une propriété notifiée, donc il suffit de gérer qu'elle langue est sélectionnée actuellement (pour ma part j'ai mis une propriété dans mon "ResourcesSwitcher"), et au moment du changement de langue il suffit de notifier cette propriété, et là le moteur de binding va réévaluer toutes vos binding et repasser par le converter :

public object Convert(object value, Type targetType, object parameter, string language)
{
    // Récupération de la Map de ressource
    ResourceMap map = value as ResourceMap;

    if (map != null && !string.IsNullOrWhiteSpace((string)parameter))
        return (map.GetValue((string)parameter, ResourcesSwitcher.Instance.CurrentLanguage).ValueAsString);

    // Pas de convertion

    return (value);
}

Et dans le converter, je vais chercher la langue en sélection pour l'évaluation de la valeur. Grâce à ça vous avez un système de localisation dynamique.

Ceci est bien sûr une solution parmi tant d'autre, qui a ses avantages et ses défaut, libre à vous l'améliorer. Vous pouvez télécharger le code par ce lien :

BlackBlog.DynamicLocalization.rar (130,97 kb)

Kinect SDK : Découverte

by Nicolas Calvi 3. avril 2012 14:21

Enfin ! C’est le 1 février dernier que Microsoft met à disposition du public la première version RTW du Kinect SDK for Windows. Au menu, optimisation, documentation et refonte des APIs. Cet article fait une comparaison avec la version BETA du SDK Kinect for Windows mais revient sur les principales fonctionnalités. Cet article fait suite à mon article paru dans le Programmez! 148 de Janvier 2012. Il est question de se familiariser avec le SDK.

1. Améliorations de l’architecture Kinect

Kinect est un véritable petit bijou de technologie. Outre les fonctionnalités disponibles sur Kinect pour XBOX 360, Kinect for Windows dispose de plusieurs améliorations :

- Existence d’un mode « near » qui permet de détecter des objets à partir de 40 cm de l’appareil.
- Possibilité de brancher jusqu’à 4 Kinect for Windows sur un même PC.
- Amélioration de la détection des squelettes.
- Intégration dans le SDK de la dernière version de Microsoft Speech Platform afin d’utiliser la reconnaissance vocale.

Pour pouvoir profiter des dernières améliorations, il va falloir acquérir un nouveau matériel estampiller « Kinect for Windows ». Ce nouveau Kinect, qui peut être acheté sur le Microsoft Store au prix de 249 euros, est une version amélioré de la « Kinect for Xbox » et permet l’utilisation du fameux mode « near », chose impossible avec une « Kinect for Xbox ».

2. Mode de licence

La plus grande interrogation à ce jour, est le mode de licence de Kinect. Jusqu’à maintenant, toute application créer avec le SDK Beta de Kinect n’était pas destinée à être vendu ou utilisé en entreprise, seul les étudiants, chercheurs et quidam du développement pouvaient s’amuser avec à des buts de prospection d’usage et aussi pour le fun.

Maintenant, il est possible de développer des applications à usage commerciale, mais il y a des conditions à respecter. Ce qu’il faut déjà comprendre, c’est que Microsoft se rémunère sur la vente de « Kinect for Windows », il faudra donc, pour déployer une application en entreprise, posséder un « Kinect for Windows » et non un « Kinect for Xbox ».

Dans les fait, le Kinect SDK permet de développer avec les deux matériels, mais une fois l’application exécutée sur la version « runtime » de Kinect, celui-ci va vérifier si le Kinect connecté est bien un « Kinect for Windows », si ce n’est pas le cas il va refuser de le prendre en charge.

Pour résumer, en développement (par le SDK) toutes les versions de Kinect sont acceptées, en mode runtime seuls les « Kinect for Windows » sont acceptés. D’ailleurs, on peut interpréter cela en se disant que l’utilisation d’un SDK non Microsoft pour utiliser Kinect est autorisé sous réserve que ce soit un matériel « Kinect for Windows » qui soit connecté. Bien sûr, prenez soin de vérifier cela auprès de Microsoft.

3. Utilisation du SDK

Au niveau du SDK, pas de changements de fond dans l’environnement de développement (Windows 7), l’outil de développement (Visual Studio 2010 toute version) et les langages de programmation disponibles (C# et C++) en 32 et 64 bits. C’est plutôt dans la forme que cela change.

Récupérer les Kinect connectés :

Dorénavant l’assembly utilisée pour développer sur Kinect est « Microsoft.Kinect ».

Afin de récupérer les informations relatives aux instances des Kinect connectés à la machine, on utilise maintenant une classe statique nommée simplement « KinectSensor ». Cette classe possède une propriété statique nommée « KinectSensors » permettant de récupérer la collection des Kinect connectés. Il suffit ensuite d’extraire de cette collection le ou les Kinect qui nous intéresse.

using Microsoft.Kinect;

private KinectSensor _kinectSensor;

public MaFenetre()
{
    // Si on trouve un kinect on l'initialise
    if (KinectSensor.KinectSensors.Count > 0)
    {
        this._kinectSensor = KinectSensor.KinectSensors[0];  
        this.InitializeKinect();
    }
}

Afin d’initialiser un Kinect il faut passer par plusieurs étapes. La première est de lancer la méthode Start de l’instance Kinect. On passe ensuite des paramètres pour éliminer le bruit (seulement nécessaire si on fait de la reconnaissance de squelette). On ouvre ensuite les flux qui nous intéressent (à savoir l’image VGA, l’image de profondeur et les squelettes) et on s’abonne à chaque événement de flux (ColorFrameReady, DepthFrameReady et SkeletonFrameReady) pour récupérer l’information.

Une autre alternative est de récupérer les informations de chaque événement en une fois avec l’événement « AllFramesReady » (figure 2). Ce dernier est une nouveauté par rapport aux anciennes versions du SDK qui nous obligeaient à nous abonner à chaque évènement séparément (ce qui est toujours possible en fonction des besoins). Mais parfois on a besoin d’avoir en même temps l’image vidéo et de profondeur, donc au lieu de la stocker pour l’utiliser plus tard, le runtime Kinect nous synchronise tous les éléments et nous les envois ensemble. Outre le fait que cela soit plus pratique, cela permet aussi un gain de traitement.

Il faut juste savoir que l’appel à la fonction « Enable() » est toujours nécessaire pour que les informations soient renseignées dans le « AllFramesReady », cette méthode démarre la capture du flux concerné.

private void InitializeKinect()
{
    // On démarre la scrutation
    this._kinectSensor.Start();

    // SmoothParamater pour éliminer le bruit
    TransformSmoothParameters parameters = new TransformSmoothParameters
    {
        Correction = 1.0f,
        JitterRadius = 0.01f,
        MaxDeviationRadius = 0.01f,
        Prediction = 1.0f,
        Smoothing = 0.9f
    };

    // Ouverture des flux
 this._kinectSensor.ColorStream.Enable(ColorImageFormat.RgbResolution640x480Fps30);
 this._kinectSensor.DepthStream.Enable(DepthImageFormat.Resolution320x240Fps30);
    this._kinectSensor.SkeletonStream.Enable(parameters);

    // On se branche sur tous les callbacks (ne pas se brancher sur les autres 
    // si on se branche sur celui-là
    this._kinectSensor.AllFramesReady += new EventHandler<AllFramesReadyEventArgs>(this.AllFramesReady);
}

Récupérer les données vidéo et les squelettes :

Afin de récupérer les données vidéo (image VGA et image de profondeur) on utilise l’argument de l’évènement levé quand toutes les frames (VGA, profondeur et squelette) sont disponibles. Les informations contenues dans l’argument vont nous être utiles pour construire notre image de rendu.

Pour obtenir les données de l’image VGA on utilise la méthode « OpenColorImageFrame », cette méthode va rendre disponible les informations coté managé et qui contient (quand l’image n’est pas nulle) un tableau de byte. Il suffit ensuite de copier ce flux dans notre application par la méthode « CopyPixelDataTo », dans notre exemple on copie les informations dans une variable que l’on nomme ici « pixelData ».

On utilise ensuite certaines propriétés de l’image (à savoir sa hauteur, sa longueur et le fameux « pixelData ») pour créer l’image VGA dans un type connu (WriteableBitmap dans notre exemple) et utilisable comme bon nous semble.

// Sert pour mettre à jour l’image
private readonly int _Bgr32BytesPerPixel = (PixelFormats.Bgr32.BitsPerPixel + 7) / 8;

private void AllFramesReady(object sender, AllFramesReadyEventArgs e)
{
    // Gestion de l'image
    using (ColorImageFrame image = e.OpenColorImageFrame())
    {
        if (image != null)
        {
            byte[] pixelData = new byte[image.PixelDataLength];
            image.CopyPixelDataTo(pixelData);
            WriteableBitmap colorFrame = new WriteableBitmap(this._kinectSensor.ColorStream.FrameWidth, this._kinectSensor.ColorStream.FrameHeight, 96, 96, PixelFormats.Bgr32, null);
            colorFrame.WritePixels(new Int32Rect(0, 0, image.Width, image.Height), pixelData, image.Width * this._Bgr32BytesPerPixel, 0);
        }
    }
    // …
}

L’accès aux informations de l’image de profondeur de Kinect est relativement similaire à celui de l’image VGA. En effet, on utilise la méthode « OpenDepthImageFrame » de l’argument de l’évènement, on crée un tableau de type « short » correspondant au « pixelData » de l’image et on construit l’image de profondeur dans un WriteableBitmap.

Il y a néanmoins une petite subtilité qui est qu’il faut transformer un peu l’image pour l’exploiter. La conversion est expliquée dans le précédent article sur Kinect, les seuls changements sont l’apparition de masques pour faciliter le traitement. 

using (DepthImageFrame image = e.OpenDepthImageFrame())
{    
    if (image != null)
    {
        short[] depthImage = new short[image.PixelDataLength];
        image.CopyPixelDataTo(depthImage);

        // Récupération des informations de profondeur
        byte[] computeImage = this.ConvertDepthFrame(depthImage, image.Width, image.Height);

        // Conversion en image exploitable
        this.DepthFrame.WritePixels(new Int32Rect(0, 0, image.Width, image.Height), computeImage, image.Width *  _Bgr32BytesPerPixel, 0);
        // …
    }
}

private byte[] ConvertDepthFrame(short[] depthFrame, int imageWidth, int imageHeight)
{
    byte[] depthFrame32 = new byte[imageWidth * imageHeight * MainViewModel.Bgr32BytesPerPixel];

    for (int i16 = 0, i32 = 0; i16 < depthFrame.Length && i32 < depthFrame32.Length; i16++, i32 += 4)
    {
        // On récupère l'index joueur et la distance
        int player = depthFrame[i16] & DepthImageFrame.PlayerIndexBitmask;
        float realDepth = Convert.ToSingle(depthFrame[i16] >> DepthImageFrame.PlayerIndexBitmaskWidth);

        // Coder ici la convention de l’image …
    }

    // On retourne la frame
    return (depthFrame32);
}

Le processus pour récupérer les squelettes est similaire dans son principe. On utilise cette fois-ci la méthode  « OpenSkeletonFrame » de l’argument qui nous permet d’en extraire les informations des différents squelettes via la méthode « CopySkeletonDataTo ».

Une fois les squelettes récupérés il suffit de les trier pour obtenir ceux qui sont dans l’état « Tracked » c’est-à-dire quand Kinect arrive à définir le squelette et de faire des opérations sur ces derniers (comme récupérer les différents points du squelette et regarder leurs coordonnées).

// Recherche des squelettes
using (SkeletonFrame frame = e.OpenSkeletonFrame())
{
    if (frame != null && frame.SkeletonArrayLength > 0)
    {
        Skeleton[] skeletons = new Skeleton[frame.SkeletonArrayLength];
        frame.CopySkeletonDataTo(skeletons);

        var result = from p in skeletons
                         where p.TrackingState == SkeletonTrackingState.Tracked
                         select p;

        // Affichage des squelettes
        foreach (Skeleton item in result)
        {
            // Faire les opérations sur les différents squelettes
            // par exemple collecter tous les « Joints » et les afficher
        }
    }
}

En ce qui concerne les changements mineurs, on notera principalement la disparition d’indice de qualité W des SkeletonPoint.

Ainsi, nous avons pu voir les différents changements dus à la sortie officielle du SDK Kinect par rapport aux SDK antérieurs. Une multitude de possibilités s’offre au développeur une fois que les informations captées par Kinect sont transmises à la machine. Laissez parler votre imagination, une nouvelle ère est née !

Je vous laisse aussi télécharger la démo de cet article : KinectDemo.rar (147,64 kb) 

Merci à Patrick-André Marendat pour m'avoir aidé à rédiger cet article :)

SqlDependency, vue rapide.

by Nicolas Calvi 14. octobre 2011 11:54

Il m’a été amené récemment à utiliser les SqlDependency dans le cadre d’un de mes projets. Je vous fais donc partager aujourd’hui mon retour sur cette fonctionnalité SQL Server / .Net et en bonus je vous donne un code pour les gérer plus facilement.

Qu’est que SqlDependecy

Avant toute chose, revenons sur cette fonctionnalité. Depuis SQL Server 2005, il est possible de créer une dépendance entre du code .Net (via les APIs ADO.Net) et un jeu de résultat SQL. Pour faire simple, SQL Server va pourvoir nous notifier quand le résultat d’une requête change. Pour cela on lui fournit une requête SELECT simple qu’il va exécuter, le résultat de cette requête servira de référence pour la notification. Ensuite, si ce même SELECT donne un résultat différent plus tard, SQL Server nous rappelle pour nous notifier que le premier résultat obtenu est différent du dernier résultat qu’il a évalué. Très pratique en web pour faire de l’invalidation de cache.

Requête et base de données

Il y a cependant des choses à connaître quand on utilise la SqlDependecy. Par exemple pour la requête qui est donnée pour référence :

  • La requête qui est donnée pour référence ne doit pas être avec un SELECT *, il lui faut des noms explicites de colonne.
  • La requête qui est donnée pour référence doit indiquer l’utilisateur qui possède la table accédée, par exemple [dbo] pour le cas le plus courant. 

On doit donc avoir des requêtes qui ressemblent à ça : 

SELECT [col1], [col2] FROM [dbo].[MaTable]

Ensuite au niveau de l’instance de la base de données, il faut activer le service BROKER sur la base, si ce n’est pas fait cette commande permettra de l’activer :

ALTER DATABASE [Nom Base] SET ENABLE_BROKER WITH ROLLBACK IMMEDIATE

Le service BROKER va permettre justement de gérer cette communication entre notre assembly et SQL Server. Une fois cette fonctionnalité activée, passons au code coté .Net.

Implémentation de la SqlDependency

Ci-dessous le code pour implémenter simplement une SqlDependency, je vais l’expliquer par la suite.

private void CreateDependency()
{
  string connectionString = "Mettre ici la chaîne de connexion";

  // Démarrage du broker sur la base
  SqlDependency.Start(connectionString);

  // Création de la connexion
  using (SqlConnection connexion = new SqlConnection(connectionString))
  {
    // Création de la commande pour la requêt de référence
    using (SqlCommand command = new SqlCommand("SELECT col1 FROM [dbo].[MaTable]", connexion))
    {
      command.Notification = null;

      // Création de la dépendance
      SqlDependency dependency = new SqlDependency(command);
      dependency.OnChange += new OnChangeEventHandler(DependencyChange);

      // On démarre la dépendance
      command.ExecuteReader();
    }

    // Fermeture de la connexion
    connexion.Close();
  }

  // Arrêt du broker sur la base
  SqlDependency.Stop(connectionString);
}

private void DependencyChange(object sender, SqlNotificationEventArgs e)
{
  // On se désabonne
  (sender as SqlDependency).OnChange -= new OnChangeEventHandler(DependencyChange);

  // Code de traitement ici
}

Ici on peut voir comment se structure la création de la SqlDependency. On doit d’abord démarrer le BROKER de la base, afin qu’il écoute les dépendances, ensuite on exécute une requête ADO.Net classique, à savoir l'instanciation d’une connexion, la création d’une commande et l'exécution de celle-ci.

La création de la dépendance se situe entre la création de la commande et son exécution. On y crée alors la dépendance sur la commande et on s’abonne à l’événement OnChange pour récupérer la notification de changement.

La dépendance n’est créée qu’au moment où la commande est exécutée, le résultat qu’elle produit est la référence SQL Server pour savoir si un changement a été fait.

Ensuite c’est très simple, dès que le résultat de la requête de dépendance change, la fonction de retour (ici DependencyChange) est appelé, il n’y a plus qu’à faire les traitements qui nous intéresses. 

Par contre, il faut bien comprendre une chose, une SqlDependency ne fonctionne qu’une seule fois ! Une fois qu’elle est traitée (en gros que la fonction de retour est invoqué) elle disparait du serveur SQL Server et ne fonctionnera plus. En gros, c’est à usage unique.

Il est donc impossible de créer une SqlDependency qui va invoquer la fonction de retour autant de fois que le résultat de la requête de dépendance est modifié. Pour pouvoir boucler sur les modifications, il faut recréer une autre dépendance.

Dans cette optique, il n’est donc pas nécessaire de conserver les instances des commandes, connexions et autre objets utilisés pour la création de la dépendance. C’est pour cela que dans le code ci-dessus, tout est libéré par des 'using'.

Manager de dépendance

Vous pouvez télécharger plus bas, deux classes qui permettent de gérer des SqlDependency de façon plus automatique et de boucler sur les notifications. J’ai créé ces classes dans le but de factoriser la gestion des SqlDependency par chaîne de connexion. Pour les utiliser, il faut ajouter l’assembly 'System.Configuration', voici le détail des deux classes :

DependencyManagerItem.cs (8,22 kb)

Cette classe gère une dépendance simple, de façon atomique. On peut l’utiliser seule, mais elle n’active pas le BROKER coté base de données. Le constructeur est classique, on y renseigne la chaine de connexion, une fonction de retour et si on active le mode continue (qui recrée la SqlDependency automatiquement).

Cette classe, possède ensuite une propriété 'Datas' qui renvoi l’état du résultat de la requête de référence, ce qui permet de l’avoir sans avoir à le requêter à nouveau. Ensuite, pour démarrer la dépendance il suffit d’invoquer la méthode 'Start()', si vous êtes dans un mode boucle, la méthode 'Stop()' permet d’arrêter la dépendance.

DependencyManager.cs (5,58 kb)

Le principe de cette classe est de gérer de façon plus globale les dépendances. On l’instancie avec une chaine de connexion et elle va démarrer le BROKER de la base, mais aussi l’arrêter quand elle sera recyclée.

Elle possède des propriétés qui lui permettent de démarrer des dépendances avec des valeurs par défaut (il est toujours possible de démarrer une dépendance sans ses valeurs par défaut).

Pour démarrer une dépendance, il faut invoquer la méthode 'Create()' qui prend en paramètre un nom, ce nom servira à identifier la dépendance pendant tout son cycle de vie dans le Manager. Ensuite il y deux versions de 'Create()', une qui crée la dépendance avec les paramètres par défaut, et l’autre pour les outrepasser. Pour finir, une méthode 'Remove()' mettra fin à une dépendance nommée.

Ces classes sont faciles d’utilisation et vous avez le code, donc libre à vous de les étudier et/ou de les adapter à vos besoins.

Dependency Property : Trucs et astuces

by Nicolas Calvi 19. octobre 2010 12:06
Lors des développements en WPF on a souvent besoin d'écrire des propriétés de dépendances pour nos objets. En dehors de la déclaration simple de notre propriété, il y a quelques éléments qui méritent d'être connue. Dans l'exemple qui va suivre, je vais écrire une propriété de dépendance appelée "MaProp", qui sera un Int32 et qui ne peut prendre des valeurs que de 1 à 100.
  
1 - Déclaration de la propriété
 
Il faut bien évidement déclarer sa propriété, dans Visual Studio il y un a snipet pour ça : propdp.
 
public static readonly DependencyProperty MaPropProperty = DependencyProperty.Register("MaProp", typeof(int), typeof(MaClasse), new UIPropertyMetadata(1));
 
Dans les conventions de nommage, on ajoute toujours "Property" sur l'a déclaration statique, ensuite pour initialiser celle-ci, on peut donner une valeur par défaut dans le dernier paramètre, ici 1.
 
2 - Déclaration de l'accesseur
 
Il faut maintenant créer l'accesseur pour cette propriété, ici rien de bien difficile, on doit simplement utiliser le GetValue(DependencyProperty) et le SetValue(DependencyProperty, object). Ces deux fonctions ne font que récupérer ou inscrire des informations dans le dictionnaire de ressource de l'objet, c'est dans celui-ci que les valeurs des propriétés de dépendance sont stockées.
public int MaProp
{
  get
  {
    return ((int)this.GetValue(MaClasse.MaProp));
  }
  set
  {
    this.SetValue(MaProp.MaProp, value);
  }
}
Il est très fortement recommander de ne mettre aucun autre code que celui-ci sur sa propriété. Pour exécuter du code à son changement ou contrôler la valeur, ce sont les deux points suivants.
  
3 - Faire un traitement après modification
 
Pour exécuter du code après un changement de valeur, il est possible de rajouter un Callback dans le constructeur du paramètre UIPropertyMetadata() dans la déclaration statique de la propriété. Ce Callback est de type PropertyChangedCallback. Voici un exemple de déclaration pour cette méthode.
public class MaClasse 
{   
  // Déclaration de la propriété   
  public static readonly DependencyProperty MaPropProperty = DependencyProperty.Register("MaProp", typeof(int), typeof(MaClasse), new UIPropertyMetadata(1, new PropertyChangedCallback(MaClasse.OnMaPropChanged)));    

  // Déclaration de la fonction statique de Callback   
  private static void OnMaPropChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)   
  {    
    // Appel a la fonction interne de modification     
    ((MaClasse)sender).OnMaPropChanged(e.OldValue, e.NewValue);  
  }    
  
  // Déclaration de la fonction interne de modification  
  protected virtual void OnMaPropChanged(int old, int new)   
  {    
     // Votre code ici   
  } 
}
Il est recommandé de déclarer la fonction statique de Callback en privée et la fonction de modification interne en protégée virtuel, ceci permet si on hérite de votre classe, de pouvoir intercepter le changement de valeur de la propriété.
 
4 - Tester la valeur avant modification
Dans notre exemple, on veut pouvoir borner la valeur entre 1 et 100, le premier mécanisme est déjà de l'initialiser dans cet interval, mais il est possible aussi avant l'affectation, de tester la valeur, ceci se passe comme pour le point 3, une fonction de Callback nous permet de la tester, elle s'appelle CoerceValueCallback(). Elle fonctionne comme le PropertyChangedCallback, voici un exemple pour cette méthode
 
public class MaClasse 
{       
  // Déclaration de la propriété       
  public static readonly DependencyProperty MaPropProperty = DependencyProperty.Register("MaProp", typeof(int), typeof(MaClasse), new UIPropertyMetadata(1, new PropertyChangedCallback(MaClasse.OnMaPropChanged), new CoerceValueCallback(MaClasse.CoerceMaProp)));      

  // Déclaration de la fonction statique de Callback  
  private static void OnMaPropChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)  
  {    
    // Appel a la fonction interne de modification    
    ((MaClasse)sender).OnMaPropChanged(e.OldValue, e.NewValue);  
  }    

  // Déclaration de la fonction statique de coerce  
  private static object CoerceMaProp(DependencyObject d, object baseValue)  
  {    
    return(((MaClasse)sender).CoerceMaProp((int)baseValue));  
  }    

  // Déclaration de la fonction interne de modification  
  protected virtual void OnMaPropChanged(int old, int new)  
  {    
    // Votre code ici  
  }    

  // Déclaration de la fonction interne de coerce  
  protected virtual object CoerceMaProp(int value)  
  {    
    // On teste la valeur    
    if (value < 1)      
      return(1);    
    else if (value > 100)      
      return(100);          

    return(value);  
  } 
}
On peut ainsi tester la valeur dans le coerce et la remettre dans un interval souhaité avant son affectation, ce qui déclenchera ensuite le PropertyChangedCallback avec la bonne valeur.

Autres

MVP

Hardware Interaction Design & Developpement

Posts récents