NSButtonCell dentro de NSCell personalizado
-
21-09-2019 - |
Pregunta
en mi aplicación cacao, necesito un NSCell personalizado para un NSTableView.Este La subclase NSCell contiene un NSButtonCell personalizado para manejar un clic (y dos o tres NSTextFieldCells para contenidos textuales).Encontrarás un ejemplo simplificado de mi código a continuación.
@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];
}
El problema con el que estoy atrapado es: ¿Cuál es la mejor/correcta forma de obtener ese botón (más precisamente:NSButtonCell) dentro de este NSCell para funcionar correctamente? "trabajo" significa:activa el mensaje de acción asignado y muestra la imagen alternativa cuando se hace clic.Fuera de la caja, el botón no hace nada cuando se hace clic.
Es difícil encontrar información/lecturas sobre este tema.Las únicas publicaciones que encontré en la red me indicaron cómo implementar
- (BOOL)trackMouse:(NSEvent *)theEvent inRect:(NSRect)cellFrame ofView:(NSView *)controlView untilMouseUp:(BOOL)untilMouseUp;
¿Es esta la forma correcta de hacerlo??? Implementar trackMouse:en mi NSCell que contiene? ¿Y luego reenviar el evento a NSButtonCell?Habría esperado que el propio NSButtonCell supiera qué hacer cuando se hace clic en él (y vi el trackMouse:métodos más en conjunto con el seguimiento real de los movimientos del mouse, no como una rueda de entrenamiento para el comportamiento de clic "estándar").Pero parece que no hace esto cuando se incluye en una celda...Parece que todavía no he comprendido el panorama general de las celdas personalizadas ;-)
Me alegraría que alguien pudiera responder esto (o indicarme algún tutorial o algo similar) a partir de su propia experiencia y decirme si estoy en el camino correcto.
Gracias de antemano, Tobi
Solución
Los requisitos mínimos son:
- Después izquierdo del ratón sobre el botón, debe aparecer presionado cada vez que el ratón está sobre ella.
- Si el ratón a continuación, acciona sobre el botón, la célula debe enviar el mensaje de acción apropiado.
Para hacer que el botón presionado, es necesario actualizar la propiedad highlighted
de la pila de botón según sea apropiado. Cambiar el estado por sí sola no va a lograr esto, pero lo que queremos es que el botón para destacar si, y sólo si, sus estados es NSOnState
.
Para enviar el mensaje de acción, es necesario tener en cuenta cuando se suelta el ratón, y luego usar -[NSApplication sendAction:to:from:]
para enviar el mensaje.
Con el fin de estar en condiciones de enviar estos mensajes, tendrá que enganchar en los métodos de seguimiento de eventos proporcionados por NSCell
. Tenga en cuenta que todos esos métodos de seguimiento, excepto el último método, -stopTracking:...
, devuelven un booleano para responder a la pregunta, "¿Quieres seguir recibiendo mensajes de seguimiento?"
El toque final es que, con el fin de ser enviados los mensajes de seguimiento en absoluto, es necesario implementar -hitTestForEvent:inRect:ofView:
y devolver una máscara de bits apropiada de los valores NSCellHit...
. En concreto, si el valor devuelto no tiene el valor NSCellHitTrackableArea
en ella, no recibirá los mensajes de seguimiento!
Por lo tanto, a un alto nivel, su aplicación se verá algo como:
- (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];
}
Otros consejos
El punto de NSCell
subclases es separar la responsabilidad de representar y controlar los elementos comunes de la interfaz de usuario (los controles) de la jerarquía visual y de eventos
Responsabilidades de la NSView
clases.Este emparejamiento permite que cada uno proporcione mayor especialización y variabilidad sin sobrecargar al otro.Mira la gran cantidad de NSButton
instancias que uno puede crear en Cocoa.Imagínate el número de NSButton
¡subclases que existirían si esta división en la funcionalidad estuviera ausente!
Uso del lenguaje de patrones de diseño para describir los roles:un NSControl
actúa como una fachada, ocultando detalles de su composición a sus clientes y transmitiendo eventos y transmitiendo mensajes a sus NSCell
instancia que actúa como delegado.
Porque su NSCell
subclase incluye otros NSCell
instancias de subclase dentro de su composición, ya no reciben directamente estos mensajes de eventos del NSControl
instancia que está en la jerarquía de vistas.Por lo tanto, para que estas instancias de celda reciban mensajes de eventos de la cadena de respuesta de eventos (de la jerarquía de vistas), su instancia de celda debe transmitir esos eventos relevantes.Estás recreando la obra del NSView
jerarquía.
Esto no es necesariamente algo malo.Replicando el comportamiento de NSControl
(y es NSView
superclase) pero en un NSCell
formulario, puede filtrar los eventos transmitidos a sus subceldas por ubicación, tipo de evento u otros criterios.El inconveniente es replicar el trabajo de NSView/NSControl
en la construcción del mecanismo de filtrado y gestión.
Entonces, al diseñar su interfaz, debe considerar si el NSButtonCell
(y NSTextFieldCell
s) están mejor en NSControl
s en la jerarquía de vista normal, o como subceldas en su NSCell
subclase.Es mejor aprovechar la funcionalidad que ya existe en una base de código que reinventarla (y continuar manteniéndola más adelante) innecesariamente.