Custom NSView avec drawRect et coordones donnes par l'utilisateur

DonioDonio Membre
11:09 modifié dans API AppKit #1

Bonjour,

Je suis debutant ça c'est clair et j'essaye d'aprendre par la pratique. J'ai reussi a faire une sorte de calculatrice donc d'utilise les NSObject et compagnie.

Maintenant je veux dessiner un rectangle dans une custom NSView avec de dimensions donnes par l'utilisateur.

Alors je veux utiliser un NSView avec un NSObject.

Et donc ma questionest comment marier les deux. Moi j'ai reussi a faire deux controleurs un pour NSView et un pour NSObject , j'ai reussi a recupere les valeurs introduites mais je ne sais pas faire la liason avec la NSView.

J'en ai recherche sur le net et j'ai trouve que des exemples qui demontre l'utilisation de drawRect mais il le fait de maniere automatique cad au lancement de l'application le rectangle est dessiné. Moi je veux avoir la NSView dans un coin de ma fenetre et alors que les champs des dimensions ont ete remplies et le buton "dessiner" clicke  que le dessin soit fait.

J'ai pense a ça, un seul controlleur DessinView.h

<br /><br />@interface DessinView : NSView {<br />}<br /><br />@interface DesenView : NSObject {<br />	IBOutlet id largeur;<br />	IBOutlet id longuer;<br />}<br /><br />-(IBAction) faitLeDessin:(id)sender;<br /><br /><br />@end<br /><br /><br /><br />@implementation DessinView<br /><br />- (id)initWithFrame:(NSRect)frame {<br />&nbsp; &nbsp; self = [super initWithFrame:frame];<br />&nbsp; &nbsp; if (self) {<br />&nbsp; &nbsp; &nbsp; &nbsp; // Initialization code here.<br />&nbsp; &nbsp; }<br />&nbsp; &nbsp; return self;<br />}<br /><br />-(IBAction) faitLeDessin:(id)sender<br /><br />{&nbsp; &nbsp; float l_a, l_b;<br />	<br />	l_a=[largeur floatValue];<br />	l_b=[longuer floatValue];<br />	<br />	<br />}<br />	<br />- (void)drawRect:(NSRect)rect {<br />&nbsp; &nbsp; // Drawing code here.<br />	<br />		<br />	NSRect rect1 = NSMakeRect ( 21,21,l_a,l_b );<br />	[[NSColor blackColor] set];<br />	1NSFrameRectWithWidth ( rect1, 1 );<br />}<br />	<br /><br />@end<br />


Mais ça ne marche pas. Donc comment je fais pour lier les deux et surtuot dessiner quand je veux pas au lancement de l'appli.


Merci.

Réponses

  • AliGatorAliGator Membre, Modérateur
    11:09 modifié #2
    Salut et bienvenue !

    Tout ce que je peux te dire c'est que tu es sur la bonne voie, je ne vois rien d'aberrant dans ton code et au niveau méthode c'est bien comme ça qu'il faut procéder : tu utilises des variables l_a et l_b dans ta sous-classe DessinView de NSView, que tu utilises dans le drawRect, et tu les changes avec ton IBAction quand tu cliques sur ton bouton dans ton interface, c'est bien la bonne méthode.

    Par contre le problème c'est que tu as déclaré "float l_a" et "float l_b" dans ta méthode "faitLeDessin:", ce qui fait que ces variables l_a et l_b sont locale à  cette méthode, et elles sont perdues dès la fin de la méthode ! Ce qu'il faut c'est déclarer "float l_a" et "float l_b" comme variables d'instance de ta classe DessinView.
    Comme tu utilises l_a et l_b dans drawRect (sans les déclarer dans drawRect) et donc que tu as bien fait les choses de ce côté, je suppose que tu as déjà  déclaré dans ton .h l_a et l_b comme variables d'instance de DessinView donc que tout est prêt. C'est juste que quand tu les redéclares dans "faitLeDessin:", les l_a et l_b locales masquent les l_a et l_b de ta classe...

    Donc normalement il suffit d'enlever "float l_a, l_b;" dans ta méthode "faitLeDessin:" pour pas que ces variables locales masquent les variables d'instance de DessinView et que ce soit bien les l_a et l_b de DessinView qui soient modifiées !
  • schlumschlum Membre
    11:09 modifié #3
    Houlà , tu n'essaierais pas de faire du VisualBasic / RealBasic toi là  ?  ???

    Cocoa ça fonctionne comme ça, c'est la vue et pas des objets extérieurs qui détermine ce qui doit être dessinée ; et c'est beaucoup plus logique comme ça.
    Si tu veux un rectangle qui est dessiné que quand on appuie sur un bouton, tu mets le dessin du rectangle dans un test et il faut que ce test devienne vrai que quand on a appuyé sur le bouton... Le clic sur le bouton demandant à  la vue de se redessiner avec "setNeedsDisplay".
  • DonioDonio Membre
    11:09 modifié #4


      Ouf, c'est deja pas mal. Merci.

    Mais mon souci c'est que si je remplace, par exemple le l_a et l_b, avec de valeurs...le dessin est fait au lancement de l'appli et n'on pas quand je click sur le bouton "dessiner".


  • DonioDonio Membre
    11:09 modifié #5
    Oui Schlum c'est encore moi. J'essaye j'essaye je persevere.. Je vais y arriver un jour.  :P :P

    Donc ce que tu veux dire :

    au l'encement du faitLeDessin je renvoi un message a la drawRect qui dit setNeedsDisplay=yes et dans la drawRect je fait un if qui dessine le rectangle avec les variable l_a et l_b si le setNeedsDisplay est yes?



  • schlumschlum Membre
    11:09 modifié #6
    dans 1228994601:

    Oui Schlum c'est encore moi. J'essaye j'essaye je persevere.. Je vais y arriver un jour.  :P :P

    Donc ce que tu veux dire :

    au l'encement du faitLeDessin je renvoi un message a la drawRect qui dit setNeedsDisplay=yes et dans la drawRect je fait un if qui dessine le rectangle avec les variable l_a et l_b si le setNeedsDisplay est yes?


    Non non ! SetNeedsDisplay:YES, c'est juste pour lui dire qu'elle devra se redessiner au prochain passage de la boucle du runtime  ;)
    Il faut que tu utilises une autre variable booléenne pour savoir s'il faut dessiner le rectangle ou non... (par exemple "shouldDrawRect")
    Donc tu feras d'abord "setShouldDrawRect:YES" puis "setNeedsDisplay:YES"
  • DonioDonio Membre
    11:09 modifié #7
    Donc :

    @implementation DessinView<br /><br />- (id)initWithFrame:(NSRect)frame {<br />&nbsp; &nbsp; self = [super initWithFrame:frame];<br />&nbsp; &nbsp; if (self) {<br />&nbsp; &nbsp; &nbsp; &nbsp; // Initialization code here.<br />&nbsp; &nbsp; }<br />&nbsp; &nbsp; return self;<br />}<br /><br />-(IBAction) faitLeDessin:(id)sender<br /><br />{&nbsp; &nbsp; float l_a, l_b;<br />	<br />	l_a=[largeur floatValue];<br />	l_b=[longuer floatValue];<br />setNeedsDisplay:YES<br />setShouldDrawRect:YES<br /><br />	<br />	<br />}<br />	<br />- (void)drawRect:(NSRect)rect {<br />&nbsp;  <br />	<br />IF (setShouldDrawRect ==YES)<br /><br />{		<br />	NSRect rect1 = NSMakeRect ( 21,21,l_a,l_b );<br />	[[NSColor blackColor] set];<br />	1NSFrameRectWithWidth ( rect1, 1 ); }<br />}<br />	<br /><br />@end
    


    C'est un peu bizarre c'est comme si on parle au code.

    Donc en fait le code du rectangle il est bien quelque part dans la nature et il attende qu'on lui dit : dessine le ?

    Si c'est comme ça alors dans le if de initWithFrame je dois mettre setShouldDrawRect:NO ?

  • AliGatorAliGator Membre, Modérateur
    11:09 modifié #8
    dans 1228996565:

    Non non ! SetNeedsDisplay:YES, c'est juste pour lui dire qu'elle devra se redessiner au prochain passage de la boucle du runtime  ;)
    Il faut que tu utilises une autre variable booléenne pour savoir s'il faut dessiner le rectangle ou non... (par exemple "shouldDrawRect")
    Donc tu feras d'abord "setShouldDrawRect:YES" puis "setNeedsDisplay:YES"
    Heu je suis d'accord, il ne faut pas appeler soi-même le drawRect on est bien d'accord... mais pourquoi utiliser un setShouldDrawRect ?

    Ou alors j'ai pas compris la demande, mais pour moi il y a une vue DessinView qui dessine un rectangle de taille AxB, et on a dans l'interface des champs (ou des sliders enfin peu importe) pour pouvoir changer les dimensions du rectangle... Quand on modifie ces valeurs puis que l'on clique sur un bouton "Changer !", cela va mettre à  jour le dessin du rectangle pour le redessiner avec les nouvelles valeurs. C'est ça ?

    Dans ce cas :
    - La classe DessinView (qui hérite de NSView) contient les deux dimensions largeur et hauteur du rectangle qu'elle a à  dessiner
    - La méthode drawRect, qui est appelée automatiquement lorsque Cocoa voit qu'il a besoin de dessiner la vue (ce n'est pas à  toi d'appeler drawRect manuellement, surtout pas) ou qu'on lui a indiqué "setNeedsDisplay:YES", dans son implémentation se contente de dessiner ton rectangle en utilisant les variables d'instance largeur et hauteur de DessinView
    - Il suffit de prévoir ensuite une méthode qui va mettre à  jour les variables largeur et hauteur de ta DessinView, et lui envoyer dans la foulée un "setNeedsDisplay:YES"

    Après pour la dernière étape, soit on met une IBAction dans DessinView qui va récupérer tout seul les valeurs dans les sliders avant de s'envoyer un setNeedsDisplay:YES, ça marche mais c'est un peu "direct", un peu trop lié à  l'interface et pas très MVC, soit on met l'IBAction dans un AppController (ton NSObject, cube bleu que tu as certainement créé dans IB) et c'est cet AppController qui va récupérer les valeurs de tes sliders (ou tes champs dans lesquels tu mets tes nouvelles dimensions), et les envoyer à  la méthode "setLargeur: andHauteur:" de dessinView que tu auras créé pour l'occasion (et qui va modifier la valeur de largeur et hauteur puis s'envoyer setNeedsDisplay)
  • schlumschlum Membre
    11:09 modifié #9
    Plutôt :

    @implementation DessinView<br /><br />- (id)initWithFrame:(NSRect)frame {<br /> &nbsp; &nbsp;self = [super initWithFrame:frame];<br /> &nbsp; &nbsp;if (self) {<br /> &nbsp; &nbsp; &nbsp; &nbsp;shouldDrawRect = NO;<br /> &nbsp; &nbsp;}<br /> &nbsp; &nbsp;return self;<br />}<br /><br />-(IBAction) faitLeDessin:(id)sender<br /><br />{ &nbsp; &nbsp;float l_a, l_b;<br />	<br />	l_a=[largeur floatValue];<br />	l_b=[longuer floatValue];<br /> &nbsp; &nbsp; &nbsp; &nbsp;[self setNeedsDisplay:YES];<br /> &nbsp; &nbsp; &nbsp; &nbsp;[self setShouldDrawRect:YES]<br />}<br />	<br />- (void)drawRect:(NSRect)rect {<br /> &nbsp; <br />	<br />if (shouldDrawRect)<br /><br />{		<br />	NSRect rect1 = NSMakeRect ( 21,21,l_a,l_b );<br />	[[NSColor blackColor] set];<br />	1NSFrameRectWithWidth ( rect1, 1 ); }<br />}<br />	<br /><br />@end
    


    En supposant que la vue joue également le rôle du contrôleur (ce qui est acceptable pour les petits trucs...)
    Et il manque le setter "setShouldDrawRect" qu'on te conseillera de faire avec un @synthetize  :P
    Il manque également le traitement de l_a, l_b qui doivent être passés de la même manière (ex : setRectWidth:height:)
  • AliGatorAliGator Membre, Modérateur
    décembre 2008 modifié #10
    @interface DessinView : NSView {<br />  float m_largeur;<br />  float m_hauteur;<br />}<br />-(void)setLargeur:(float)larg andHauteur:(float)haut;<br />@end<br /><br />@implementation DessinView<br /><br />-(void)setLargeur:(float)larg andHauteur:(float)haut {<br />	m_largeur = larg;<br />	m_hauteur = haut;<br />	[self setNeedsDisplay:YES];<br />}<br />	<br />- (void)drawRect:(NSRect)rect {<br />	NSRect rect1 = NSMakeRect ( 21,21,m_largeur,m_hauteur );<br />	[[NSColor blackColor] set];<br />	NSFrameRectWithWidth ( rect1, 1 );<br />}<br /><br />@end
    
    et pour ton AppController :
    @interface AppController : NSObject {<br />  IBOutlet NSTextField* largField; // connecté à  ton premier champ indiquant la largeur voulue<br />  IBOutlet NSTextField* hautField; // connecté à  ton deuxième champ indiquant la hauteur voulue<br />  IBOutlet DessinView* dessinView; // connecté à  ta vue DessinView dans ton interface<br />}<br />-(IBAction)applyNewSize:(id)sender; // connecté à  l&#39;action de ton bouton<br />@end<br /><br />@implementation AppController<br />-(IBAction)applyNewSize:(id)sender {<br />  // va demander à  dessinView de prendre en compte les nouvelles valeurs et de se redessiner dans la foulée avec le setNeedsDisplay:YES<br />  [dessinView setLargeur:[largField floatValue] andHauteur:[hautField floatValue]];<br />}<br />@end
    



    Je vois vraiment pas ce que le setShouldDrawRect vient faire là  dedans, schlum ??
    Justement la méthode drawRect n'est appelée que lorsque Cocoa voit qu'il est nécessaire de redessiner ton rectangle, soit parce qu'il a été masqué par une fenêtre que tu as mis au premier plan (et encore avec le backbuffering c'est pas vrai il ne redessine pas il garde en mémoire l'image dessinée) ou ce genre de cas géré automatiquement, soit parce que tu lui a explicitement dit que ton dessin n'était plus à  jour et qu'il doit le redessiner (en appelant tout seul drawRect lors de la future boucle de rendu mais ça tu le laisses faire) en appelant setNeedsDisplay.

    Donc tout ce mécanisme est déjà  géré automatiquement, pourquoi rajouter une couche (qui en plus ne marchera pas si jamais il n'y avait pas de doublebuffering de ta vue et qu'il avait à  appeler drawRect pour redessiner une partie du rectangle même s'il n'a pas changé de taille parce qu'il aurait été masqué entre temps ?)
  • schlumschlum Membre
    11:09 modifié #11
    dans 1229004246:

    Ou alors j'ai pas compris la demande, mais pour moi il y a une vue DessinView qui dessine un rectangle de taille AxB, et on a dans l'interface des champs (ou des sliders enfin peu importe) pour pouvoir changer les dimensions du rectangle... Quand on modifie ces valeurs puis que l'on clique sur un bouton "Changer !", cela va mettre à  jour le dessin du rectangle pour le redessiner avec les nouvelles valeurs. C'est ça ?


    Il veut qu'au démarrage il n'y ait aucun rectangle de dessiné... Il faut donc un "shouldDrawRect"
    Ou alors on suppose que quand l_a et l_b sont tous les eux nuls, on ne dessine pas le rectangle...
  • AliGatorAliGator Membre, Modérateur
    11:09 modifié #12
    dans 1229004776:

    dans 1229004246:

    Ou alors j'ai pas compris la demande, mais pour moi il y a une vue DessinView qui dessine un rectangle de taille AxB, et on a dans l'interface des champs (ou des sliders enfin peu importe) pour pouvoir changer les dimensions du rectangle... Quand on modifie ces valeurs puis que l'on clique sur un bouton "Changer !", cela va mettre à  jour le dessin du rectangle pour le redessiner avec les nouvelles valeurs. C'est ça ?


    Il veut qu'au démarrage il n'y ait aucun rectangle de dessiné... Il faut donc un "shouldDrawRect"
    Ou alors on suppose que quand l_a et l_b sont tous les eux nuls, on ne dessine pas le rectangle...
    Ah ok... mais avec ma solution (qui est il me semble la façon classique de faire, que tous les tutos précaunisent etc, non ?) c'est le cas déjà  !
    Bon éventuellement dans le drawRect je pourrais faire un test si m_largeur ou m_hauteur sont nuls, ne pas dessiner, mais ça n'optimiserai que de trois fois rien puisque les méthodes NSFrameRect... doivent déjà  gérer le cas où les dimensions du rectangle à  dessiner son nulles.

    Après j'ai pas rajouté mais il serait plus propre de déclarer une seule variable de type NSSize dans le DessinView (et de remplacer "setLargeur: andHauteur:" par "setRectSize:") mais bon c'est histoire de faire plus propre et de profiter qu'il existe une structure NSSize existante pour grouper les deux variables largeur et hauteur en une  ;)
Connectez-vous ou Inscrivez-vous pour répondre.