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...


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 ?


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 !


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.

Your code is running even though the program is stopped (And the truth is out there)

February 14, 2008 09:15 by salfab

Récemment, je me suis trouvé face au cas fort étrange d'une application WPF donc l'interprétation du XAML par le designer générait une erreur alors que l'exécution du programme compilé se déroulait sans encombre. Phénomène normal ou paranormal ? La vérité est ici !

Tout d'abord, situons un peu le cadre du problème. Afin d'avoir un exemple simple pour illustrer le problème, j'ai créé un exemple de toute pièce.

Il s'agit d'une application WPF, et comme toute bonne application WPF, elle contient des Binding : Une TextBox qui est liée à une DependencyProperty (DP) de type int. La valeur de cet int sera située entre 1 et 7, et indiquera le nom du jour à afficher dans la textbox. malheureusement, comme nous sommes liés à un entier, WPF n'a aucun moyen de savoir que nous voulons afficher un nom de jour de la semaine.

Afin de directement couper cours aux NitPickers, je tiens à préciser qu'il s'agit ici d'un exemple un peu tiré par les cheveux mais qui a le mérite d'illustrer des problèmes du même acabit qui pourraient être rencontrés dans des contextes bien plus complexes. Dans un cas d'application réel, nous aurions utilisé l'enum DayOfWeek. Maintenant que ceci est clarifié, revenons-en au coeur du problème.

Si l'on se contente de faire un Binding entre une textbox et un entier, la représentation donnée par la méthode ToString apparaitra dans la textbox. Pour nous permettre de convertir notre entier en nom de jour de la semaine, nous allons utiliser un Value Converter.

les value converters permettent d'ajouter la tuyauterie manquante pour adapter la valeur de la source à celle que la cible attend.

Si par malheur, dans notre ValueConverter, nous utilisons un objet qui n'a pas de valeur par défaut, ou n'a pas été instancié par le constructeur d'une ressource déclarée dans le XAML - Les ressources déclarées dans le XAML sont également instanciées, et ce, même en mode design - la compilation du projet en cours se passera sans encombres, l'exécution également, mais le designer ne pourra pas afficher la fenêtre du programme à l'écran et ajoutera une erreur dans la fenêtre ErrorList. L'erreur qui sera mentionnée peut prendre plusieurs formes. Il s'agit généralement d'une erreur de référence nulle sur un objet, mais il arrive parfois d'avoir quelques surprises. Nous verrons en détails pourquoi. Si nous naviguons jusqu'à a portion de code concernée, nous verrons qu'elle se trouvera dans le XAML, sur l'élément auquel nous avons appliqué un Binding.

Pourquoi donc notre code fonctionne-t-il au runtime, et pas en mode design ? 

Dans le projet d'exemple attaché, nous avons initialisé un tableau de chaînes de caractères dans le constructeur de App. Ce constructeur, n'a aucune raison d'être exécuté par le designer puisqu'aucune application n'a été instanciée, jusqu'ici, rien de surprenant. Un comportement que les développeurs de contrôles utilisateurs sous winforms auront certainement déjà rencontré est le fait que lors du design d'une application utilisant un contrôle utilisateur, le contrôle vit. Il possède son propre comportement en mode design, et d'une certaine façon, il est, lui, déjà en cours d'exécution.

En ce qui concerne le Binding en WPF, il s'agit du même phénomène : Le fait de permettre l'affichage à l'écran en live des modifications de notre interface graphique implique qu'une certaine logique applicative décrive la façon dont ces modifications seront reflètées. Dans la plupart des cas, cette logique applicative est incluse dans la framework .Net et est tout à fait transparente pour le développeur, cependant, dans noter cas, nous utilisons une fonctionnalité avancée pour nous permettre de crocher notre propre logique applicative dans le processus de rendu.

Afin de pouvoir continuer à reflèter les changements lors du design de façon cohérente, le designer va prendre en compte notre ValueConverter, et l'exécuter. Si celui-ci contient un bug, une exception sera générée. Généralement, il s'agit d'une erreur de type Object reference not set to an instance of an object, mais dans certains cas, l'erreur mentionnée peut être d'un autre type, par exemple, une erreur de casting. Ceci s'explique par le fait qu'au lieu d'être levée et affichée dans une fenêtre d'exception comme au runtime, elle sera affichée dans la fenêtre ErrorList. Celle-ci ne permettant pas de naviguer dans les InnerExceptions, elle affiche uniquement l'exception de niveau le plus élevé. Le choix de représenter ces erreurs dans la fenêtre ErrorList n'est pas anodin. En utilisant cette représentation, il est possible de signaler plusieurs erreurs simultanément, chose qui ne serait pas possible avec l'affichage séquentiel de plusieurs fenêtres d'exceptions, et qui pourrait être réellement handicapant dans le cas où un nombre important d'erreurs serait commis.

Il existe diverses manières de résoudre ce problème :

  • La première, qu'il est recommandé d'adopter, est de revoir le design de l'application : Les cibles de Binding sont généralement des DependencyProperties. Il est également possible d'utiliser des collections ou des propriétés qui implémentent INotifyPropertyChanged ou INotifyCollectionChanged. Dans ce cas, il est recommandé d'opter plutôt pour une DependencyProperty, qui permettent, elles, de définir une valeur par défaut. Il conviendra de ne pas choisir la valeur null comme valeur par défaut, bien évidemment.
  • Il n'est malheureusement pas toujours possible de changer le design de son application sans remettre en cause une grande partie du code de l'application. Il existe donc une autre façon de procéder, moins élégante, mais qui peut parfois sauver des vies : De la même façon qu'en WinForms il existe la propriété DesignerMode, WPF tient à jour une DependencyProperty IsInDesignMode, accessible par la méthode GetIsInDesignMode. Cette méthode fait partie de la classe DesignerProperties.

En attachement, un petit projet Visual Studio 2008 dont certaines portions de code sont en commentaires, N'hésitez pas à vous amuser à commenter / décommenter les différentes portions du code pour mettre en action les différents comportements.

XFiles.zip (12,58 kb)


WPF 3D - With your feet in the air, your head on the ground

February 12, 2008 13:20 by salfab

Avec WPF, il n'a jamais été aussi facile d'incorporer de la 3D dans une interface utilisateur. Il s'agit là d'un réel rendu 3D géré en DirectX, et très bien intégré aux composants d'interface utilisateur. Il est par exemple possible de rendre l'aspect d'un bouton en tant que cube 3D, le tout accéléré matériellement. L'intégration est même tellement simple en comparaison d'une utilisation traditionnelle en DirectX, que les novices seront tentés de se plonger rapidement dans la création de scènes 3D sans s'attarder sur les bases du rendu 3D. Pourtant, certains concepts ne devraient pas passer à la trappe. c'est notamment le cas des rotations de caméras.

En jouant avec la propriété LookDirection d'une caméra, il peut arriver que la scène disparaisse brusquement de l'écran. Généralement, ce phénomène se produit lorsque la LookDirection pointe sur un vecteur 100% vertical (0,1,0) ou (0,-1,0). Si l'on poursuit la rotation, la scène réapparaitra, mais inversée, comme au travers d'un miroir.

 À première vue, ceci a tout l'air d'un bug.

Premier réflexe : Il y'a un bug dans WPF
Second Réflexe : Il y'a quelque chose dans WPF que je n'utilise pas correctement.

Il existe une seconde propriété qu'il est nécessaire de gérer lors d'une rotation d'une caméra.

LookDirection ne permet que d'indiquer quel est la direction dans laquelle la caméra pointe, mais rien n'indique où se trouve le haut et où se trouve le bas.

UpDirection permet de spécifier un vecteur qui indiquera le haut.

Explications :

Par défaut, le haut se trouve être un vecteur vertical. Pour comprendre ce compoertement, il va nous falloir faire appel à nos vagues souvenirs de mathématiques.

UpDirection et LookDirection n'ont pas nécessairement besoin d'être perpendiculaires. WPF va automatiquement calculer le produit cross entre le vecteur LookDirection et le vecteur UpDirection.

Si, par un jeu de rotations, le vecteur de la direction à regarder et le vecteur indiquant le haut sont confondus, le produit Cross sera nul et on ne pourra déterminer aucune composante perpendiculaire au vecteur LookDirection. Pour cette raison, le résultat du rendu est indéfini, et la scène n'est pas affichable.  LookDirection passera de l'autre côté du vecteur, et le

En poursuivant la rotation, si le vecteur UpDirection n'est pas mis à jour, il aura pour effet d'indiquer la direction précédemment considérée comme le bas en tant que nouvelle direction du haut.

Il n'y a donc pas de bug à ce niveau là dans WPF, juste un manque de connaissance en ce qui concerne le positionnement des caméras.

 Source : WPF Unleashed,Nathan Adler, p.427


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

DependencyProperty setters & Binding : You can't always get what you want...

February 6, 2008 14:16 by salfab

... but if you try sometimes, you might find you get what you need !

En effet, le philosophe Jagger avait absolument raison.

 Une particularité intéressante de C# sur nombre de langages concurrents est certainement la notion de propriétés :

Alors qu'en Java, il est nécessaire de créer des méthodes setters & getters pour ne pas exposer les membres privés d'une classe, le langage C# permet d'exposer une sorte de variable publique qui permettra de retourner ou d'assigner une valeur à un membre privé, mais surtout, d'ajouter une logique applicative lors de la manipulation de ce membre. Un exemple typique serait d'incrémenter un compteur qui définirait le nombre de modifications effectuées sur un certain membre privé.

À noter que Python a introduit le concept de propriétés dès sa version 2.2 à condition d'utiliser les new style classes.

Dès .Net 3.0, une nouvelle sorte de propriété à fait son apparition, les DependencyProperties. Elles sont utilisées notamment pour permettre le DataBinding entre des éléments graphiques de l'interface utilisateur décrite en XAML et d'autres objets étant par exemple décrits dans le codebehind. Il est alors très simple grâce à un binding de garder une synchonisation en tout temps entre une textbox et une valeur stockée dans un objet fait maison. (la classe ayant servi à l'instanciation de cet objet maison dérivera de DependencyObject)

Exemple de dataBinding entre une TextBox et une DependencyProperty :

<TextBox Text="{Binding Source={StaticResource myEcwViewInfos}, Path=EcwViewZoom}"/>

À noter que dans notre exemple, myEcwViewInfos est une ressource déclarée dans Window.Resources, et la classe utilisée pour l'instancier contient une DependencyProperty appellée EcwViewZoom.

Cependant, la façon dont sont stockées les propriétés dans l'objet est radicalement différente de l'approche que l'on connait avec les propriétés habituelles : les valeurs sont stockées et retournées grâce aux méthodes SetValue et GetValue, et qui sont directement gérées par la framework.

Afin de rendre ce concept plus transparent, il est possible de wrapper ces appels dans une propriété .Net classique. C'est d'ailleurs ce que fait par défaut le snippet propdp dans visual studio 2008. 

public double EcwViewZoom
{
    get { return (double)GetValue(EcwViewZoomProperty); }
    set
   
{
        m_counter++;

        SetValue(EcwViewZoomProperty, value);
    }
}

// Using a DependencyProperty as the backing store for EcwZoom. This enables animation, styling, binding, etc...
public static readonly DependencyProperty EcwViewZoomProperty =
DependencyProperty.Register("EcwViewZoom", typeof(double), typeof(EcwViewInfos), new UIPropertyMetadata(1.0));

Imaginons maintenant que nous désirions ajouter de la logique applicative pour compter le nombre de lectures d'une DependencyProperty ( DP pour les intimes ) : le premier reflexe serait d'incrémenter un membre privé de type int dans le setter de la DP. 

Si nous établissons un Binding entre une TextBox et cette DP, nous pourrons remarquer que le contenu de la textbox et la DP seront en tout temps synchronisés, mais que jamais, le compteur ne sera incrémenté, quand bien même nous effectuons une multitude de modification de la textbox. ne vous inquiétez pas, l'explication est tout à fait logique.

Comme mentionné précédemment, l'accès à la DP se fait par les méthodes GetValue et SetValue. Le wrapper .Net est là uniquement pour uniformiser l'accès à une DP depuis le codebehind, ainsi, accèder à une Propriété ou une DP se fait syntaxiquement de la même manière, mais la framework, en interne, accède directement aux méàthodes Getvalue / SetValue. Il est alors plus facile de comprendre le comportement sur le Binding : le Wrapper .Net de la DP n'étant jamais appellé par le biais du binding car la framework appelle directement SetValue, le compteur ne se voit jamais incrémenté. Si l'ajout de logique applicative est nécessaire en cas d'assignation de la DP, il convient d'utiliser les RegisteredCallbacks. La méthode Register de DependencyProperty permet de définir une méthode à appeller lors de la modification de la DP.

public static readonly DependencyProperty EcwViewZoomProperty =
DependencyProperty.Register("EcwViewZoom", typeof(double), typeof(EcwViewInfos), new FrameworkPropertyMetadata(0.0, new PropertyChangedCallback(EcwViewInfos.OnZoomChanged)));

La méthode OnZoomChanged sera alors appellée lorsque la propriété sera modifiée via la méthode SetValue.

public static void OnBoundariesChanged(DependencyObject o, DependencyPropertyChangedEventArgs e)
{
    // ...
}