NSSlider le sous-classement: Besoin d'une solution de contournement pour manque souris vers le haut des événements (Cocoa OSX)
Question
Je suis en train de sous-classe NSSlider pour créer un contrôle appelé molette de défilement. Fondamentalement, ce que j'ai besoin est un curseur qui commence toujours au milieu et quand il est déplacé vers la gauche ou la droite, il envoie des notifications tous si souvent (déterminée par un un attribut peut définir) informer son récipient de sa valeur actuelle, lorsque vous vous lâchez le bouton, il retournera au milieu. J'espérais mettre en œuvre la fonctionnalité pour ramener le curseur aux notifications moyen et l'envoi d'arrêt en cas mouseUp du curseur, mais il semble que, pour une pomme raison désactive l'événement MouseUp après un événement mouseDown sur le curseur et les poignées de toutes les fonctionnalités de curseur à un niveau inférieur. Y at-il de toute façon je peux obtenir le retour de l'événement mouseUp? Sinon quelqu'un peut-il suggérer une solution raisonnable?
La solution
Chaque fois que vous remarquez que n'est pas appelé la mise en œuvre de mouseDragged:
ou mouseUp:
d'une superclasse, il est plus probable que la mise en œuvre de la classe de mouseDown:
entre dans une boucle de suivi. Cela est certainement vrai de nombreuses sous-classes de NSControl
y compris NSSlider
.
Une meilleure façon de détecter une souris vers le haut est la sous-classe de la cellule et remplacer la méthode de suivi approprié. Dans ce cas, vous voulez probablement - (void)stopTracking:(NSPoint)lastPoint at:(NSPoint)stopPoint inView:(NSView *)controlView mouseIsUp:(BOOL)flag
, mais les variantes de startTracking:
et continueTracking:
pourrait se révéler utile aussi bien pour ce que vous essayez de faire.
Autres conseils
Il y a un truc que j'utilise (mais n'a pas inventé) pour de telles situations. Tout d'abord, en IB, désigner le curseur comme « continu », de sorte que vous obtenez des messages d'action que le curseur est déplacé. Ensuite, dans la méthode d'action, faites ceci:
[NSObject cancelPreviousPerformRequestsWithTarget: self
selector: @selector(finishTrack) object: nil ];
[self performSelector: @selector(finishTrack) withObject: nil
afterDelay: 0.0];
Après la souris est relâchée, la méthode finishTrack
sera appelée.
Cela fonctionne pour moi (et est plus facile que le sous-classement 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
}
}
Il semble que l'idée de Kperryua fournirait la solution la plus propre, donc je marquerai que la réponse acceptée, mais je me suis retrouvé avec un peu d'un hack qui a fonctionné pour ma situation spécifique, donc je pensais que je pourrais partager aussi.
L'application que je fais a besoin d'être multi-plateforme, donc je suis en utilisant Cocotron (un projet open source qui implémente une grande partie de l'API Cocoa sur Windows) pour atteindre cet objectif et cocotron ne supporte pas le stopTracking: ... méthode mentionné ci-dessus.
Par chance, tout en jouant avec un petit programme de test que nous avons fait, il a été découvert que si vous remplacez le la méthode mouseDown du NSSlider et appelez super de cette méthode, il ne retourne pas jusqu'à ce que le bouton de la souris est relâché, de sorte que vous peut simplement mettre ce code doit être à l'intérieur de la méthode mouseUp mouseDown après l'appel à super. Ceci est un peu un hack sale, mais il semble être la seule solution qui fonctionnera dans notre cas donc nous allons devoir aller avec elle.
Juste au cas où quelqu'un se demande comment y parvenir à Swift 3 avec l'état d'un NSSlider est réglé en continu:
@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)
}
}
...
Cela peut être fait que par des méthodes telles que (sous-classement de @mprudhom ne fonctionnera pas à 100% pour la libération de savoir)
Pour le début de la traînée, vous devez becomeFirstResponder
catch (pour cela, vous devez également fournir needsPanelToBecomeKey
)
pour la fin de glisser, vous devez sous-classe mouseDown:
(elle est appelée mouseDown, mais il est appelé lorsque le bouton relâché)
Remarque: cette approche fera de votre curseur le premier répondeur, annulant tout autre premier courant répondeur
Note: approche de 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
J'ai eu un besoin similaire, mais pour un curseur sur un NSTouchBar. J'ai utilisé une approche similaire « rapide et sale », comme Marc Prud'hommeaux et Martin Majewski, à des événements légèrement différentes pour inspecter. Voici le code Swift qui vous permet de suivre à la fois la valeur continue et la valeur finale du curseur sur une barre tactile.
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)")
}
}
}
}