子类化 NSLider:需要一个解决方法来解决丢失鼠标事件(Cocoa OSX)

Posted

技术标签:

【中文标题】子类化 NSLider:需要一个解决方法来解决丢失鼠标事件(Cocoa OSX)【英文标题】:Subclassing NSSlider: Need a workaround for missing mouse up events (Cocoa OSX) 【发布时间】:2010-10-12 23:55:05 【问题描述】:

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

【问题讨论】:

【参考方案1】:

当您注意到mouseDragged:mouseUp: 的超类实现没有被调用时,很可能是因为该类的mouseDown: 实现进入了跟踪循环。许多NSControl 子类(包括NSSlider)当然是这样。

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

【讨论】:

【参考方案2】:

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

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

鼠标松开后,会调用finishTrack方法。

【讨论】:

【参考方案3】:

这对我有用(并且比继承 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
    

【讨论】:

这在所有情况下都不起作用(开始拖动然后转到菜单,您将永远不会注册endingDrag)【参考方案4】:

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

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

幸运的是,在玩弄一个小测试程序时,我们发现如果你重写 NSSlider 的 mouseDown 方法并调用该方法的 super,它不会返回,直到释放鼠标按钮,所以你可以在调用 super 之后将 mouseUp 中的任何代码放入 mouseDown 方法中。这有点肮脏,但它似乎是唯一适用于我们的案例的解决方案,所以我们将不得不接受它。

【讨论】:

【参考方案5】:

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


...

【讨论】:

【参考方案6】:

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

【讨论】:

【参考方案7】:

我也有类似的需求,但需要 NSTouchBar 上的滑块。我使用了与 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)")
            
        
    

【讨论】:

【参考方案8】:

我认为将 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)
    

【讨论】:

如果isContinuousNSSlider 上设置为false,此解决方案将不会捕获mouseDown 事件。这种情况的解决方案是在调用super.mouseDown(with:)之前调用mouseDown(with:)中的mouseDownClosure,然后调用sendAction(_:to:)中的mouseUpClosure

以上是关于子类化 NSLider:需要一个解决方法来解决丢失鼠标事件(Cocoa OSX)的主要内容,如果未能解决你的问题,请参考以下文章

每天一个设计模式-5 工厂方法模式

子类化视图控制器:前向声明

Spring IOC&DI

我们可以声明一个抽象方法来返回抽象超类中的子类对象而不需要新的实例化吗?

如何给父类和子类赋值

java抽象类