[C#] GDI : Redessiner une partie de la fenêtre sur OnPaint

GDI : Redessiner une partie de la fenêtre sur OnPaint [C#] - C#/.NET managed - Programmation

Marsh Posté le 30-01-2006 à 23:09:24    

Je suis en train de découvrir GDI avec C#. Ca m'a l'air assez simple d'utilisation, même si je ne suis pas sûr de tout comprendre le fonctionnement :D
 
J'ai en mémoire un array de 4 cases.
 
Sur "onpaint", je dessine 4 images correspondant aux trois cases.
Vu qu'à terme la zone définie par le tableau sera bien plus grande que la zone d'affichage, j'ai fait de l'optimisation de la mort qui tue, c'est à dire que si je vois qu'une cases n'est pas dans le tableau, je ne la dessine pas (super non ? ;))
 
Et j'aimerais bien vérifier que ça marche... Et c'est là que ça va plus !
 
Dans le coin suppérieur gauche de la fenête, je dessine un compteur indiquant combien j'ai dessiné de cases.
 
Lorsque je redimensionne ma form, OnPaint se lance bien, et le code qui est dedans aussi.
A priori, l'optimisation marche bien d'après mes tests sauf que... mon compteur ne se redessine pas !
De ce que je pense avoir compris du fonctionnement du bignou, même si dans OnPaint je redéfini l'affichage de toute la fenête, il ne relatte les modifications que sur la partie qui a bel et bien bougé, je me trompe ?
Alors comment lui dire de redessiner mon compteur qui n'est pas dans la zone redessinée ???

Reply

Marsh Posté le 30-01-2006 à 23:09:24   

Reply

Marsh Posté le 30-01-2006 à 23:13:49    

Voivi le bout de code qui coince :

Code :
  1. protected override void OnPaint(PaintEventArgs e)
  2.         {
  3.             Graphics g = e.Graphics;
  4.             int cpt = 0;
  5.             for (Int64 i = 0; i < world.height; i++)
  6.             {
  7.                 if (i * 51 > this.ClientSize.Width)
  8.                 {
  9.                     break;
  10.                 }
  11.                 else
  12.                 {
  13.                     for (Int64 j = 0; j < world.width; j++)
  14.                     {
  15.                         if (j * 51 > this.ClientSize.Height)
  16.                         {
  17.                             break;
  18.                         }
  19.                         else
  20.                         {
  21.                             g.DrawImage(Image.FromFile("pictures/terrains/" + world.lands[i, j].terrain.pictureName), i * 51, j * 51);
  22.                             cpt++;
  23.                         }
  24.                     }
  25.                 }
  26.             }
  27.             g.DrawString(cpt.ToString(), new Font("Verdana", 10), Brushes.White, 10, 10);
  28.         }


Message édité par Arjuna le 30-01-2006 à 23:48:33
Reply

Marsh Posté le 30-01-2006 à 23:15:16    

PS: je suppose que parmi les améliorations possibles je devrais créer un array d'Image qui contiennent toutes les images possible non ? Là il fait réellement un accès disque pour chaque image, ou s'il est intelligent et se rappelle qu'il a déjà chargé l'image le coup d'avant ?

Reply

Marsh Posté le 30-01-2006 à 23:47:36    

Chais pas si il faisait un accès au disque ou pas, mais en tout cas, il me laissait un handle exclusif sur le disque, ce qui ne me plaisait pas. Corrigé en foutant mes images dans un tableau, puis aussi en recopiant chaque image dans un memorystream que j'utilise alors comme source pour l'image au final.
 
Et du coup, afin d'optimiser un peu la vitesse, en mémoire je peux gérer l'image en BMP au lieu de PNG, ce qui évite un re-décodage de mes images à chaque OnPaint. J'ai bon ou je suis totalement à la ramasse ?
 
PS: pour ce qui est du réaffichage de mon compteur, ben ça marche toujours pas :D C'est un comble, c'est un truc de débug qui m'empêche d'avancer lol

Reply

Marsh Posté le 30-01-2006 à 23:48:18    

faut faire un Invalidate() pour forcer le redessin complet de la fenêtre


---------------
J'ai un string dans l'array (Paris Hilton)
Reply

Marsh Posté le 30-01-2006 à 23:51:15    

Harkonnen a écrit :

faut faire un Invalidate() pour forcer le redessin complet de la fenêtre


ok... sauf que là je vais avoir un soucy :) si je le fait dans le "OnPaint" ça sent le crash non ? :D
 
en fait, même si la solution de mettre le invalidate dans le "OnResize" marcherait, j'aimerais savoir si je ne peux pas tout simplement dire de réafficher un bout de la fenêtre en particulier.
 
-- Edit :
Euh... invalidate peut prendre un rectangle en paramètre. merci ! :jap:

Message cité 1 fois
Message édité par Arjuna le 30-01-2006 à 23:52:42
Reply

Marsh Posté le 30-01-2006 à 23:57:13    

Arjuna a écrit :

ok... sauf que là je vais avoir un soucy :) si je le fait dans le "OnPaint" ça sent le crash non ? :D


surtout pas malheureux [:totoz]
tu places un Invalidate() après avoir réalisé le tracé de ton compteur
 

Arjuna a écrit :


en fait, même si la solution de mettre le invalidate dans le "OnResize" marcherait, j'aimerais savoir si je ne peux pas tout simplement dire de réafficher un bout de la fenêtre en particulier.


y'a une surcharge qui permet de spécifier un rectangle à redessiner ;)
 
edit: il a édité [:pingouino]


Message édité par Harkonnen le 30-01-2006 à 23:57:32

---------------
J'ai un string dans l'array (Paris Hilton)
Reply

Marsh Posté le 31-01-2006 à 00:32:57    

ze soucy, c'est que le compteur est recalculé lors du repaint dans mon code ;)
 
mais c'est pas grave, j'ai mis le code du truc (c'est clair :D) dans le onresize et ça marche bien :)
 
j'ai ajouté un handler sur le "isinputchaisplusquoi" et je peux maintenant bouger un gif transparent par dessus mon "monde" avec le clavier :)
 
Allez, dans deux jours j'ai recodé civilization à ma sauce :D


Message édité par Arjuna le 31-01-2006 à 00:33:35
Reply

Marsh Posté le 31-01-2006 à 00:36:17    

par contre, je suis en train de réfléchir que dans le onpaint je devrais peut-être gérer seulement la partie à réafficher... parcequ'actuellement, je recalcule l'image de toute la partie visible de la fenêtre... sauf que de toute façon il n'affiche les modifs que dans la partie qui a effectivement bougé :D du coup non seulement je traîte trops de trucs, mais en plus ils servent à rien :D

Message cité 1 fois
Message édité par Arjuna le 31-01-2006 à 00:36:42
Reply

Marsh Posté le 01-02-2006 à 14:16:39    

Arjuna a écrit :

par contre, je suis en train de réfléchir que dans le onpaint je devrais peut-être gérer seulement la partie à réafficher...

C'est la sagesse même :)

Arjuna a écrit :

parcequ'actuellement, je recalcule l'image de toute la partie visible de la fenêtre... sauf que de toute façon il n'affiche les modifs que dans la partie qui a effectivement bougé :D du coup non seulement je traîte trops de trucs, mais en plus ils servent à rien :D

Bon j'ai pas tout compris à la problématique, mais je te suggèrerai bien d'utiliser le double buffering :

Code :
  1. // Ton double buffer dans lequel tu fais le dessin
  2. private Bitmap _doublebuffer = null;
  3. // flag pour resetter le dessin si besoin
  4. private bool _reset = false;
  5. // Si le control est resizé
  6. protected override void OnResize(EventArgs e)
  7. {
  8. // si le double buffer n'est pas null
  9. if(this._doublebuffer != null)
  10. {
  11.  // le supprimer
  12.  this._doublebuffer.Dispose();
  13.  this._doublebuffer = null;
  14. }
  15. // réallouer le buffer
  16. this._doublebuffer = new Bitmap(this.Width, this.Height);
  17. // faire reset :)
  18. this._reset = true;
  19. }
  20. // A appeler si la valeur de ton compteur change
  21. protected override void OnResize(EventArgs e)
  22. {
  23. // reset le dessin
  24. this._reset = true;
  25. }
  26. /// Paint the control
  27. protected override void OnPaint(PaintEventArgs e)
  28. {
  29. base.OnPaint (e);
  30. // si le buffer n'est pas nul (par précaution, mais normallement, le OnResize est toujours appelé avant)
  31. if(this._doublebuffer != null)
  32. {
  33.  // si le dessin est resetté
  34.  if(this._reset)
  35.  {
  36.   // faire le dessin
  37.   this.DrawControlIntoBuffer();
  38.   // disable the reset flag
  39.   this._reset = false;
  40.  }
  41.  // coller le dessin dans ta fenêtre
  42.  using(Graphics gc = this.CreateGraphics())
  43.   gc.DrawImage(this._doublebuffer, 0, 0);
  44. }
  45. }


---------------
Tout est normal, suffit de comprendre pourquoi.
Reply

Marsh Posté le 01-02-2006 à 14:16:39   

Reply

Marsh Posté le 01-02-2006 à 15:13:20    

si dans ma fenêtre j'ai un "invalidate" sur le rectangle "(10, 10), (20, 20)"
 
En 0, 0 j'ai un carré rouge
 
Dans le "onpaint", je change le carré rouge en carré vert.
 
Ben même après appel du invalidate le carré reste rouge : le onpaint ne redessine que la zone spécifiée dans la zone à redessiner, même si on redessine tout (enfin, c'est ce que j'ai compris d'après mes tests)

Reply

Marsh Posté le 01-02-2006 à 15:16:17    

sinon, pas tout pigé au double buffer :D ça fait quoi ça marche comment ?
 
ça va résoudre le problème de clignotement ?

Reply

Marsh Posté le 01-02-2006 à 16:24:48    

t'as une explication du double buffering sur ce magnifique topic qu'on devrait classer R+ :o
http://forum.hardware.fr/forum2.ph [...] =0#t517627


---------------
J'ai un string dans l'array (Paris Hilton)
Reply

Marsh Posté le 01-02-2006 à 16:38:37    

Ouaip, ça va résoudre tes problèmes de clignotement.
Avec ça tu ne fais qu'une seule opération sur le Graphics associé à ta fenêtre, donc ça tourne du feu de dieu.
Je fais des animations avec ce principe.
 
Je comprend pas ton problème avec ton Invalidate. Ce que tu dit semble logique : s'il redessine la zone 10x10, 20x20, il ne redessine pas ce qui se trouve en 0x0. Avec le code que je t'ai filé, j'ai pas pris en compte le ClipRectangle : je redessine tout à chaque fois. Mais si tu veux ne redessiner qu'une partir, tu peux : il suffit de passer le ClipRectangle en paramètre de la méthode 'DrawControlIntoBuffer'.
 
Ensuite, j'ai une interrogation déontologique (si si !) : pourquoi tu décides de changer un carré rouge en vert dans ton OnPaint ? Il est là pour dessiner, pas pour choisir si il faut que ce soit vert ou rouge. Pour métaphorer (oulàlàlà) : le OnPaint c'est le colleur d'affiche. Bah c'est pas à lui de décider si le texte doit être en vert ou en noir. C'est au créatif :)    Bon c'est ptet moi qui ai pas compris ton bp en fait...


---------------
Tout est normal, suffit de comprendre pourquoi.
Reply

Marsh Posté le 01-02-2006 à 17:08:22    

_Mose_ a écrit :

Ouaip, ça va résoudre tes problèmes de clignotement.
Avec ça tu ne fais qu'une seule opération sur le Graphics associé à ta fenêtre, donc ça tourne du feu de dieu.
Je fais des animations avec ce principe.
 
Je comprend pas ton problème avec ton Invalidate. Ce que tu dit semble logique : s'il redessine la zone 10x10, 20x20, il ne redessine pas ce qui se trouve en 0x0. Avec le code que je t'ai filé, j'ai pas pris en compte le ClipRectangle : je redessine tout à chaque fois. Mais si tu veux ne redessiner qu'une partir, tu peux : il suffit de passer le ClipRectangle en paramètre de la méthode 'DrawControlIntoBuffer'.
 
Ensuite, j'ai une interrogation déontologique (si si !) : pourquoi tu décides de changer un carré rouge en vert dans ton OnPaint ? Il est là pour dessiner, pas pour choisir si il faut que ce soit vert ou rouge. Pour métaphorer (oulàlàlà) : le OnPaint c'est le colleur d'affiche. Bah c'est pas à lui de décider si le texte doit être en vert ou en noir. C'est au créatif :)    Bon c'est ptet moi qui ai pas compris ton bp en fait...


ok pour le double buffering :) vais tester ça ce soir :)
 
sinon, pour le problème du clip rectangle, j'ai pas de souci, c'est juste qu'au départ :
-> sur un resize, un onpaint est déclenché pour les zones modifiée. hors moi je voulais affiche un petit compteur dans la zone d'affichage, et j'avais beau le modifier, il ne se mettait pas à jour lors de l'affichage.
d'où :
- ma question du départ sur la façon de forcer le redessin d'une zone particulière (avec invalidate)
- et le fait que j'ai compris que ça servait à rien que je redessine dans mon graphics toute la zone d'affichage, puisque seule la zone du cliprectangle était effectivement redessinée, d'où ma recherche d'optimisation pour ne redessiner que la zone qui va bien :)

Reply

Marsh Posté le 01-02-2006 à 17:56:37    

Okidoki.
Je pense que tu vas apprécier le double buffering :)
Et si t'as des questions pour moi, c'est demain ou dans deux mois et demi. Après demain je me casse loin... très loin...


---------------
Tout est normal, suffit de comprendre pourquoi.
Reply

Marsh Posté le 01-02-2006 à 19:08:49    

Harkonnen a écrit :

t'as une explication du double buffering sur ce magnifique topic qu'on devrait classer R+ :o
http://forum.hardware.fr/forum2.ph [...] =0#t517627


Lu :) En fait, je savais déjà ça. Moi c'était la façon de le coder que je m'attendais à trouver expliquée ;) Car en fait, j'ai un peu rien compris à ce qu'à posté _Mose_ :ange:

Reply

Marsh Posté le 01-02-2006 à 20:20:49    

En attendant de maîtriser le fonctionnement du double buffer, j'ai avancé sur la gestion des mouvements des unités dans mon monde.
 
Pour résumer :
- Le monde est défini par un tableau à deux dimensions de "cLand".
- cLand est un objet contenant un "cTerrain" qui indique les propriétés du terrain, et un tableau à une dimension de "cUnit" qui contient les unités qui se trouvent sur ce terrain.
 
Bon, avec tout mon joyeux bordel qui commence à être bordelique, j'arrive à balader une pièce dans ce petit monde.
 
Par contre, pour parcourir les unités (genre pour trouver l'unité suivante après la fin du mouvement de l'unité en cours de mouvement), c'est un peu pas évident.
 
Du coup, j'ai l'idée de rajouter un tableau à une dimension de "cUnitLocation" dans l'objet du monde (au même niveau que le array de "cLand" ) contenant des liens vers toutes les unités qui se trouvent dans le monde avec associée, la position x,y dans on array de "cLand".
 
Seulement, là je vais avoir un petit problème. C# ne sait pas faire de tableaux redimensionnables. Et utiliser un ListArray, je ne suis pas sûr que ce soit une bonne solution si je veux que ce soit tout bien optimisé.
 
Me reste alors une autre solution bien batarde, qui consiste à dimensionner mon array à 65535 lignes initialisées à <null> et remplir au fur et à mesure avec mes unités... Et remettre <null> quand une unité est par exemple détruite. Je ne suis pas certain que ce soit vraiment mieu, puisque je vais passer mon temps à me balader dans ce grand tableau, et en plus c'est pas super propre...
 
Reste la solution de recopier mon tableau dans un tableau plus grand ou plus petit à chaque fois qu'une unité est crée ou détruite, mais là je crois que c'est ce qu'il y a de pire ! C'est ce que j'ai opté pour le array de "cUnit" qui est dans "cLand" lorsqu'une unité se déplace, parceque le nombre d'unités sur une même case ne devrait jamais être grand, mais là, si je fait ça pour toutes les unités du monde, ça va être bonbon...
 
C'est quoi la meilleur solution ? Y'en a d'autres mieux ?


Message édité par Arjuna le 01-02-2006 à 20:21:10
Reply

Marsh Posté le 02-02-2006 à 12:59:02    

* Pour les ArrayList, perso j'utilise beaucoup, maintenant niveau perf j'en sais rien, c'est pas ça qui fait des ralentissements chez moi. Ca a le mérite d'être simple et rapide à utiliser, et ça peut se transformer en tableau sur demande (ToArray)
* La solution du gros tableau statique est pas très pratique : si tu passes ton temps à enlever des unités et à en remettre, tu vas devoir chercher des places libres quand tu créé une nouvelle unité.
* Sinon tu peux coder ta propre liste chaînée (http://fr.wikipedia.org/wiki/Liste), mais je crois que niveau perf avec C# c'est pas forcément mieux que les ArrayList.
 
M'enfin comme on dit souvent dans le métier : "Eviter d'optimiser trop tôt". En gros, je serais toi, je commencerais avec le plus simple, et je verrais à améliorer après.
Si tu veux profiter pleinement de la puissance de l'objet et pouvoir faire tes modif plus tard sans avoir à repasser dans tout ton code, je te conseille de faire une classe vide 'MonTableauRedimensionnable' qui hérite de ArrayList. Comme ça le jour où tu changes, tu n'auras qu'une seule classe à refaire, et en attendant d'avoir des pb de perf, tu peux avancer !


---------------
Tout est normal, suffit de comprendre pourquoi.
Reply

Marsh Posté le 02-02-2006 à 14:41:58    

c pas faux ça :D

Reply

Marsh Posté le 10-02-2006 à 21:17:02    

Salut Harkonnen
 
Simple question : le GDI avec C# c'est plus simple qu'avec les MFC ou c'est aussi chiant ?

Reply

Marsh Posté le 10-02-2006 à 22:01:40    

Spa con ct'histoire de double buffering, rajouterai ca (dans 10 jours, la je suis en vacances  :sol:  ) dans le projet du taff pour éviter le clignotement lors du raffraichissement...
 
Sinon, pour répondre à ProblemSomewhere, ca fait pas longtemps que je fais du GDI+/MFC, mais je trouve ca assez sympa à utiliser (en même temps, c'est la première fois que j'ai à dessiner des trucs moi même sur une interface windows...).

Reply

Marsh Posté le 11-02-2006 à 02:11:10    


Salut,
c'est largement plus simple à utiliser. les classes de .NET étant bien mieux faites que les MFC, c'est un régal de faire du GDI+ en .NET


---------------
J'ai un string dans l'array (Paris Hilton)
Reply

Marsh Posté le 11-02-2006 à 19:32:24    

C'est bon à savoir.  
 
Je vais pouvoir me former sur mon temps libre (de chômeur). Vue la demande (quasi exclusivement destinées aux BAC+4/5) mettant en oeuvre UML/.NET/J2EE/Web services, ça ne sera probablement pas une mauvaise idée. ;)
 
Il faut que je me mette à Eclipse aussi. :o


Message édité par Profil supprimé le 11-02-2006 à 19:33:20
Reply

Marsh Posté le    

Reply

Sujets relatifs:

Leave a Replay

Make sure you enter the(*)required information where indicate.HTML code is not allowed