NSButtonCell all'interno di NSCell personalizzato
-
21-09-2019 - |
Domanda
nella mia applicazione per il cacao, ho bisogno di un NSCell personalizzato per un NSTableView.Questo La sottoclasse NSCell contiene un NSButtonCell personalizzato per gestire un clic (e due o tre NSTextFieldCells per i contenuti testuali).Di seguito troverai un esempio semplificato del mio codice.
@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];
}
Il problema con cui sono bloccato è: qual è il modo migliore/giusto per ottenere quel pulsante (più precisamente:il NSButtonCell) all'interno di questo NSCell per funzionare correttamente? "lavoro" significa:attivare il messaggio di azione assegnato e mostrare l'immagine alternativa quando si fa clic.Per impostazione predefinita, il pulsante non fa nulla quando viene cliccato.
Informazioni/letture su questo argomento sono difficili da trovare.Gli unici post che ho trovato in rete mi hanno indirizzato all'implementazione
- (BOOL)trackMouse:(NSEvent *)theEvent inRect:(NSRect)cellFrame ofView:(NSView *)controlView untilMouseUp:(BOOL)untilMouseUp;
È questo il modo corretto per farlo??? Implementa trackMouse:nel mio NSCell contenente? E poi inoltrare l'evento a NSButtonCell?Mi sarei aspettato che lo stesso NSButtonCell sapesse cosa fare quando viene cliccato (e ho visto il trackMouse:metodi più in combinazione con il monitoraggio reale dei movimenti del mouse - non come una ruota di allenamento per il comportamento dei clic "standard").Ma sembra che non lo faccia quando è incluso in una cella stessa...Sembra che non abbia ancora colto il quadro generale delle celle personalizzate ;-)
Sarei felice se qualcuno potesse rispondere a questa domanda (o indicarmi qualche tutorial o simili) in base alla propria esperienza - e dirmelo se sono sulla strada giusta.
Grazie in anticipo, Tobi.
Soluzione
I requisiti minimi sono:
- Dopo aver premuto il pulsante sinistro del mouse sul pulsante, questo deve apparire premuto ogni volta che il mouse si trova sopra di esso.
- Se il mouse viene rilasciato sul pulsante, il tuo cellulare deve inviare il messaggio di azione appropriato.
Per far sì che il pulsante sembri premuto, è necessario aggiornare la cella del pulsante highlighted
proprietà a seconda dei casi.Cambiare lo stato da solo non porterà a questo risultato, ma quello che vuoi è che il pulsante venga evidenziato se, e solo se, il suo stato è NSOnState
.
Per inviare il messaggio di azione, è necessario essere consapevoli di quando il mouse viene rilasciato e quindi utilizzarlo -[NSApplication sendAction:to:from:]
per inviare il messaggio.
Per poter inviare questi messaggi, dovrai agganciarti ai metodi di tracciamento degli eventi forniti da NSCell
.Tieni presente che tutti questi metodi di tracciamento, tranne quello finale, -stopTracking:...
metodo, restituisce un valore booleano per rispondere alla domanda "Vuoi continuare a ricevere i messaggi di tracciamento?"
La svolta finale è che, per poter ricevere qualsiasi messaggio di tracciamento, è necessario implementare -hitTestForEvent:inRect:ofView:
e restituire una maschera di bit appropriata di NSCellHit...
valori.Nello specifico, se il valore restituito non ha l'estensione NSCellHitTrackableArea
valore in esso, non riceverai alcun messaggio di tracciamento!
Quindi, ad alto livello, la tua implementazione sarà simile a:
- (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];
}
Altri suggerimenti
Il punto di NSCell
sottoclassi è quello di separare la responsabilità per il rendering e la gestione di elementi comuni dell'interfaccia utente (i controlli) dalla visual- ed eventi-gerarchia
responsabilità della NSView
classi.Questo abbinamento consente a ciascuno di fornire maggiore specializzazione e variabilità senza gravare sull'altro.Guarda il gran numero di NSButton
istanze che si possono creare in Cocoa.Immagina il numero di NSButton
sottoclassi che esisterebbero se questa suddivisione delle funzionalità fosse assente!
Utilizzando il linguaggio del design pattern per descrivere i ruoli:UN NSControl
agisce come una facciata, nascondendo i dettagli della sua composizione ai suoi clienti e trasmettendo eventi e trasmettendo messaggi ai suoi NSCell
istanza che funge da delegato.
Perché il tuo NSCell
la sottoclasse include altro NSCell
istanze della sottoclasse all'interno della sua composizione, non ricevono più direttamente questi messaggi di evento da NSControl
istanza che si trova nella gerarchia della vista.Pertanto, affinché queste istanze di cella ricevano messaggi di eventi dalla catena di risponditori di eventi (della gerarchia di visualizzazione), l'istanza di cella deve trasmettere quegli eventi rilevanti.Stai ricreando il lavoro di NSView
gerarchia.
Questa non è necessariamente una cosa negativa.Replicando il comportamento di NSControl
(e il suo NSView
superclasse) ma in an NSCell
modulo, puoi filtrare gli eventi trasmessi alle tue sottocelle per posizione, tipo di evento o altri criteri.Lo svantaggio è replicare il lavoro di NSView/NSControl
nella costruzione del meccanismo di filtraggio e gestione.
Quindi, nel progettare la tua interfaccia, devi considerare se il file NSButtonCell
(E NSTextFieldCell
s) stanno meglio dentro NSControl
s nella normale gerarchia di visualizzazione o come sottocelle nel tuo NSCell
sottoclasse.È meglio sfruttare la funzionalità che già esiste in una codebase piuttosto che reinventarla (e continuare a mantenerla in seguito) inutilmente.