NSButtonCell intérieur NSCell custom
-
21-09-2019 - |
Question
dans ma demande de cacao, je besoin d'un NSCell personnalisé pour un NSTableView. Cette sous-classe NSCell contient une coutume NSButtonCell pour la manipulation d'un clic (et deux ou trois NSTextFieldCells pour le contenu textuel). Vous trouverez un exemple simplifié de mon code ci-dessous.
@implementation TheCustomCell
- (void)drawWithFrame:(NSRect)cellFrame inView:(NSView *)controlView {
// various NSTextFieldCells
NSTextFieldCell *titleCell = [[NSTextFieldCell alloc] init];
....
// my custom NSButtonCell
MyButtonCell *warningCell = [[MyButtonCell alloc] init];
[warningCell setTarget:self];
[warningCell setAction:@selector(testButton:)];
[warningCell drawWithFrame:buttonRect inView:controlView];
}
Le problème que je suis coincé avec est: quelle est la meilleure / bonne façon d'obtenir ce bouton (plus précisément: le NSButtonCell)? Intérieur de ce NSCell fonctionne correctement « travail » signifie: déclenchement le message d'action assigné et lui montrer l'autre image lorsque vous cliquez dessus. Hors de la boîte, le bouton ne fait rien quand on clique dessus.
Informations / lectures sur ce sujet est difficile à trouver. Les seuls postes que j'ai trouvé sur le net m'a fait de mettre en œuvre
- (BOOL)trackMouse:(NSEvent *)theEvent inRect:(NSRect)cellFrame ofView:(NSView *)controlView untilMouseUp:(BOOL)untilMouseUp;
Est-ce la bonne façon de le faire ??? Mettre en œuvre trackMouse: dans mon contenant NSCell Et puis transmettre l'événement au NSButtonCell? Je me serais attendu à la NSButtonCell lui-même pour savoir quoi faire quand il clique dessus (et je vis les trackMouse: méthodes plus cunjunction avec le suivi des mouvements de la souris vraiment - non pas comme une roue de formation pour les « standard » cliquez sur le comportement). Mais il semble que cela ne fait pas lorsqu'il est inclus dans une cellule elle-même ... Il semble que je ne l'ai pas saisi la grande image sur les cellules personnalisées, encore; -)
Je serais heureux si quelqu'un pourrait répondre à cette (ou me pointer vers un certain tutoriel ou similaires) de sa propre expérience - et me dire si je suis sur la bonne voie <. / p>
Merci d'avance, Tobi
La solution
Les exigences minimales sont les suivantes:
- Après gauche de la souris sur le bouton, il doit apparaître pressé chaque fois que la souris est dessus.
- Si la souris libère alors sur le bouton, votre cellule doit envoyer le message d'action approprié.
Pour le look bouton pressé, vous devez mettre à jour la propriété highlighted
de pile bouton selon le cas. Modification de l'état seul ne sera pas accomplir cela, mais ce que vous voulez pour le bouton pour être mis en évidence si, et seulement si, ses états est NSOnState
.
Pour envoyer le message d'action, vous devez être au courant lorsque la souris est relâché, et ensuite utiliser -[NSApplication sendAction:to:from:]
pour envoyer le message.
Pour être en mesure d'envoyer ces messages, vous devrez connecter dans les méthodes de suivi des événements fournis par NSCell
. Notez que toutes ces méthodes de suivi, à l'exception de la dernière méthode, -stopTracking:...
, renvoient un booléen pour répondre à la question: « Voulez-vous continuer à recevoir des messages de suivi? »
La torsion finale est que, pour être envoyé des messages de suivi du tout, vous devez implémenter -hitTestForEvent:inRect:ofView:
et retourner un bitmask approprié des valeurs de NSCellHit...
. Plus précisément, si la valeur retournée n'a pas la valeur de NSCellHitTrackableArea
en elle, vous ne recevez des messages de suivi!
Alors, à un niveau élevé, votre implémentation ressemblera à quelque chose comme:
- (NSUInteger)hitTestForEvent:(NSEvent *)event
inRect:(NSRect)cellFrame
ofView:(NSView *)controlView {
NSUInteger hitType = [super hitTestForEvent:event inRect:cellFrame ofView:controlView];
NSPoint location = [event locationInWindow];
location = [controlView convertPointFromBase:location];
// get the button cell's |buttonRect|, then
if (NSMouseInRect(location, buttonRect, [controlView isFlipped])) {
// We are only sent tracking messages for trackable areas.
hitType |= NSCellHitTrackableArea;
}
return hitType;
}
+ (BOOL)prefersTrackingUntilMouseUp {
// you want a single, long tracking "session" from mouse down till up
return YES;
}
- (BOOL)startTrackingAt:(NSPoint)startPoint inView:(NSView *)controlView {
// use NSMouseInRect and [controlView isFlipped] to test whether |startPoint| is on the button
// if so, highlight the button
return YES; // keep tracking
}
- (BOOL)continueTracking:(NSPoint)lastPoint at:(NSPoint)currentPoint inView:(NSView *)controlView {
// if |currentPoint| is in the button, highlight it
// otherwise, unhighlight it
return YES; // keep on tracking
}
- (void)stopTracking:(NSPoint)lastPoint at:(NSPoint)stopPoint inView:(NSView *)controlView mouseIsUp:(BOOL)flag {
// if |flag| and mouse in button's rect, then
[[NSApplication sharedApplication] sendAction:self.action to:self.target from:controlView];
// and, finally,
[buttonCell setHighlighted:NO];
}
Autres conseils
Le point de sous-classes NSCell
est de séparer la responsabilité pour le rendu et la manipulation des éléments de l'interface utilisateur commune (les contrôles) de la visuo et la hiérarchie des événements
responsabilités des classes NSView
. Cette association permet à chacun de fournir une plus grande spécialisation et à la variabilité sans surcharger l'autre. Regardez le grand nombre d'instances de NSButton
on peut créer à Cocoa. Imaginez le nombre de sous-classes de NSButton
qui existerait si cette scission fonctionnalité étaient absents!
En utilisant un langage de modèle de conception pour décrire les rôles:. Un agit NSControl
comme une façade, cachant les détails de sa composition de ses clients et des événements de passage et le rendu des messages à son instance de NSCell
qui agit en tant que délégué
Parce que votre sous-classe de NSCell
comprend d'autres instances de sous-classe de NSCell
dans sa composition, ils ne reçoivent plus directement ces messages d'événement de l'instance NSControl
qui est dans la hiérarchie de la vue. Ainsi, pour que ces instances de cellules pour recevoir des messages d'événement de la chaîne de répondeur d'événements (de la hiérarchie de la vue), votre instance de cellule doit transmettre ces événements pertinents. Vous recréez le travail de la hiérarchie NSView
.
Ce n'est pas nécessairement une mauvaise chose. En reproduisant le comportement de NSControl
(et sa superclasse NSView
), mais sous une forme de NSCell
, vous pouvez filtrer les événements transmis à vos sous-cellules par emplacement, type d'événement, ou d'autres critères. L'inconvénient est reproduit le travail de NSView/NSControl
dans la construction du mécanisme de filtrage et de gestion.
Ainsi, dans la conception de votre interface, vous devez examiner si le NSButtonCell
(et NSTextFieldCell
s) sont mieux en NSControl
s dans la hiérarchie de la vue normale, ou en tant que sous-cellules dans votre sous-classe NSCell
. Il est préférable de tirer parti de la fonctionnalité qui existe déjà pour vous dans une base de code que de réinventer (et continuer à maintenir ultérieurement) inutilement.