Создание подклассов NSSlider: нужен обходной путь для пропущенных событий мыши (Cocoa OSX) - PullRequest
6 голосов
/ 13 октября 2010

Я пытаюсь создать подкласс NSSlider для создания элемента управления, называемого поворотным переключателем. По сути, мне нужен слайдер, который всегда начинается с середины, и когда он перемещается влево или вправо, он будет отправлять уведомления очень часто (определяется атрибутом, который можно установить), информируя свой контейнер о его текущем значении, а затем, когда вы отпустите ручку, она вернется к середине. Я надеялся реализовать функциональность для возврата ползунка в середину и прекращения отправки уведомлений в событии mouseUp ползунка, но кажется, что по какой-то причине apple отключает событие MouseUp после события mouseDown на ползунке и обрабатывает все функции ползунка. на более низком уровне. Есть ли способ вернуть событие mouseUp обратно? Если нет, то может ли кто-нибудь предложить разумный обходной путь?

Ответы [ 7 ]

7 голосов
/ 13 октября 2010

Есть прием, который я использую (но не придумывал) для таких ситуаций. Во-первых, в IB обозначьте ползунок как «непрерывный», чтобы при перемещении ползунка вы получали сообщения действий. Затем в методе действия сделайте это:

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

После отпускания мыши будет вызван метод finishTrack.

6 голосов
/ 22 августа 2012

Это работает для меня (и это проще, чем создание подкласса 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
    }
}
6 голосов
/ 13 октября 2010

Всякий раз, когда вы замечаете, что реализация суперкласса mouseDragged: или mouseUp: не вызывается, скорее всего, потому что реализация класса mouseDown: входит в цикл отслеживания.Это, безусловно, верно для многих NSControl подклассов, включая NSSlider.

. Лучший способ обнаружить мышь вверх - это создать подкласс ячейки и переопределить соответствующий метод отслеживания.В этом случае вы, вероятно, захотите - (void)stopTracking:(NSPoint)lastPoint at:(NSPoint)stopPoint inView:(NSView *)controlView mouseIsUp:(BOOL)flag, но варианты startTracking: и continueTracking: могут оказаться полезными и для того, что вы пытаетесь сделать.

3 голосов
/ 13 октября 2010

Кажется, что идея Кперриуа обеспечит самое чистое решение, поэтому я буду отмечать это как принятый ответ, но я прекратил использовать хак, который работал для моей конкретной ситуации, поэтому я подумал, что могу поделиться этим.

Приложение, которое я создаю, должно быть кроссплатформенным, поэтому я использую Cocotron (проект с открытым исходным кодом, который реализует большую часть API-интерфейса Cocoa для Windows) для достижения этой цели, а Cocotron не поддерживает stopTracking :... метод, упомянутый выше.

К счастью, во время игры с небольшой тестовой программой, которую мы сделали, было обнаружено, что если вы переопределите метод mouseDown NSSlider и вызовете super этого метода, он не вернется, пока не будет отпущена кнопка мыши., так что вы можете просто поместить любой код, который должен быть в mouseUp, внутри метода mouseDown после вызова super.Это немного грязный взлом, но, похоже, это единственное решение, которое будет работать в нашем случае, поэтому нам придется пойти на это.

2 голосов
/ 02 января 2017

На всякий случай, если кто-то задается вопросом, как добиться этого в Swift 3 с NSSlider, состояние которого постоянно:

0 голосов
/ 26 августа 2017

У меня была похожая потребность, но для слайдера на NSTouchBar.Я использовал такой же «быстрый и грязный» подход, как и Марк Прудомо и Мартин Маевски, только немного разные события для проверки.Вот код Swift, который позволяет отслеживать как непрерывное значение, так и окончательное значение ползунка на сенсорной панели.

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)")
            }
        }
    }
}
0 голосов
/ 24 ноября 2013

Это может быть сделано только с помощью подклассов (методы, подобные @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
...