Affichage NSButton
J'ai deux problèmes à soumettre à un développeur sérieux qui maitrise bien Cocoa.
Je développe avec Xcode et Interface Builder à l'aide de Cocoa en Objective-C sous 10.3.9.
La situation est simple. Je dispose d'une palette de contrôles (identique à celle d' Interface Builder). Ces contrôles sont matérialisés par des objets NSImageView désactivés afin de récupérer la localisation lors d'un clic.
Ces objets sont déplaçables vers une fenêtre qui est l'éditeur de l'interface, comme pour Interface Builder.
La plupart des objets ne pose aucun problème à l'exception de ceux de la classe NSButton et plus particulièrement ceux dont le paramétrage est le suivant
• ButtonType = NSMomentaryPushInButton & BezelStyle = NSRoundedBezelStyle
Il s'agit du bouton classique qui est bleuté lorsqu'il est par défaut.
• ButtonType = NSMomentaryPushInButton & BezelStyle = NSRegularSquareBezelStyle
Il s'agit du bouton permettant l'insertion d'un texte et/ou d'un image.
Le problème réside dans l'affichage. En effet, les bordures sont décalées par rapport au reste du bouton.
D'autre part, lorsqu'un objet de ce type est glissédéposé dans la fenêtre (plutôt la contentView, qui une instance d'une classe dérivée de NSView !). Son affichage est incorrecte. Par contre lorsqu'il est déplacé dans l'éditeur, il reprends sa forme correcte. Ce comportement est valable pour les deux types de bouton.
Le second problème concerne l'affichage des poignées pour redimensionner les objets.
Les poignées ne sont pas correctement dessinées. Les parties masquées sont celles qui débordent sur le contrôles.
J'ai adapté le code de l'application Sketch fournie avec les outils de développement.
N'hésitez pas à me contacter pour tout détail supplémentaire.
Un grand merci à celle ou celui qui m'aidera.
Je développe avec Xcode et Interface Builder à l'aide de Cocoa en Objective-C sous 10.3.9.
La situation est simple. Je dispose d'une palette de contrôles (identique à celle d' Interface Builder). Ces contrôles sont matérialisés par des objets NSImageView désactivés afin de récupérer la localisation lors d'un clic.
Ces objets sont déplaçables vers une fenêtre qui est l'éditeur de l'interface, comme pour Interface Builder.
La plupart des objets ne pose aucun problème à l'exception de ceux de la classe NSButton et plus particulièrement ceux dont le paramétrage est le suivant
• ButtonType = NSMomentaryPushInButton & BezelStyle = NSRoundedBezelStyle
Il s'agit du bouton classique qui est bleuté lorsqu'il est par défaut.
• ButtonType = NSMomentaryPushInButton & BezelStyle = NSRegularSquareBezelStyle
Il s'agit du bouton permettant l'insertion d'un texte et/ou d'un image.
Le problème réside dans l'affichage. En effet, les bordures sont décalées par rapport au reste du bouton.
D'autre part, lorsqu'un objet de ce type est glissédéposé dans la fenêtre (plutôt la contentView, qui une instance d'une classe dérivée de NSView !). Son affichage est incorrecte. Par contre lorsqu'il est déplacé dans l'éditeur, il reprends sa forme correcte. Ce comportement est valable pour les deux types de bouton.
Le second problème concerne l'affichage des poignées pour redimensionner les objets.
Les poignées ne sont pas correctement dessinées. Les parties masquées sont celles qui débordent sur le contrôles.
J'ai adapté le code de l'application Sketch fournie avec les outils de développement.
N'hésitez pas à me contacter pour tout détail supplémentaire.
Un grand merci à celle ou celui qui m'aidera.
Connectez-vous ou Inscrivez-vous pour répondre.
Réponses
En tous cas, bienvenue sur ce forum. Allez, hop, tournée générale ...
L'opération permettant l'installation d'un objet visuel dans la fenêtre consiste à glisser/déposer une image NSImageView. Le presse-papier contient une chaà®ne détenant le type précis de l'objet à construire dans la fenêtre.
Ainsi lorsque le grag&drop s'est déroulé correctement, la dernière méthode invoquée est la suivante:
//**********************************************************************************************************
// concludeDragOperation
//**********************************************************************************************************
-(void)concludeDragOperation:(id <NSDraggingInfo>)sender
{
[self clearSelection]; // Supprime l'ancienne selection (poignees des autres controles)
[self createGraphic:_newControlType with:sender]; // Construit le controle
[_newControlType release]; // Supprime la copie de la donnes déterminée par le presse papier
} /* concludeDragOperation */
Voici le code qui installe le bouton dans la vue éditrice
//**********************************************************************************************************
// createGraphic
//**********************************************************************************************************
-(void)createGraphic:(NSString*)inImageName with:(id <NSDraggingInfo>)inSender
{
NSRect rect;
// 1- Deselectionne tout
[self invalidateGraphics:_graphics];
// 2- Calcul de la taille du controle dans la vue de depot
rect.origin = [self convertPoint:[inSender draggedImageLocation] toView:nil];
rect.size = [[inSender draggedImage] size];
rect.origin.y -= rect.size.height;
rect = NSInsetRect(rect,-1,-1);
// 3- Creation du controle
if ([inImageName compare:@Button] == NSOrderedSame)
_creatingGraphic = [self createCXButton:rect withButtonType:NSMomentaryPushInButton withBezelStyle:NSRoundedBezelStyle];
etc...
La classe CXButton est dérivée de NSButton. Elle apporte des modifications au comportement des boutons.
Voici le code complet de la méthode init en creation.
//**********************************************************************************************************
// initWithFrame
//**********************************************************************************************************
-(id)initWithFrame:(NSRect)inFrame withButtonType:(NSButtonType)inType withBezelStyle:(NSBezelStyle)inStyle
{
if ((inType == NSRadioButton) || (inType == NSSwitchButton))
inFrame.size.width += 50; // pour le texte
self = [super initWithFrame:inFrame];
if (self)
{
_name = nil;
_controller = nil;
_manipulatingFrame = NO;
_Frame = inFrame;
_origFrame = NSZeroRect;
_superviewSaved = nil;
_buttonType = inType;
[self setButtonType:inType];
if (inStyle != NoBezelStyle)
[self setBezelStyle:inStyle];
}
return self;
} /* initWithFrame */
D'autre part, la super vue du bouton implémente la méthode suivante:
//**********************************************************************************************************
// drawRect
//**********************************************************************************************************
-(void)drawRect:(NSRect)rect
{
unsigned i;
NSView* curGraphic;
BOOL isSelected;
NSRect drawingFrame;
NSGraphicsContext* currentContext = [NSGraphicsContext currentContext];
// 1- Gestion de l'apparence pour le drag/drop
if (_dragOperation)
{
if (_highlighted)
{
[[NSColor lightGrayColor] set];
NSFrameRectWithWidth(rect,3.0);
}
}
// 2- Gestion de l'affichage de la grille magnétique
if ([gApp showGrid])
DrawGridWithSettingsInRect([gApp spaceGrid], [NSColor lightGrayColor], rect, NSZeroPoint);
i = [_graphics count];
while (i-- > 0)
{
curGraphic = [_graphics objectAtIndex:i];
// frame de l'objet visuel + taille des poignées (les poignées sont placées à cheval sur l'objet)
drawingFrame = [curGraphic drawingFrame];
if (NSIntersectsRect(rect, drawingFrame))
{
if (! _gvFlags.knobsHidden)
{
// Figure out if we should draw selected.
isSelected = [self graphicIsSelected:curGraphic];
// Account for any current rubberband selection state
if (_rubberbandGraphics && (isSelected == _gvFlags.rubberbandIsDeselecting) && [_rubberbandGraphics containsObject:curGraphic])
isSelected = (isSelected ? NO : YES);
}
else
// Do not draw handles on graphics that are editing.
isSelected = NO;
// **** Je ne comprend rien en ce code, les contextes graphiques ?
[currentContext saveGraphicsState];
[curGraphic setNeedsDisplayInRect:drawingFrame]; // L'affichage du bouton s'effectue en invoquant drawRect du controle
[currentContext restoreGraphicsState];
if (isSelected)
[curGraphic drawHandlesInView:self];
}
}
// Dessine la zone de selection si elle existe
if (! NSEqualRects(_rubberbandRect, NSZeroRect))
{
[[NSColor knobColor] set];
NSFrameRect(_rubberbandRect);
}
} /* drawRect */
La plupart du code provient de l'application sketch.
Je pense que tu dispose de toutes les pièces. Si tu veux éclaircir un point particulier, pas de problème.
Par avance, merci beaucoup.
A vrai dire j'ai le souvenir d'avoir déjà eut ce problème la une fois (le decalage d'une partie du bouton) mais je ne me souviens pas quand.
Par contre, j'ai essayé de créer un bouton "standard" par le code :
et ca marche très bien. Ce que je te propose donc tout d'abord, c'est de "laisser tomber" ta sous-classe CXButton et créer tes boutons "directement" dans la méthode createGraphics (au fait, pour tester la chaine inImageName, je te conseille d'utiliser isEqualTo: ou isEqualToString: plutot que compare: ) et tu regardes si ca marche.
Petite Explication des contextes graphiques :
Tu dois voir ca un peu comme des boites dans lesquelles tu vas imposer certains comportements (la couleur, le mode de fusion, l'ombre, ...). Ce sont des boites que tu "encastres" les unes dans les autres. Lorsque tu fais saveGraphicsContext, tu vas créer cette boite ; puis tu vas faire des appels du genre [[NSColor blueColor] set]; - tout ce qui va modifier les propriétées du dessin que tu vas effectuer - puis appeler tes procédures de dessin ([NSBezierPath fillRect:rect] par exemple) et enfin, lorsque tu auras terminé avec ces dessins nécessitant toutes ces propriétés, tu vas appeler restoreGraphicsContext pour revenir au status quo. Pour revenir aux boites, une boite donnée ne sait pas ce qu'il se passe dans les boites qu'elle contient. Ainsi saveGraphicsContext ajoute une boite sur la pile alors que restoreGraphicsContext l'enlève. Ce qu'il faut savoir c'est que les opérations de dessin seront toujours faites avec les propriétés de la boite qui est au dessus de la pile.
Enfin, un dernier petit truc : setNeedsDisplayInRect: comme setNeedsDisplay: impliquent certes des appels à drawRect: mais en différé (tu ne sais pas quand ils auront lieu, mais ils auront lieu avant la fin de la run loop). Ainsi ici, les encadrer de save/restoreGraphicsContext ne sert à rien - il vaut mieux faire ces appels dans le drawRect:
En ce qui concerne ton code, je l'avais déjà essayé. Le résultat montre un bouton correctement dessiné.
Une remarque sur la bizarrerie du défaut:
Lorsque le bouton est déplacé sans l'activation du magnétisme, en fait le déplacement s'effectue au premier pixel, l'apparence reste inchangée. Dans le cas contraire, le bouton redevient normal ! C'est incompréhensible.
J'ai même essayé de simuler un clic (mouseDown par NSEvent) sur le bouton nouvellement créé, aucun résultat.
Par contre en ce qui concerne la sous classe. Elle est nécessaire pour les raisons suivantes:
1°) Elle modifie le comportement du bouton lorsqu'il est considéré dans le mode conceptuel. C'est à dire lorsque le clic se traduit par l'affichage des poignées sans modification de l'apparrence du bouton en question.
2°) Le mode conceptuel implique l'ajout de données membres pour le gérer. Par contre pour supporter ce dispositif intégralement , j'ai déclaré une catégorie sur NSView afin que chaque contrôle déposé dans la vue réponde correctement.
3°) Le fonctionnement classique proposé par Cocoa ne permet que l'usage du couple (target,action). Il n'est pas possible d'implémenter les autres méthodes événementielles telles que mouseDown, etc., héritées de NSResponder. Le projet doit fournir tous les événements disponibles même si la plupart d'entre eux seront ignorés.
Pour le reste, merci de ton explication, je vais cogiter pour l'essayer.
A+
[edit] Oui ca doit être ca puisque, lorsque je modifie un peu mon code en faisant initWithFrame:NSMakeRect(30,30.5,100,30) ca me donne la même chose que toi
Chapeau, tu as trouvé! Les deux controles qui posaient problèmes sont désormais affichés correctement.
Très bien, et encore félicitation pour ton aide. Je n'aurais jamais trouvé car j'ai toujours raisonné au niveau du pixel, qui est par essence non fractionnable ! Je suis assez nouveau en Cocoa. J'ai longtemps utilisé PowerPlant de Metrowerks !
Il faut en effet, extraire les parties entières des coorddonnées de la structure NSRect comme le montre le code:
A propos de pixels à valeur non entière, on peut mentionner un comportement intéressant lorsque l'on fait des dessins : mettons que l'on veuille dessiner une droite horizontale ou vertical avec des NSBezierPath et d'épaisseur 1 px, et que l'on veut qu'elle fasse réellement 1px à l'écran, il faut alors donner des coordonnés du style (x1, 2.5)-(x2, 10.5) pour une droite horizontale. En effet, sinon l'anti-aliasing fera son oeuvre et ca donne des trucs très moches ...
Au plaisir de te revoir bientôt ...
Comme quoi on retrouve toujours les mêmes problèmes