IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)
Navigation

Inscrivez-vous gratuitement
pour pouvoir participer, suivre les réponses en temps réel, voter pour les messages, poser vos propres questions et recevoir la newsletter

Windows Forms Discussion :

[C#]Changer couleur d'une partie d'un texte


Sujet :

Windows Forms

  1. #1
    Membre éprouvé
    Profil pro
    Inscrit en
    Juin 2008
    Messages
    612
    Détails du profil
    Informations personnelles :
    Localisation : Belgique

    Informations forums :
    Inscription : Juin 2008
    Messages : 612
    Points : 1 050
    Points
    1 050
    Par défaut [C#]Changer couleur d'une partie d'un texte
    Bonjour,

    Voici mon problème :
    Je dois écrire (entre autres) un texte dans la méthode OnPaint(PaintEventArgs e)

    Jusque là, pas de problème avec e.Graphics.DrawString que j'utilise souvent.

    Ensuite, je dois sélectionner une partie du texte, et donc entourer cette partie d'un rectangle bleu. Ici non plus, pas de problème, j'utilise les régions, comme ceci dans cet extrait que j'ai simplifié pour mes tests :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
     
    gfx.DrawString(text, Font, brushFore, posX, posY);
     
    var charRanges = new CharacterRange[]                                       // créer un tableau de Zones de texte
      { new CharacterRange(selectionStart, 1) };                                  // une seule zone, la zone sélectionnée
    sf.SetMeasurableCharacterRanges(charRanges);                                // lie le stringFormat aux zones de texte
    var regions = gfx.MeasureCharacterRanges(text, Font, ClientRectangle, sf);  // obtient toutes les régions des zones textes (1 seule ici)
    gfx.FillRegion(brushbackselect, regions[0]);                                // colorie le fond du texte sélectionné
    var rec = regions[0].GetBounds(gfx);                                        // récupère le rectangle de la région
    Là où ça se gâte, c'est lorsque je dois réécrire la partie masquée de la chaîne avec une couleur contrastante avec ce rectangle, et là, mission impossible avec :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    gfx.DrawString(text.Substring(selectionStart, 1), Font, brushforeselect, rec, sf);
    Parce qu'avec un seul caractère sélectionné (ici, selectionLenght est forcé partout à 1) ce caractère s'affiche bien, mais par à l'exacte position correcte, il est décalé d'un ou deux pixels (souvent vers le bas et la droite) en fonction de la fonte utilisée.

    De plus, si je sélectionne 2 caractères ou plus, alors 1 seul s'affiche car il manque quelques pixels pour afficher le second, bien que le rectangle bleu recouvre correctement les 2 caractères à remplacer. Je suis contraint d'élargir le rectangle d'une taille dépendant de la largeur de la fonte, et donc non reproductible, et évidemment mes caractères réaffichés souffrent du même problème de non alignement.

    J'ai évidemment cherché dans la MSDN, mais justement ils fournissent des exemples qui opèrent avec la première partie du code que j'affiche, mais en utilisant une couleur semi-transparence, ce qui leur évite de devoir réécrire la portion de texte masquée, et donc de dévoiler ce problème. Comme quoi ils ont forcément remarqué le dit problème.

    Bref, quelqu'un a-t-il un moyen pour réécrire une portion de chaine sur un texte avec une autre couleur en superposant exactement cette portion à son emplacement d'origine?

    Ou, autre solution, comment écrire un texte de 2 couleurs différentes?

    J'ai bien pensé à transformer le rectangle concerné en bitmap et changer la couleur de chaque pixel trouvé, mais ça me semble excessivement lourd.

    Merci d'avance,
    Claude

  2. #2
    Expert confirmé

    Homme Profil pro
    Chef de projet NTIC
    Inscrit en
    Septembre 2006
    Messages
    3 580
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Haute Garonne (Midi Pyrénées)

    Informations professionnelles :
    Activité : Chef de projet NTIC
    Secteur : Aéronautique - Marine - Espace - Armement

    Informations forums :
    Inscription : Septembre 2006
    Messages : 3 580
    Points : 5 195
    Points
    5 195
    Par défaut
    le code suivant fait ce que tu demandes me semble t-il

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
            private void Form1_Paint(object sender, PaintEventArgs e)
            {
                Graphics g = e.Graphics;
     
                SolidBrush br1 = new SolidBrush(Color.LightBlue);
                SolidBrush br2 = new SolidBrush(Color.Red);
     
                SizeF t1 = g.MeasureString("nico", Font, new PointF(0, 10), StringFormat.GenericTypographic);
                SizeF t2 = g.MeasureString("las", Font);
                g.DrawString("nico", Font, br1, new PointF(0, 10));
                g.DrawString("las", Font, br2, new PointF(t1.Width, 10));
                g.DrawString("nicolas", Font, br1, new PointF(0, 40));
            }
    The Monz, Toulouse
    Expertise dans la logistique et le développement pour
    plateforme .Net (Windows, Windows CE, Android)

  3. #3
    Membre éprouvé
    Profil pro
    Inscrit en
    Juin 2008
    Messages
    612
    Détails du profil
    Informations personnelles :
    Localisation : Belgique

    Informations forums :
    Inscription : Juin 2008
    Messages : 612
    Points : 1 050
    Points
    1 050
    Par défaut
    le code suivant fait ce que tu demandes me semble t-il
    Effectivement, et ton exemple fonctionnel m'a permis de trouver ce qui ne fonctionnait pas dans mon code, dont les paramètres de StringFormat. Merci

    Je profite de ta syntaxe pour poser une question subsidiaire (malheureusement, aucun prix à gagner) :

    Quand on utilise cette syntaxe :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    var dim = g.MeasureString("nico", Font, new PointF(0, 10), StringFormat.GenericTypographic);
    ou de façon plus générale, celle-ci (monObjet étant un objet IDisposable) :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    MaMethode (new monObjet());
    L'objet est-il disposé automatiquement ou non après l'appel de la méthode ou simplement plus référencé?

    Autrement dit les deux syntaxes précédentes sont-elles équivalentes à ceci :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    using (var sf = StringFormat.GenericTypographic)
    using (var obj = new monObjet())
    {
       var dim = g.MeasureString("nico", Font, new PointF(0, 10), sf);
        MaMethode(obj);
    }
    ?
    C'est une question qui me préoccupe un peu, je n'ai rien trouvé à ce sujet dans les documentations sur Dispose().
    Pour l'instant, dans le doute, j'utilise systématiquement la seconde méthode (ou .Dispose()), pour ne pas laisser trainer des objets non disposés après usage.

    Merci

  4. #4
    Expert confirmé

    Homme Profil pro
    Chef de projet NTIC
    Inscrit en
    Septembre 2006
    Messages
    3 580
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Haute Garonne (Midi Pyrénées)

    Informations professionnelles :
    Activité : Chef de projet NTIC
    Secteur : Aéronautique - Marine - Espace - Armement

    Informations forums :
    Inscription : Septembre 2006
    Messages : 3 580
    Points : 5 195
    Points
    5 195
    Par défaut
    de toute façon, ton objet sera dégagé par le GC (garbage collector) si il n'est plus utilisé ni référencé

    Pour moi, le fait de faire un dispose() aide "grandement" le GC à éviter d'avoir à chercher ce qu'il doit dégager car en faisant dispose(), tu lui dis, "tiens, cet objet, tu peux le poubelliser sans problème"... avantage important puisque celà lui evitera d'avoir à chercher quoi nettoyer

    C'est pourquoi, par exemple, quand on crée des Brush, des Pen(s) etc..; il est conseillé de les disposer pour gagner en performance plutot que de laisser le GC à un moment nettoyer tout en prenant plus de temps qu'avec le Dispose().

    En aucun cas, tu n'auras de Memory Leak.. mais avec le dispose() tu éviteras les montées dans les tours de ta mémoire
    The Monz, Toulouse
    Expertise dans la logistique et le développement pour
    plateforme .Net (Windows, Windows CE, Android)

  5. #5
    Membre éprouvé
    Profil pro
    Inscrit en
    Juin 2008
    Messages
    612
    Détails du profil
    Informations personnelles :
    Localisation : Belgique

    Informations forums :
    Inscription : Juin 2008
    Messages : 612
    Points : 1 050
    Points
    1 050
    Par défaut
    Oui, j'avais bien compris le mécanisme de Dispose et aussi comment l'implémenter. A ce niveau, je n'ai aucun problème particulier, j'ai lu pas mal de docs à ce sujet.

    J'ai bien compris (je pense) qu'il faut appeler dispose pour tous les objets IDisposable, et également comment implémenter l'interface IDisposable, ainsi qu'un finaliseur de sécurité pour les ressources non managées dans l'objet de base.

    Ce que je voulais savoir c'est si lorsque j'écris

    MaMethode (new MonObjet());

    Donc, en créant l'objet "MonObjet" sans lui donner de nom explicite directement dans les paramètres passés à ma méthode, ou lorsqu'on obtient une instance dans les paramètres, comme tu l'as fait avec StringFormat.GenericTypographic (qui crée en fait une instance avec des valeurs par défaut),

    l'objet est-il disposé automatiquement par le code compilé final, ou "oublié" et donc laissé à disposition du GC mais sans libérer de suite ses ressources?

    Parce que si la réponse est non, ça veut dire qu'il ne faudrait donc jamais pour bien faire utiliser cette syntaxe avec des objets IDisposables, puisqu'on ne dispose bien évidemment d'aucun nom d'instance pour les libérer ensuite, et que donc on se fierait alors uniquement au GC avec tous les désagréments que ça occasionne.

    Dit de façon résumée, si ceci est toujours correct :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    MaMéthode (new ObjetNonDisposable()); // Pas besoin de Dispose, la question ne se pose pas
    est-ce que ceci l'est ?

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    MaMethode (new ObjetDisposable()); // l'objet est-il correctement disposé?
    J'espère que dit comme ça, ma question est plus claire

  6. #6
    Membre émérite Avatar de Guulh
    Homme Profil pro
    Inscrit en
    Septembre 2007
    Messages
    2 160
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 42
    Localisation : France, Paris (Île de France)

    Informations forums :
    Inscription : Septembre 2007
    Messages : 2 160
    Points : 2 925
    Points
    2 925
    Par défaut
    Citation Envoyé par ClaudeBg Voir le message
    est-ce que ceci l'est ?

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    MaMethode (new ObjetDisposable()); // l'objet est-il correctement disposé?
    Non, il ne l'est pas. Son finaliseur sera appelé à un moment ou à un autre, mais la méthode dispose n'est jamais appelée automatiquement.

    Donc oui, un objet Disposable qui n'a vocation qu'à passer en paramètre d'une méthode doit tout de même être disposé (que ce soit avec un bloc using ou un appel explicite, of course), ce qui donne
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    using(ObjetDisposable d = new ObjetDisposable())
        MaMethode(d);
    Et tant que j'y suis, Dispose n'a rien à voir avec la mémoire La mémoire, le framework la gère tout seul. Dispose sert à libérer d'autres types de ressources, comme des connexions réseaux, des fichiers, des handles, ...
    Et éventuellement de la mémoire si t'en avais alloué à la main (en interop par exemple), mais il y a peu souvent des raisons de le faire, dans notre beau monde managé.
    ಠ_ಠ

  7. #7
    Membre éprouvé
    Profil pro
    Inscrit en
    Juin 2008
    Messages
    612
    Détails du profil
    Informations personnelles :
    Localisation : Belgique

    Informations forums :
    Inscription : Juin 2008
    Messages : 612
    Points : 1 050
    Points
    1 050
    Par défaut
    Non, il ne l'est pas. Son finaliseur sera appelé à un moment ou à un autre, mais la méthode dispose n'est jamais appelée automatiquement.
    Alors j'ai bien fait d'utiliser dans le doute le using pour les objets disposables au lieu de les créer dans les paramètres.

    C'est quand même dommage que Microsoft n'aie pas fait une équivalence de syntaxe, parce que lorsqu'on crée un paramètre de cette façon on n'a évidemment aucun moyen de le disposer explicitement.

    Merci à vous deux pour les réponses

    A+
    Claude

  8. #8
    Membre éprouvé
    Profil pro
    Inscrit en
    Juin 2008
    Messages
    612
    Détails du profil
    Informations personnelles :
    Localisation : Belgique

    Informations forums :
    Inscription : Juin 2008
    Messages : 612
    Points : 1 050
    Points
    1 050
    Par défaut
    theMonz31, si ta méthode a semblé fonctionné chez moi, c'est juste dû à une bonne combinaison taille de texte/fonte utilisée.

    Pas de chance

    J'ai réalisé toute la partie de code utilisant cette méthode mais :

    1) Si la sous-chaine commence ou termine par un espace, ça ne fonctionne plus. Mais ça, j'ai pu résoudre en ajoutant un caractère quelconque avant et après la sous-chaine puis en effectuant la soustraction de la chaine modifiée - la taille des 2 caractères, afin d'inclure les espaces dans la mesure.

    2) Avec certaines fontes (notamment si on augmente la taille), ça ne fonctionne plus parce qu'à nouveau MeasureString renvoie 1 de trop à la largeur mesurée. Bref, ça fonctionne avec la taille par défaut, mais si j'augmente je me retrouve à partir d'une certaine taille de nouveau décalé de 1 pixel.

    Je cherche à déterminer comment calculer la valeur de correction à apporter en fonction de la fonte, j'ai lu quelque part que MeasureString renverrait un "0em" de trop lors de la mesure, et qu'il fallait donc compenser.

    La méthode que j'utilisais avec CharacterRanges produit exactement le même résultat.

    Si quelqu'un a une idée, je suis preneur, sinon je continue de chercher et si je trouve une solution je l'indique ici au cas où ça pourrait servir à d'autres.

    Apparemment, à voir sur le net, beaucoup maudissent MeasureString, LOL

    J'aimerais pouvoir me passer d'appeler une fonction API parce qu'à terme j'aimerais pouvoir utiliser ces controles sous mono pour portage du programme en cours de réalisation vers linux.

    Edit : Chose "amusante", j'arrive à obtenir maintenant un réalignement correct pour le début et la fin de la sous-chaîne, l'astuce étant d'arrondir toutes les largeurs renvoyées par MeasureString avant de les cumuler.

    Mais si la chaîne contient des espaces, le début et la fin de la chaînes sont alignés qu'on écrive en une seule fois ou en plusieurs parties, mais les caractères intermédiaires ne sont pas répartis de façon identique.

    Je me suis amusé à comparer ce qui est écrit dans un textbox utilisant la même fonte (très grande), et effectivement les caractères sont alignés différemment que lorsqu'on utilise DrawString, il y a des différences de 1 pixel en plus ou en moins selon l'emplacement du caractère affiché, et ce uniquement si la chaîne contient des espaces.

    Donc, également un bug dans Drawstring qui semble "arrondir" les positions des caractères internes de la chaîne et qui semble très mal appréhender les espaces.

    Je sens que je vais devoir passer par l'écriture de chaque caractère séparément. Pas génial tous ces bugs dans des fonctions qui me semblent élémentaires.

    Merci
    Claude

  9. #9
    Membre émérite Avatar de Guulh
    Homme Profil pro
    Inscrit en
    Septembre 2007
    Messages
    2 160
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 42
    Localisation : France, Paris (Île de France)

    Informations forums :
    Inscription : Septembre 2007
    Messages : 2 160
    Points : 2 925
    Points
    2 925
    Par défaut
    Citation Envoyé par ClaudeBg Voir le message
    C'est quand même dommage que Microsoft n'aie pas fait une équivalence de syntaxe, parce que lorsqu'on crée un paramètre de cette façon on n'a évidemment aucun moyen de le disposer explicitement.
    Ca doit pas etre quelque chose que l'on fait suffisamment souvent pour meriter une syntaxe particuliere. Le besoin est plus pressant en C++, ou la gestion de la memoire est manuelle et où donc plus de classes ont besoin de code de nettoyage, mais en C# assez peu de classes detiennent des ressources.
    Il se trouve que le domaine qui t'interesse, le dessin, est de ceux qui neccesitent une attention particuliere aux ressources
    ಠ_ಠ

  10. #10
    Membre éprouvé
    Profil pro
    Inscrit en
    Juin 2008
    Messages
    612
    Détails du profil
    Informations personnelles :
    Localisation : Belgique

    Informations forums :
    Inscription : Juin 2008
    Messages : 612
    Points : 1 050
    Points
    1 050
    Par défaut
    Salut
    -----
    Ca doit pas etre quelque chose que l'on fait suffisamment souvent pour meriter une syntaxe particuliere
    Il leur aurait suffit dans leur compilateur de prendre en compte que si la syntaxe methode(new objet()) utilisait un objet disposable, le dispose serait appelé automatiquement sur objet après exécution de methode. Ca me semble assez élémentaire à faire, mais bon, à partir du moment où on le sait , on fait attention.

    Il se trouve que le domaine qui t'interesse, le dessin, est de ceux qui neccesitent une attention particuliere aux ressources
    En fait, j'ai surtout besoin de créer une série de contrôles particuliers pour une application spécifique (pilotage d'interfaces domotiques). Donc, qui dit contrôle personnalisé dit dessin, LOL.

    Sinon, pour mon problème de DrawString et de MeasureString, voici ce que ça donne en image.

    Sur cette image, au-dessus un textBox ordinaire, en dessous un contrôle perso où je me contente d'écrire une chaîne en une seule fois.
    Les deux fontes sont les mêmes, et les chaînes également.



    Notez que l'affichage n'est pas identique, alignement différent.
    Par contre, si aucune des chaînes ne contient d'espace, on obtient un alignement strictement identique. Drawstring n'imprime donc pas les espaces avec la même largeur que la méthode utilisée dans TextBox. J'avoue assez mal comprendre comment il n'a pas été possible de donner à DrawSrtring le même comportement que celui de l'appel API utilisé dans le TextBox, ça n'a aucun sens.

    Maintenant, je sélectionne un caractère dans le contrôle du dessous, ce qui a comme résultat d'afficher la chaîne en 3 parties distinctes : gauche - texte sélectionné - droite. Voici ce que ça donne :



    On remarque que les calculs de position sont exacts, puisque le "D" final continue de correspondre et n'a pas subit de déplacement.
    Par contre, le 2eme et le 3eme "0" se sont déplacés d'un pixel vers la gauche mais pas le 4eme (il faut bien regarder) : ça n'a aucun sens. En statique, ça ne se remarque pas, mais en dynamique (sélection qui bouge en fonction du clavier) on voit nettement les caractères se balader. Or ça ne se produit évidemment pas dans le TextBox

    Je déduis de ça que Microsoft propose une fonction DrawString et MeasureString qu'il n'utilise pas lui-même dans ses propres contrôles.

    On en est donc réduit, soit à utiliser les contrôles Microsoft originaux, soit à dériver de ces contrôles (avec des tas de suppression de propriétés devenues inutiles et donc lourd), soit à faire appel aux dll, ce qui, si j'ai bien compris, rend le code non portable vers Mono (bien joué), soit à produire un résultat "pas pro".

    J'avoue être un peu déçu, sauf si je n'ai pas bien saisi quelque chose.
    Bon, il reste encore la possibilité de trafiquer une méthode DrawString qui gère différemment les espaces, mais ça commence à devenir lourd pour "bêtement" imprimer correctement un texte, ce qui est à la portée de n'importe quel langage basique dans n'importe quel environnement basique.

    Claude

  11. #11
    Membre éprouvé
    Profil pro
    Inscrit en
    Juin 2008
    Messages
    612
    Détails du profil
    Informations personnelles :
    Localisation : Belgique

    Informations forums :
    Inscription : Juin 2008
    Messages : 612
    Points : 1 050
    Points
    1 050
    Par défaut
    Bon, pour informer celui que ça intéresse

    J'ai repris mes méthodes d'affichage et je les ai transformées en n'affichant plus des textes entiers avec DrawString, mais en affichant caractère par caractère et en calculant la position du caractère suivant avec MeasureString sur le caractère qu'on vient d'afficher.

    En opérant comme ça, je n'ai plus de décalage ET en plus mon texte est strictement aligné avec le texte d'un TextBox classique.

    Moralité,

    - DrawString ne donne pas le même résultat si on imprime "mot1mot2" que si on imprime "mot1" puis "mot2" si mot1 ou mot2 contient un espace (alors qu'avec un StringFormat.GenericTypographic on devrait obtenir la même chose).

    - Les contrôles natifs du framework utilisent manifestement une méthode qui produit les mêmes résultats que si on affiche caractère par caractère, et non si on affiche des chaînes complètes.

    - DrawString comporte un bug de positionnement lorsque la chaîne contient au moins un espace

    Je trouve le bug quand même grave concernant une opération si basique, mais bon.... Manifestement on est en présence d'erreurs d'arrondis.

    Evidemment en opérant comme ça on se retrouve confronté à une "anomalie" de MeasureString qui fait que cette méthode ne prend pas en compte un espace en début ou en fin de chaîne, et en mesurant caractère par caractère on va tomber sur ce cas.

    Dit autrement, mesurer la largeur d'un espace comme ceci :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    var largSpace = gfx.MeasureString(" ", Font, Width, sf).Width;
    ne fonctionne pas.
    Il faut utiliser une ruse pour ne pas avoir d'espace aux extrémités, comme par exemple :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    var largSpace = gfx.MeasureString("! !", Font, Width, sf).Width - gfx.MeasureString("!!", Font, Width, sf).Width;
    Le tout cumulé produit un code qui est bien plus conséquent que d'utiliser simplement Drawstring sur une chaîne complète, mais c'est le prix à payer pour obtenir un affichage correct lorsqu'on a besoin de superpositions.

    Reste à savoir ce que ça coûte en terme de performances, il faudrait vraiment une méthode DrawString qui fonctionne correctement. Ma solution définitive pour les textes longs, c'est donc de recréer une méthode MyDrawString qui opère comme ceci :

    - Scindage du texte en sous-chaines sur base du séparateur 'espace'.
    - Calculer la largeur d'un espace avec l'astuce ci-dessus
    - Appeler DrawString pour chaque sous-chaine
    - Espacer les sous-chaines d'une distance égale à la largeur d'un espace calculé multiplié par le nombre d'espaces consécutifs

    De la sorte, on obtient une impression correcte et qui correspond à l'impression utilisée dans les contrôles Microsoft. Mais c'est lourd pour un simple affichage de texte.

    Merci de m'avoir aidé
    Claude

+ Répondre à la discussion
Cette discussion est résolue.

Discussions similaires

  1. Réponses: 2
    Dernier message: 26/04/2010, 15h36
  2. changer la couleur d'une partie d'une chaine de caractère
    Par mohcultiv dans le forum Général JavaScript
    Réponses: 10
    Dernier message: 18/01/2008, 00h16
  3. Réponses: 1
    Dernier message: 12/09/2007, 18h11
  4. [TTrackbar] Changer couleur de la partie selected
    Par MiJack dans le forum Delphi
    Réponses: 7
    Dernier message: 13/11/2006, 13h16
  5. changer couleur d'une "case" selon clique
    Par Jéjé2reims dans le forum MFC
    Réponses: 4
    Dernier message: 05/02/2004, 12h19

Partager

Partager
  • Envoyer la discussion sur Viadeo
  • Envoyer la discussion sur Twitter
  • Envoyer la discussion sur Google
  • Envoyer la discussion sur Facebook
  • Envoyer la discussion sur Digg
  • Envoyer la discussion sur Delicious
  • Envoyer la discussion sur MySpace
  • Envoyer la discussion sur Yahoo