2010-10-12 10 views
6

Estoy intentando crear una subclase NSSlider para crear un control llamado jog dial. Básicamente lo que necesito es un control deslizante que siempre comienza en el medio y cuando se mueve hacia la izquierda o hacia la derecha enviará notificaciones de vez en cuando (determinado por un atributo que puede establecerse) informando a su contenedor de su valor actual, luego cuando dejarte ir por la perilla, volverá al medio. Esperaba implementar la funcionalidad para devolver el control deslizante al centro y dejar de enviar notificaciones en el evento mouseUp del control deslizante pero parece que, por alguna razón, Apple desactiva el evento MouseUp después de un evento mouseDown en el control deslizante y maneja toda la funcionalidad del control deslizante en un nivel inferior ¿Hay alguna forma de recuperar el evento mouseUp? Si no, ¿alguien puede sugerir una solución razonable?Subclase NSSlider: necesita una solución alternativa para los eventos faltantes del mouse (Cocoa OSX)

Respuesta

5

Siempre que detecte que la aplicación de una superclase de mouseDragged: o mouseUp: no está recibiendo llamadas, es más probable debido a la aplicación de mouseDown: de la clase entra en un bucle de seguimiento. Esto es ciertamente cierto para muchas subclases NSControl incluyendo NSSlider.

Una mejor forma de detectar un mouse es subclase de la celda e ignorar el método de seguimiento apropiado. En este caso, probablemente desee - (void)stopTracking:(NSPoint)lastPoint at:(NSPoint)stopPoint inView:(NSView *)controlView mouseIsUp:(BOOL)flag, pero las variantes startTracking: y continueTracking: también pueden resultar útiles para lo que está intentando hacer.

6

Hay un truco que utilizo (pero no inventé) para tales situaciones. Primero, en IB, designe el control deslizante como "continuo", de modo que obtendrá mensajes de acción a medida que se mueve el control deslizante. Luego, en el método de acción, hacer esto:

[NSObject cancelPreviousPerformRequestsWithTarget: self 
    selector: @selector(finishTrack) object: nil ]; 
[self performSelector: @selector(finishTrack) withObject: nil 
    afterDelay: 0.0]; 

Después de soltar el ratón, se llamará al método finishTrack.

3

Parece que la idea de Kperryua proporcionaría la solución más limpia, así que lo marcaré como la respuesta aceptada, pero terminé usando un truco que funcionó para mi situación específica, así que pensé que podría compartir eso también.

La aplicación que estoy creando necesita ser multiplataforma, así que estoy usando Cocotron (un proyecto de código abierto que implementa gran parte de Cocoa API en Windows) para lograr este objetivo y cocotron no es compatible con stopTracking: ... método mencionado anteriormente.

Afortunadamente, mientras jugábamos con un pequeño programa de prueba, descubrimos que si anula el método MouseSnow del NSSlider y llama al súper de ese método, no regresa hasta que se suelta el botón del mouse, entonces solo puede poner el código que debe estar en mouseUp dentro del método mouseDown después de la llamada a super. Esto es un poco sucio, pero parece ser la única solución que funcionará en nuestro caso, así que vamos a tener que ir con eso.

6

Esto funciona para mí (y es más fácil que la subclasificación NSSlider):

- (IBAction)sizeSliderValueChanged:(id)sender { 
    NSEvent *event = [[NSApplication sharedApplication] currentEvent]; 
    BOOL startingDrag = event.type == NSLeftMouseDown; 
    BOOL endingDrag = event.type == NSLeftMouseUp; 
    BOOL dragging = event.type == NSLeftMouseDragged; 

    NSAssert(startingDrag || endingDrag || dragging, @"unexpected event type caused slider change: %@", event); 

    if (startingDrag) { 
     NSLog(@"slider value started changing"); 
     // do whatever needs to be done when the slider starts changing 
    } 

    // do whatever needs to be done for "uncommitted" changes 
    NSLog(@"slider value: %f", [sender doubleValue]); 

    if (endingDrag) { 
     NSLog(@"slider value stopped changing"); 
     // do whatever needs to be done when the slider stops changing 
    } 
} 
+1

esto no funciona en todos los casos (comience a arrastrar y vaya al menú, nunca registrará la terminaciónDrag) –

0

Esto se puede hacer solamente por subclases (como métodos de @mprudhom no funcionarán al 100% para la liberación know)

para el inicio de la resistencia al avance, es necesario coger becomeFirstResponder (para ello es necesario para proporcionar también needsPanelToBecomeKey)

para el fin de arrastrar, necesita subclase mouseDown: (la llamada mouseDown, pero se pone la calle d cuando la perilla liberado)

Nota: este enfoque hará que su deslizador del primer nivel de respuesta, la cancelación de cualquier otra corriente de primer nivel de respuesta

Nota: aproximación desde mprudhom

@implementation CustomSlider 

- (void)mouseDown:(NSEvent *)theEvent { 
    [super mouseDown:theEvent]; 
    NSLog(@"OK"); 
} 

- (BOOL)needsPanelToBecomeKey { 
    [super needsPanelToBecomeKey]; 
    return YES; 
} 

- (BOOL)becomeFirstResponder { 
    [super becomeFirstResponder]; 
    NSLog(@"Became first responder."); 
    return YES; 
} 

@end 
1

Por si alguien se pregunta cómo para lograr esto en Swift 3 con un NSSlider eso es estado se establece en continuo:

@IBOutlet weak var mySlider: NSSlider! 

... 

@IBAction func handlingNSSliderChanges(_ sender: Any) { 

    // Perform updates on certain events 
    if sender is NSSlider{ 
     let event = NSApplication.shared().currentEvent 

     if event?.type == NSEventType.leftMouseUp { 
      print("LeftMouseUp event inside continuous state NSSlider") 
     } 
    } 

    // Do continuous updates 
    if let value = mySlider?.integerValue{ 
     demoLabel.stringValue = String(value) 
    } 
} 

... 
0

que tenían una necesidad similar, pero para un control deslizante en una NSTouchBar . Utilicé un enfoque similar "rápido y sucio" como Marc Prud'hommeaux y Martin Majewski, eventos ligeramente diferentes para inspeccionar. Aquí está el código Swift que le permite rastrear tanto el valor continuo como el valor final del control deslizante en una barra táctil.

func sliderValueChanged(_ sender: NSSlider) { 
    let doubleValue = sender.doubleValue 

    Swift.print("Continuous value: \(doubleValue)") 

    // find out if touch event has ended 
    let event = NSApplication.shared().currentEvent 
    if event?.type == NSEventType.directTouch { 
     if let endedTouches = event?.touches(matching: .ended, in: nil) { 
      if (endedTouches.count > 0) { 
       Swift.print("The final value was: \(doubleValue)") 
      } 
     } 
    } 
} 
Cuestiones relacionadas