Подкласс NSSlider: нужен обходной путь для пропущенных событий Mouse Up (Cocoa OSX)
Вопрос
Я пытаюсь подключить NSSlider, чтобы создать элемент управления, называемый циферблатом JOG. По сути, мне нужен ползунок, который всегда начинается в середине, и когда он перемещается влево или вправо, он будет отправлять уведомления так часто (определяется атрибутом, который можно установить), информируя свой контейнер своего текущего значения, затем, когда вы Отпустите ручку, она вернется в середину. Я надеялся реализовать функциональность, чтобы вернуть слайдер в середину и прекратить отправлять уведомления в событии MouseUp о слайдере, но кажется, что по какой -то причине Apple отключает событие MouseUp после события Mousedown на ползунке и обрабатывает все функции слайдера на более низком уровне. В любом случае, я могу вернуть событие MouseUp? Если нет, может ли кто -нибудь предложить разумный обходной путь?
Решение
Всякий раз, когда вы замечаете, что внедрение суперкласса mouseDragged:
или mouseUp:
не вызывается, это наиболее вероятно, потому что внедрение класса mouseDown:
Входит в петлю отслеживания. Это, безусловно, верно для многих NSControl
Подклассы, включая NSSlider
.
Лучшим способом обнаружения мыши является подкласс ячейки и переопределить соответствующий метод отслеживания. В этом случае вы, вероятно, хотите - (void)stopTracking:(NSPoint)lastPoint at:(NSPoint)stopPoint inView:(NSView *)controlView mouseIsUp:(BOOL)flag
, но startTracking:
и continueTracking:
Варианты могут оказаться полезными для того, что вы пытаетесь сделать.
Другие советы
Есть трюк, который я использую (но не изобрел) для таких ситуаций. Во -первых, в IB назначьте ползунок «непрерывным», чтобы вы получили сообщения действий, когда слайдер перемещается. Затем, в методе действия, сделайте это:
[NSObject cancelPreviousPerformRequestsWithTarget: self
selector: @selector(finishTrack) object: nil ];
[self performSelector: @selector(finishTrack) withObject: nil
afterDelay: 0.0];
После того, как мышь выйдет finishTrack
Метод будет вызван.
Это работает для меня (и проще, чем подклассная 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
}
}
Похоже, что идея Kperryua предоставила бы самое чистое решение, поэтому я отмечу это как принятый ответ, но я закончил, используя немного взлома, который работал для моей конкретной ситуации, поэтому я подумал, что могу поделиться этим.
Приложение, которое я делаю, должно быть кросс -платформой, поэтому я использую Cocotron (проект с открытым исходным кодом, который реализует большую часть API какао в Windows) для достижения этой цели, и Cocotron не поддерживает остановку: ... ... упомянутый выше метод.
К счастью, играя с небольшой тестовой программой, мы сделали, что было обнаружено, что если вы переопределяете метод Mousedown NSSlider и позвоните в супер этого метода, она не вернется, пока кнопка мыши не будет выпущена, так что вы можете просто положить Какой бы код должен находиться в мышке внутри метода Mousedown после вызова Super. Это немного грязный взлом, но, похоже, это единственное решение, которое будет работать в нашем случае, поэтому нам придется пойти с этим.
На всякий случай, если кто -то задается вопросом, как достичь этого в Swift 3 с NSSLider, который утверждает, что непрерывно:
@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)
}
}
...
Это может быть сделано только путем подкласса (такие методы, как @mprudhom, не будут работать на 100% для выпуска знания)
Для начала сопротивления вам нужно поймать becomeFirstResponder
(Для этого вам также необходимо предоставить needsPanelToBecomeKey
)
Для конца перетаскивания вам нужно подкласс mouseDown:
(Это называется Mousedown, но его называют, когда выпустил ручку)
Примечание: этот подход сделает ваш ползунок первым респондентом, отменив любого другого текущего первого респондента
Примечание: подход от 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
У меня была похожая потребность, но для слайдера на NSTOUCHBAR. Я использовал аналогичный «быстрый и грязный» подход, как Марк Пруд'хоммо и Мартин Мажеюски, немного разные события для проверки. Вот быстрый код, который позволяет вам отслеживать как непрерывное значение, так и конечное значение слайдера на сенсорной панели.
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)")
}
}
}
}