子类化NSSlider:需要一种缺少鼠标悬停事件的解决方法(Cocoa OSX)

时间:2010-10-12 23:55:06

标签: cocoa macos nsslider

我正在尝试子类化NSSlider来创建一个名为慢速拨号的控件。基本上我需要的是一个总是从中间开始的滑块,当它向左或向右移动时,它会经常发送通知(由可以设置的属性确定)通知其容器当前值,然后当你让你去旋钮,它会回到中间。我希望实现将滑块返回到中间并停止在滑块的mouseUp事件中发送通知的功能但似乎由于某种原因苹果在滑块上的mouseDown事件之后禁用MouseUp事件并处理所有滑块功能在较低的水平。无论如何,我可以恢复mouseUp事件吗?如果没有,任何人都可以提出合理的解决方法

8 个答案:

答案 0 :(得分:6)

对于这种情况,我使用了一种技巧(但没有发明)。首先,在IB中,将滑块指定为“连续”,以便在移动滑块时获取操作消息。然后,在action方法中,执行以下操作:

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

释放鼠标后,将调用finishTrack方法。

答案 1 :(得分:6)

每当您注意到超级类的mouseDragged:mouseUp:的实现未被调用时,很可能是因为该类的mouseDown:实现进入了跟踪循环。对于包括NSControl的许多NSSlider子类来说,情况确实如此。

检测鼠标的更好方法是对单元格进行子类化并覆盖相应的跟踪方法。在这种情况下,您可能需要- (void)stopTracking:(NSPoint)lastPoint at:(NSPoint)stopPoint inView:(NSView *)controlView mouseIsUp:(BOOL)flag,但startTracking:continueTracking:变体可能对您正在尝试的内容有用。

答案 2 :(得分:6)

这对我有用(并且比子类化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
    }
}

答案 3 :(得分:3)

似乎Kperryua的想法会提供最干净的解决方案,所以我会将其标记为已接受的答案,但我最终使用了一些适合我特定情况的黑客攻击,所以我想我也可以分享它。

我正在制作的应用程序需要跨平台,因此我使用Cocotron(一个在Windows上实现大部分Cocoa API的开源项目)来实现这一目标,而cocotron不支持stopTracking:...方法上文提到的。

幸运的是,在玩一个小测试程序时,我们发现如果你覆盖NSSlider的mouseDown方法并调用该方法的super,它会在鼠标按钮释放后才会返回,所以你可以在调用super之后将mouseUp中的任何代码放在mouseDown方法中。这是一个肮脏的黑客,但它似乎是唯一的解决方案将在我们的情况下工作,所以我们将不得不采取它。

答案 4 :(得分:2)

以防万一有人想知道如何在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)
    }
}

...

答案 5 :(得分:0)

这只能通过子类化来完成(像@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

答案 6 :(得分:0)

我有类似的需求,但对于NSTouchBar上的滑块。我使用过类似的快速和肮脏的#34;接近Marc Prud' hommeaux和Martin Majewski,只是略微不同的事件要检查。这是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)")
            }
        }
    }
}

答案 7 :(得分:0)

我认为将NSSlider子类化比在目标中添加额外的代码更好。

此方法转发完整的mouseup和mousedown事件(对于获取键修饰符可能很有用),并准备在需要时处理其他类型的事件。

此滑块在鼠标向下移动时存储初始值,因此我们可以在鼠标向上移动时获取初始值和最终值。这对于不可撤销的操作非常有用。

class Slider: NSSlider {
    var mouseDownClosure: ((Slider, NSEvent)->Void)?
    var mouseUpClosure: ((Slider, NSEvent)->Void)?

    var initialValue: Double = 0
    
    override func sendAction(_ action: Selector?, to target: Any?) -> Bool {
        let result = super.sendAction(action, to: target)
    
        if let event = NSApplication.shared.currentEvent {
            switch event.type {
            case .leftMouseUp:
                mouseUpClosure?(self, event)
            case .leftMouseDown:
                mouseDownClosure?(self, event)
            default:
                break
            }
        }
        return result
    }

    override func mouseDown(with event: NSEvent) {
        initialValue = self.doubleValue
        super.mouseDown(with: event)
    }
}