See Sharp
Is it really just a matter of point of view ?

This silver is kinda light on the bindings : you can't do them with style !

February 2, 2010 14:00 by salfab

Depuis peu, voilà qu'il m'est donné la possibilité de jouer quelque peu avec Silverlight :

Beaucoup de choses disponibles en WPF ne le sont pas en Silverlight. Habituellement, des choses facilement contournables, mais la petite aventure du jour mérite certainement d'être consignée : 

Silverlight en tous cas jusqu'à la version 3 ne supporte pas le databinding dans les style setters.

Voyons un cas dans lequel ceci peut être embêtant :

Imaginons une ListBox dont on voudrait remplacer le pannel par défaut par un Canvas, de façon à y positionner les Items. Leur position serait définie par des coordonnées stockées dans les éléments eux-même, et exposées par une DependencyProperty.

Malheureusement, le contrôle ListBox encapsule ses éléments dans un ListBoxItem. Si l'on modifie le ItemTemplate pour ajouter les propriétés attachées Canvas.Top et Canvas.Left à l'élément racine du DataTemplate, l'effet ne sera pas appliqué.

Ceci s'explique par le fait que l'élément "racine" du DataTemplate ne sera pas directement dans le canvas : il sera contenu dans un ListBoxItem. Il faut donc définir les propriétés attachées Canvas.Left et Canvas.Top dans le ListBoxItem.

Ceci est faisable grâce à la propriété ItemContainerStyle qui contient le style du ListBoxItem.

<ListBox.ItemContainerStyle>
   
<Style>
       
<Setter Property="Canvas.Top" Value="{Binding Path=Y}" />
        
<Setter Property="Canvas.Left" Value="{Binding Path=X}" />
   
</Style>
</ListBox.ItemContainerStyle>

En WPF, ceci ne pose pas de problème, on peut définir avec un Style Setter, les propriétés attachées du Canvas, en leur appliquant un Binding.

En Silverlight, malheureusement, s'il est effectivement possible de donner des valeurs fixes pour les propriétés attachées du Canvas, il n'est pas possible de procéder de la sorte pour récupérer par Binding les coordonnées auxquelles placer l'élément : comme le DataBinding n'est pas supporté dans les style setter, il faudra trouver un moyen de créer ce binding dans le codebehind.

Ceci sera faisable en sous-classant le contrôle ListBox et en créant un override de la méthode PrepareContainerForItemOverride(DependencyObject element, object item), dans laquelle il sera possible de définir le Binding par code :    

        FrameworkElement contentitem = element as FrameworkElement; 
       
Binding leftBinding = new Binding("Left"); // "Left" is the property path that you want to bind the value to. 
        contentitem
.SetBinding(Canvas.LeftProperty, leftBinding); 
 
       
base.PrepareContainerForItemOverride(element, item); 

À noter que ItemsControl encapsule également ses éléments dans un ItemContainer, mais ItemContainerStyle n'est pas exposé par ItemsControl en Silverlight...


Playing around with DirectX

September 10, 2009 14:48 by salfab

Une fonctionnalité que je voulais implémenter dans StopMo.Net était la possibilité d’effectuer des expositions multiples sur la même image afin de créer un effet de flou dans le but de rendre les mouvements plus réalistes.

Si en WPF il est très facile d’afficher à l’écran de multiples images superposées à l’aide de multiples éléments Images empilés dans une Grid, il n’est pas aussi trivial d’exporter le résultat obtenu sous forme d’un fichier sur disque.

Une option séduisante et simplissime de résoudre le problème avec WPF uniquement serait d’utiliser une VisualBrush ayant comme source un empilage d’objets Image, pour peindre ensuite une surface et la sauver dans un fichier. malheureusement, l’image ainsi produite serait alors déjà traitée par WPF, altérant l’image, modifiant sa taille et sa résolution, voire même de lisser l’image.

Le but étant ici de conserver une image ayant le moins de distorsion possible, cette option n’est pas envisageable. Deux options plus pures étaient alors possibles.

La première étant d’utiliser GDI+, la seconde d’utiliser Direct2D en conjonction avec l’API WIC de Microsoft, utilisée en arrière plan par WPF pour la gestion de rendu d’images.

Microsoft ayant sorti très récemment un wrapper (Windows7 API Code Pack) pour DirectX 10/11, Direct2D et le Windows 7 SDK. Ce wrapper inclut également (pour l’instant partiellement) WIC. Direct2D est la solution Microsoft pour la gestion d’affichage en deux dimensions visant à remplacer GDI+, et supportant l’accélération matérielle. J’ai donc choisi d’utiliser Direct2D, histoire également de découvrir cette nouvelle technologie.

Ce qu’il est important de savoir est que pour l’instant, le fameux CodePack n’implémente encore pas toutes les fonctionnalités de DirectX.

Direct3D 11.0, Direct3D 10.1/10.0, DXGI 1.0/1.1, Direct2D 1.0, DirectWrite, Windows Imaging Component (WIC) APIs. (DirectWrite and WIC have partial support)

Elles le seront cependant probablement dans le futur. Mes essais préliminaires m’ont donc mené à écrire quelques fonctionnalités manquantes notamment pour la création d’objets WICBitmap.

Un grand merci d’ailleurs à Florian pour son aide et sa patience lors des longues discussions que nous avons eu sur le sujet, et pour son Hack consistant à récupérer par réflexion les objets WIC utilisés en arrière plan par les classes WPF. Ceci afin d’éviter de devoir réécrire de nombreuses fonctionnalités encore manquantes dans le CodePack.

Le résultat est qu’il m’est à présent possible de superposer avec un niveau de transparence arbitraire plusieurs images, créant ainsi une superposition qui visera à donner une impression de mouvement sur une animation Stop Motion.


Nouveau projet - StopMo.Net

September 9, 2009 09:09 by salfab

Plusieurs mois, presqu'une année sans grande activité sur ce blog.

Quelques ébauches de billets tout de même, issus de problèmes techniques en WPF et WCF principalement auxquels j'ai été confronté, à développer pour qu'ils soient publiables...

Un total donc de 11 billets non publiés.

Cette fois-ci, rien de très technique, mais une nouvelle catégorie de post : Stop Motion. En effet, après avoir commencé à mes débuts en WPF à faire un programme d'animation Stop Motion (vous savez, ces films d'animation image par image), j'ai décidé de reprendre le projet après plus d'une année d'inactivité : mon niveau en WPF ayant considérablement changé et fort d'une expérience grandissante en WPF, j'ai décidé de tout jeter et de recommencer From Scratch en utilisant un joli design pattern MVVM, une meilleure découpe, une plus grande modularité, etc. du coup, me revoici sur les rails pour refaire un soft d'animation qui, je l'espère, me permettra rapidement de recommencer à faire des films d'animation (puisqu'à la base c'est quand même ça le but !)

Par la même j'ouvre également une nouvelle catégorie appelée Stop Motion sur ce blog, où je me permettrai de poster des billets en rapport avec mon hobby qu'est l'animation image par image, éventuellement mes progrès tant au niveau du développement du logiciel que des futures animations que je réaliserai.

Le logiciel en question s'appelle StopMo.Net, est écrit en C#/WPF, et utilise les SDK canon pour la communication avec un appareil photo digital : La première étape étant de supporter mon appareil Powershot (CDSDK) , puis dans un second temps, d'investir dans un appareil photo EOS DSLR et d'utiliser un autre SDK prévu à cet effet.

Canon ne supporte officiellement plus les SDK pour canon powershot, donc à terme, le support de choix pour StopMo.Net sera très probablement les appareils Canon EOS, mais il est pensé pour pouvoir ajouter de façon dynamique des providers d'image de différentes sources (Scanners, Webcam, Cameras DV, Appareils numériques d'autres marques, ... )

La date d'anniversaire de coup d'envoi du projet est donc définie : 09.09.09 : Quelle jolie date de naissance n'est-ce pas ?


Joey doesn't share food, WCF doesn't share Types (unless you ask them nicely ?)

April 23, 2009 09:37 by salfab

Introduction 

Au menu de ce jour, un peu de WCF. Celà faisait longtemps que nous n'avions plus joué avec ce joli jouet... Il est l'heure de se remettre à l'ouvrage !

Scénario

Soit le scénario suivant :

  • un service expose un type "CompositeType" et une opération retournant une instance de CompositeType
  • un second service ayant une serviceReference sur le premier pour connaitre "CompositeType" (CompositeType est alors un ProxyObject dans le second service)
    et exposant une méthode acceptant un paramètre de type "CompositeType" (Proxy object)
  • Un client consommant les deux services, utilisant le premier pour récupérer une instance de CompositeType et invoquant une opération du second acceptant unb CompositeType comme paramètre

Pour mettre les idées au clair, voici un petit exemple de notre situation
WCFTypeSharing.zip (108,57 kb)

CompositeType provient dans tous les cas du DataContract défini dans le premier service, mais comme le second service consomme le premier et expose son propre proxy object, les namespaces ne sont pas les mêmes dans les deux cas : même s'ils seront sérialisés de la même manière, la CLR les considèrera comme deux objets différents.

Résoudre le problème

Solution 1: Forcer les namespaces des deux proxy objects à êtr eles mêmes ( éventuellement supprimer les références à double ) 

  • avec ScvUtil : utiliser la commande /namespace sur chacun des services. example : SvcUtil.exe /namespace:*,BusinessServices.Common *.wsdl *.xsd /out:Reference.cs
  • supprimer à la main les types redondants
    • Il semblerait qu'il soit possible d'utiliser SvcUtil pour générer les références de tous les services d'un seul coup sans doublons.
  • modifier les fichiers app.config en conséquences pour prendre en compte les nouveaux namespaces

Solution 2: créer une dll que nous appellerons "MandatoryModuleHandler" qui contiendra les ServiceReferences sur le service exposant le type partagé. Le second service, qui consommera des instances de ce type, reste dans l'assembly principale.

  • Le client doit dans la configuration de ses propres ServicesReferences cocher la case "reuse types in specified referenced assemblies" et s'assurer que la dll MandatoryModuleHandler soit bien cochée dans la liste !
  • Comme le dit le texte, les types réutilisés sont recherchés uniquement dans les assemblies référencées, c'est pourquoi si le type existe dans l'assembly courante (donc pas de référence) le conflit de types persistera.
    • Il semblerait qu'il y'ait un bug dans la résolution des types appartenant à des assemblies référencées si celles-ci sont déjà compilées (références sur des fichiers dll) Pour palier à ce problème, il est nécessaire d'ajouter à la solution le projet servant à compiler la dll, de cette façon, les types qu'elle contient seront correctement résolus et utilisés pour le service qui voudra consommer ces types. Si les types ont déjà été résolus avec une référence de type projet, le remplacement de celle-ci par une référence sur une dll binaire ne reproduira cependant plus directement le bug précité.
  • Mettre à jour le app.config de l'application principale pour y copier toutes les informations sur les endpoints et bindings qui auront été générés dans le fichier app.config de la librairies MandatoryModuleHandler. Le fichier app.config de MandatoryModuleHandler pourra alors être effacé.

Conclusion 

Ces deux solutions ont chacune leur avantage :

  • La première ne crée pas de fichier .dll supplémentaire et/ou ne force pas à l'ajout du projet de cette dll dans la solution : on poura dès lors l'utiliser si l'on ne possède pas les sources d'une dll référençant un service.
  • La seconde permet de tout effectuer depuis Visual Studio sans avoir à utiliser d'outils en ligne de commande.

The Dread Dead Locks a.k.a. You (almost) can't serve and be served

September 14, 2008 10:50 by salfab

Introduction 

La dernière fois que nous avons abordé le theme des services WCF, nous avons vu comment mettre en place une solution Client / Serveur à l'aide de WCF. Pour ce faire, nous avions utilisé trois projets, le premier définissant le contrat du service, le second le serveur hébergeant ce service, et le dernier, un client pour le consommer.

Aujourd'hui, nous allons aborder une autre architecture. Admettons que nous avons un programme que nous aimerions piloter depuis plusieurs "front-ends". Une version web, une version en WPF, et une version en silverlight.

La première idée est bien sur de reprendre notre architecture de la dernière fois, mais ceci implique d'avoir deux exécutables pour un seul programme. Un pour le serveur, et un pour l'interface utilisateur, le client. Il s'agit là de la configuration idéale pour minimiser le footprint de notre solution.

Une autre approche possible est d'avoir une application avec la couche business ainsi que l'interface utilisateur dans le même exécutable, mais de quand même pouvoir piloter la couche business depuis un autre "front-end". Deux possibilités ici :

  • Appel direct aux méthodes de la couche business pour l'UI par défaut.
    • On ne passe alors plus par le service WCF pour attaquer la couche business.
  • Appel uniformisé pour touts les FrontEnds
    • Afin d'uniformiser la façon dont la couche business est attaquée, et de s'assurer qu'il n'existe aucune différence entre les différents front-ends, l'UI se trouvant dans le même process que l'hôte du service créera également un client WCF.

si la première approche ne pose aucun problème majeur, à part le fait qu'il faudra veiller à traiter les appels de la même façon pour les front-ends internes et externes, la seconde approche, elle, pose son lot de soucis.

Avant de commencer, précisons que cette seconde approche n'est pas forcément celle à adopter, mais elle a le mérite de mettre en évidences certains mécanismes de WCF...

Mise en place

Tout d'abord, faisons deux projets, l'un pour le contrat du service, l'autre pour notre application. tout se passe comme lors de notre dernière renncotre avec WCF, à la différence que cette fois-ci, le code instanciant le service et le client se trouvent dans le même projet.

Les sections Service et Client  devront donc tous deux se trouver dans le même app.conf, celui se trouvant dans le projet de l'application.

Si tout est correctement fait, le serveur va être instancié correctement, le client également, mais au premier appel d'une operation de notre service, le programme va se bloquer, et après une minute, une exception de TimeOut sera lancée.

Explications

WCF utilise un contexte de synchronisation (SynchronizationContext) pour simplifier la vie du développeur face aux problèmes de multithreading. Par défaut, l'utilisation du SynchronizationContext est activée, mais peut être désactivée en utilisant l'attribut [ServiceBehavior(UseSynchronizationContext = false)] sur la classe qui implémente le service en question. L'utilisation du contexte de synchronization aura pour effet de mettre tous les appels à ce service dans une file d'attente. Les appels dans cette file d'attente seront exécutés dans le thread dans lequel le canal du service a été créé, et ce, dès que celui-ci sera libéré.

Ceci présente l'avantage, si le canal du service est créé dans le UIThread, de pouvoir acceder aux contrôles graphiques sans avoir à synchroniser nous-même des threads afin d'éviter une exception "Cross-Thread operation not valid" : Tous les appels au service seront appellés dans le bon thread.

Malheureusement, dans notre cas précis, le client et le service son exécutés dans le même thread, ainsi, lorsque le client fait appel à une opération du service, l'appel est bloquant, attendant la réponse du service. Le service, quant-à-lui attend que le thread sur lequel il est mis en attente se libère. Le client attend donc la réponse du service, et le service attend que le client libère le thread sur lequel il doit s'exécuter, d'où interbloquage.

Résolution 

Pour résoudre ce problème, il suffira de désactiver l'utilisation du contexte de synchronisation avec l'attribut précité, mais par la même, nous perdrons l'avantage qu'il confère, ce qui signifie qu'îl ne nous est pas assuré que les opérations du service seront exécutées sur le "bon" thread.

Ce problème pourra toujours être résolu au cas par cas en assurant manuellement la synchronisation des threads grâce à la méthode Invoke. Il sera alors possible de modifier les éléments de l'interface graphique.


Tags: , ,
Categories:
Actions: E-mail | Permalink | Comments (17) | Comment RSSRSS comment feed

it's PART_Ytime, baby !

August 29, 2008 12:23 by salfab

Introduction 

Aujourd'hui, jettons un oeil à ces étranges Named Template Parts que l'on peut parfois voir dans les Templates de certains contrôles, et qui sont facilement reconnaissable au préfixe PART_

Qu'est-ce que ce PART_ ?

De façon à pouvoir rendre un CustomControl hautement Templateable (Anglicisme fait maison qui signifie "un truc qu'on peut lui appliquer un template dessus"), il va nous falloir poser quelques contraintes. En effet : Si l'utilisateur de notre contrôle peut entièrement le re-templater, qui nous assure qu'un certain élément sur lequel nous nous reposons est toujours existant ?

Exemple : SalFab crée une TextBox en 3D. Ce contrôle s'appelle évidemment SalFab3DTextBox, et contient dans son template par défaut, en plus de tout l'attirail nécessaire à la gestion de la 3D, un élément TextBox qui contiendra le texte à entrer - Le nerf de la guerre. Comme SalFab désire ardemment que tout le monde utilise son contrôle, il décide de permettre au monde entier de changer le template de son contrôle. Comment être sûr que l'utilisateur utilise bel et bien un élément TextBox dans son template ? Rappellons que toute la logique du contrôle se repose sur le postulat qu'il existe un tel élément dans son template !

L'idée est de donner un nom prédéfini à cet élément ( grâce à la propriété x:Name du TextBox ), par exemple, "PART_UnderLyingTextBox". Afin de pouvoir travailler avec dans le codebehind, nous aimerions en récupérer une référence. Ceci peut se faire dans la méthode OnApplyTemplate de notre contrôle, grâce à un appel de la méthode GetTemplateChild("PART_UnderLyingTextBox"). Le paramètre de la méthode est bien entendu le nom, arbitraire, de notre élément. Il nous sera alors possible de lui appliquer la logique applicative désirée. Par exemple, enregistrer son évènement TextChanged. On pourra également tester si le retour de GetTemplateChild est null, et adapter le comportement du contrôle en conséquence.

Si le nom à donner à x:Name est bien arbitraire, l'usage veut que pour ce genre d'utilisation, l'on utilise le préfixe PART_.

Notons qu'il ne s'agit pas là d'un nom réservé, mais d'une convention de codage. Par conséquent, il sera judicieux de documenter explicitement les Parts utilisées par notre contrôle, chose qui peut être faite à l'aide de l'attribut TemplatePartAttribute

Par exemple, dans notre cas :

[TemplatePart(Name = "PART_UnderLyingTextBox", Type = typeof(TextBox))]

Notons finalement que l'attribut TemplatePartAttribute n'applique aucun comportement, mais est uniquement présent pour décrire les parts utilisées. Cet attribut ser utilisé par des outils de design pour mettre en évidence leur existence.

 Happy Templating !


Get a grip on the DataGrid - Snoop your way out of your troubles

August 22, 2008 12:15 by salfab

mi-Août 2008, un nouveau contrôle a été mis à disposition sur CodePlex en même temps que le SP1 pour la .Net Framework 3.5 et Visual Studio 2008.

Pour le moment, il s'agit d'un CTP qui est téléchargeable séparément sur CodePlex, mais sera vraissemblablement inclus dans les prochaines versions de la framework. Les sources sont disponibles, ainsi qu'une librairie binaire. .Net 3.5 SP1 est obligatoire pour utiliser ce contrôle.

De nombreux articles proposent des tutoriels pour se familiariser avec ce contrôle, cet article n'a donc pas cette vocation.

Cependant, nous allons voir comment résoudre un problème précis, et en profiter pour découvrir un nouvel outil : Snoop.

DataGrid permet une chose fort sympathique : le style alternatif, histoire de changer une ligne sur deux le style d'une ligne (DataGridRow).

Manque de bol : On s'apperçoit, lorsqu'on définit un Background ou un AlternativeBackground, que les lignes sont colorées de bout en bout d'un contrôle, alors que la surbrillance (Highlighting) pour mettre en évidence les éléments sélectionnés s'arrêtent au niveau de la dernière colonne, provocant ainsi une inconsistence : une ligne avec une couleur de fond qui va jusqu'à la fin de la "colonne de padding" (La dernière colonne vide sans en-tête ni contenu) alors que la sélection s'arrête à la fin de la dernière colonne valide.

Notre but ici est de mettre en surbrillance toute la ligne.

Première tentative :

Ajouter la définition de propriété suivante dans le tag de DataGrid :
SelectionUnit="FullRow"
Malheureusement, ça n'a pas l'effet escompté : On sélectionne effectivement toute la ligne, au lieu d'une seule cellule, mais la sélection ne se propage pas jusque sur la colonne de padding.

Seconde tentative :

Le reflexe suivant que toute personne sensée aurait serait d'utiliser un Trigger pour changer le Background de la DataGridRow lorsqu'elle est sélectionnée. Là encore : chou blanc : en analysant le code de DataGrid on s'apperçoit qu'il y a un CoerceValue sur la couleur de Background appliquée sur notre DataGridRow : il faut donc trouver un auter endroit où changer cette couleur.

C'est ici que nous nous trouvons à cours de "réflexes". Il est l'heure de sortir la grosse artillerie : Snoop. ( ou Mole, pour ceux qui préfèrent. Personnellement, ma préférence va pour Snoop.

Troisième tentative

Snoop nous permet de voir l'arbre visuel (VisualTree) de notre application, et par conséquent, de noter contrôle DataGrid. En se balladant dans l'arborescence on peut voir où se trouve les éléments graphiques qui représentent notre sélection.

Il y a probablement une multitude de façons différentes de règler ce problème. Heureusement, nous n'en avons besoin que d'une seule. L'approche que j'ai choisie ici est de modifier l'élément qui est chargé de présenter les cellules pour une ligne donnée : DataGridCellsPresenter. Comme nous sommes très chanceux, et que le code de notre contrôle DataGrid est disponible sur CodePlex, nous pouvons sans autre visualiser le template défini pour ce type dans le fichier generic.xaml (ou le .xaml relatif au thème sur lequel on veut se baser) du projet de DataGrid. Comme nous sommes vraiment très chanceux : tout se trouve dans generic.xaml : Les templates sont les mêmes pour tous les thèmes, ce qui nous évitera de devoir surdéfinir plusieurs templates pour couvrir tous les thèmes.

Petite parenthèse : Si nous avions voulu faire ce travail sur un contrôle de la framework .Net il est possible de retrouver sur la MSDN ou, plus simple, dans le programme XamlHack les templates utilisés par les widgets créés par Microsoft. Si le contrôle est un contrôle tiers sans les sources : Il sera peut être possible de s'amuser avec Reflector, et de reconstruire le xaml à partir du baml se trouvant dans les resources de l'assembly du contrôle, mais ceci dépasse le cadre de cet article.

Résolution du problème

Maintenant que nous avons identifié le type dont nous voulons changer le Template, il nous suffit de copier coller la définition existante : Elle se trouve sous forme de ressources dans un ResourceDictionary, que nous pouvons incorporer dans notre Windows.Resources ou tout autre dictionnaire de ressources accessible par notre DataGrid... et de la modifier selon nos désirs, évidemment.

Après quelques essais à le tripoter, le template est passé de :

<Style x:Key="{x:Type dg:DataGridCellsPresenter}" TargetType="{x:Type dg:DataGridCellsPresenter}">
  <Setter Property="Template">
    <Setter.Value>
      <ControlTemplate TargetType="{x:Type dg:DataGridCellsPresenter}">
        <ItemsPresenter />
      </ControlTemplate>
    </Setter.Value>
  </Setter>
</Style>

à

<Style x:Key="{x:Type toolkit:DataGridCellsPresenter}" TargetType="{x:Type toolkit:DataGridCellsPresenter}">
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="{x:Type toolkit:DataGridCellsPresenter}">
                <Grid>
                    <Grid.ColumnDefinitions>
                        <ColumnDefinition Width="Auto" />
                        <ColumnDefinition Width="*" />
                    </Grid.ColumnDefinitions>
                <ItemsPresenter />
                <Border Grid.Column="1">
                    <Border.Style>
                        <Style TargetType="{x:Type Border}">
                            <Style.Triggers>
                                <DataTrigger Binding="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType=toolkit:DataGridRow}, Path=IsSelected}" Value="True">
                                    <DataTrigger.Setters>
                                        <Setter Property="Background" Value="{DynamicResource {x:Static SystemColors.HighlightBrushKey}}" />
                                    </DataTrigger.Setters>
                                </DataTrigger>
                            </Style.Triggers>
                        </Style>
                    </Border.Style>
                </Border>
            </Grid>
        </ControlTemplate>
    </Setter.Value>
</Setter>

L'idée est d'avoir un élément qui prendra toute la place disponnible (Grid) , d'y placer tout à gauche ( Première colonne ) L'élément dont la taille est faite pour correspondre aux headers des colonnes (Ce qui nous permet d'avoir une largeur de colonne de Auto pour la première) et de placer dans la seconde colonne, un élément qui coloriera le "reste" de l'espace, ici un Border.

Pour le changement de couleurs, on utilise la même approche que lors de la seconde tentative, mais puisque notre Border se trouve au dessus des DataGridRow, et que son Background n'est pas forcé à l'aide d'un CoercevalueCallback, la ligne apparaîtra comme en surbrillance sur toute la largeur du contrôle.

Mission accomplie, et ce, sans créer un nouveau contrôle ! Merci WPF, Merci les Templates !


Erster Monat im Leipzig

August 11, 2008 17:46 by salfab

Un mois et demi déjà.

Et oui. Heureusement, entrecoupés par un petit voyage à l'improviste du côté suisse, (ainsi que le  NIFFF au tout début de mon aventure) histoire de recharger ses batteries, se rappeller à quoi ressemble la langue française, ce genre de choses.

Bilan des courses ? :

Expérience Professionnelle : J'ai enfin la possibilité d'utiliser la technologie WPF  dans le monde de l'industrie, ce qui est une grande chose pour moi, ceux qui ont pu travailler avec moi par le passé savent à quel point cette technologie m'est chère. ( et à quel point quand j'ai une idée en tête je ne l'ai pas ailleurs - Sorry about this, guys !). Le travail est intéressant, mon collègue principal est fort sympathique, bonne ambiance dans le bureau, donc !

Langue Allemande : de gros gros progrès : Mon quotidien est Teuton, je pense Allemand, je parle Allemand, je mange Allemand, et encore bien des choses. A vrai dire, ce point-ci est ma grande fierté personnelle : Jamais je n'aurais pensé pouvoir parler toute une journée en allemand, de choses techniques, qui plus est, ( et ceux qui me connaissent savent que lorsque je parle, je parle beaucoup, et avec verve, panache et fougue sans pareil ) et de me sentir relativement à l'aise avec ça. Un grand merci quand même à mon patient collègue qui prend le temps de trouver des synonymes en allemand, voire une traduction en anglais quand rien ne va plus ;)

Cercle social : le point le plus décevant pour le moment : deux rencontres dans un bar à jeux ( non, ce n'était pas des filles que j'ai lourdement accosté, mais juste un groupe de gens en manque de joueurs pour une partie de Carcassonne ) échanges de contacts, un rendez-vous auquel je ne pouvais me rendre, plus de nouvelles. Dommage.

Contacts avec la Suisse : trois premières semaines sans retour, puis une semaine et demi à présent ( j'en suis au milieu ) avant mon prochain retour en suisse. Je pense que je reviendrai plus souvent que prévu au pays, ne serait-ce que le temps d'un Week-End. Et oui, c'est ça d'avoir du monde à voir en suisse... Un retour par mois n'est franchement pas envisageable... à voir encore comment les choses se décantent.

Voili voilà. je sais, je ne mets pas vraiment beaucoup de nouvelles à ce sujet sur ce blog, mais sachez, jeunes gens inquiets, que tout se passe bien ici, je réussis à me nourrir correctement, je ne vis pas encore dans un amoncellement de détritus, et je me tiens propre ...

 A bientôt pour de nouvelles aventures.


Tags:
Categories: Die Deutsche Reise
Actions: E-mail | Permalink | Comments (46) | Comment RSSRSS comment feed

Tips for Binding On ToolTips

July 24, 2008 14:41 by salfab

Introduction 

Mon quotidien Allemand est principalement axé sur la technologie WPF, ces temps.

Au programme d'aujourd'hui : Un outil permettant la localisation de ressources :

Charger un fichier XAML contenant un ResourceDictionnary, en extraire toutes les ressources de type sys:String, et les afficher à l'écran. L'idée est de pouvoir éditer lesdites chaines, nous allons donc les charger dans une TextBox.

Et puisqu'il est intéressant de pouvoir se rappeller quelle était la valeur par défaut : Nous voulons afficher dans le ToolTip, la valeur originale.

Implémentation 

En admettant que nous ayons un XmlDataSource déclaré dans le XAML ( ne pas oublier de déclarer aussi les différents namespaces XML !! ) nommé xmlTranslationProvider.

Pour utiliser un contrôle visuel léger, utilisons ItemsControl : Puisque nous n'utilisons pas les mécanismes de sélection d'éléments, nous pouvons nous passer de l'overhead d'une ListBox.

Nous devrons ensuite lier l'ItemSource de ItemsControl à notre XmlDataProvider, afin de définir quels seront les Items à afficher : Les éléments de type sys:String.

<ItemsControl ItemsSource="{Binding Source={StaticResource xmlTranslationProvider}, XPath=sys:String }" >

Pour définir le rendu de chacun de nos Items : Définissons le template de ItemsControl.ItemTemplate, où nous insèrerons la TextBox qui servira à la traduction.

Intuitivement, les Bindings appliqués devraient ressembler à celà :

<TextBox Name="tbTranslationField" Text="{Binding XPath=.}" Grid.Column="0" ToolTip="{Binding XPath=.,Mode=OneTime}" />

Le binding de ItemsControl.ItemsSource définissant quels seront les items, nous définissons ici comment les représenter : La propriété Text affichera la valeur du noeud, de même pour le ToolTip, à l'exception près que nous voulons que le ToolTip garde sa valeur d'origine, d'où le Mode OneTime. OneTime permet de ne recharger les données que lorsque l'application démarre, où que le DataContext est changé.

Les ennuis commencent

Malheureusement, dans les faits, à chaque fois que le ToolTip est affiché, il contient les dernières modifications apportées à la chaîne à traduire.

Pour quelle raison ?

Analysons la démarche :

Créons un nouveau TextBox dont la propriété Text possèdera un Binding identique au ToolTip ci-dessus : Le comportement est celui que nous attendions : son contenu est mis à jour une seule fois, et n'est jamais changé, en dépit du fait que la donnée liée, elle, change : Le mode OneTime fonctionne : Que se passe-t-il ?

Le TextBox est généré au lancement de l'application. il va chercher les données auxquelles il est lié, l'affiche à l'écran, et ne les touche plus.

Explications

Qu'y a-t-il de différent avec un ToolTip ?

Le ToolTip attend que le pointeur de la souris passe au dessus de la zone appropriée, puis à ce moment là, et uniquement à ce moment là, il est construit : Il va donc chercher les données auxquelles il est lié, et ne reçoit plus jamais de notification en cas de modification des données auxquelles il est lié.

Si l'utilisateur déplace la souris, le ToolTip est détruit.

Si l'utilisateur déplace la souris à nouveau au dessus de la zone sensible, le ToolTip est à nouveau reconstruit de A à Z. Il va donc rechercher les informations auxquelles il est lié, les affiche à l'écran, et ne reçoit plus jamais de notification en cas de modification des données liées.

Que se passe-t-il si l'utilisateur, entre la destruction du ToolTip et sa nouvelle création, modifie les données auxquelles le ToolTip est lié ?

Le ToolTip est créé, puis il va rechercher les données auxquelles il est lié. Mais attention, nous avons spécifié le mode OneTime, WPF possède donc un mécanisme pour s'assurer que les données liées ne doivent être chargées qu'au démarrage de l'application ou lors d'un chargement de DataContext....

Mais alors pourquoi diable, mon ToolTip change-t-il tout de même de valeur ??

La réponse est simple : Les données liées n'ont pas changé !!!

Et pourtant le ToolTip, lui, ne possède pas le même contenu ! Pourquoi ?

Tout ceci est dû au fait que nous avons fait une erreur de sémantique dans notre Binding : Nous avons en effet lié un Noeud XML à notre TextBox et à notre ToolTip, et non pas une chaîne de caractère. L'objet XmlNode en question, qui est lié à nos propriétés ToolTip et Text, n'a absolument pas été changé. Il a seulement été altéré !

Les ValueConverters très puissants de WPF ont simplement converti un noeud XML en représentation sous forme de string à notre place.

Ainsi, lorsque le ToolTip est créé après que le Noeud XML auquel il est lié ait été altéré, le mécanisme de WPF s'assure qu'il n'ait pas été changé, et le ToolTip peut sans autre lire le texte se trouvant dans le XmlNode, cependant, celui-ci a été changé.

C'est de par la nature même du ToolTip que l'on peut observer ces différences de comportement entre un TextBox qui est rendu à l'écran une fois pour toutes, et un ToolTip qui est recréé à partir de zéro plusieurs fois au cours du cycle de vie de l'application.

Résoudre le problème :

L'idée est donc d'indiquer au mécanisme du mode OneTime de s'assurer non-pas que l'objet XmlNode reste inchangé, mais bien que le texte qu'il contient reste le même ! Le texte en question est représenté par la propriété InnerText de l'objet XmlNode.

Par conséquent, nous allons modifier les Bindings de notre TextBox comme suit :

<TextBox Name="tbTranslationField" Text="{Binding XPath=., Path=InnerText}" Grid.Column="0" ToolTip="{Binding XPath=.,Mode=OneTime, Path=InnerText}" />

A noter que le même genre de subtilités survient lorsqu'une DependencyProperty de type List<T> (n'implémentant pas INotifyCollectionChanged) est altérée : Si l'on ajoute un élément à une List<T>, la propriété référencant cette liste n'est pas modifiée, car l'adresse mémoire de l'objet pointé par la propriété est resté le même, en dépit du fait que l'objet lui même ait été altéré.

En eséprant que cet article puisse aider quelques personnes à mieux comprendre les subtilités du binding.

Die Abreise

June 25, 2008 20:57 by salfab

Nous sommes un jeudi, une opportunité s'offre à moi : aller travailler à l'étranger : En Allemagne, dans la ville Ex-Est-Allemande qu'est Leipzig.

Et ceci pour aller y travailler avec la technologie WPF (!!)

Deadline pour donner ma réponse ?

Vendredi : Les choses sont un peu soudaines mais c'est bien le genre d'opportunités qui  se présentent rarement.

Départ, donc.

Le temps d'obtenir le feu vert de la direction, de fair eun premier voyage sur place pour une rencontre-meeting avec mes futurs nouveaux collègues, et me voilà parti pour l'Est.

Une semaine dans un hôtel avant de revenir en suisse pour ce festival, Dear and Near to my heart qu'est le NIFFF, puis re-départ en allemagne pour embrasser mon avenir professionnel et social.

Je relaterai d'ailleurs ici dans cete catégorie : Die Deutsche Reise ( à ne pas abréger D.D.R. ni pour les intimes, ni pour personne.) les quelques éventuelles aventures qui parsèmeront ma vie sociale au pays de la Curry Wurst.