我正在尝试继承 NSSlider 以创建一个称为缓动盘的控件。基本上我需要的是一个总是从中间开始的滑块,当它向左或向右移动时,它会每隔一段时间发送通知(由一个可以设置的属性确定)通知它的容器它的当前值,然后当你松开旋钮,它会回到中间。我希望实现将滑块返回到中间并停止在滑块的 mouseUp 事件中发送通知的功能,但似乎出于某种原因,苹果在滑块上的 mouseDown 事件后禁用了 MouseUp 事件并处理所有滑块功能在较低的水平。无论如何我可以找回 mouseUp 事件吗?如果没有,任何人都可以提出合理的解决方法吗?
8 回答
每当您注意到超类的实现mouseDragged:
ormouseUp:
没有被调用时,很可能是因为该类的实现mouseDown:
进入了跟踪循环。这对于许多子类来说当然是正确的,NSControl
包括NSSlider
.
检测鼠标向上的更好方法是对单元格进行子类化并覆盖适当的跟踪方法。在这种情况下,您可能想要- (void)stopTracking:(NSPoint)lastPoint at:(NSPoint)stopPoint inView:(NSView *)controlView mouseIsUp:(BOOL)flag
,但startTracking:
andcontinueTracking:
变体可能对您尝试做的事情也很有用。
这对我有用(并且比继承 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
}
}
在这种情况下,我使用(但没有发明)一个技巧。首先,在 IB 中,将滑块指定为“连续”,以便在移动滑块时收到操作消息。然后,在 action 方法中,执行以下操作:
[NSObject cancelPreviousPerformRequestsWithTarget: self
selector: @selector(finishTrack) object: nil ];
[self performSelector: @selector(finishTrack) withObject: nil
afterDelay: 0.0];
释放鼠标后,finishTrack
将调用该方法。
似乎 Kperryua 的想法将提供最干净的解决方案,因此我将其标记为已接受的答案,但我最终使用了一些适用于我的特定情况的 hack,所以我想我也可以分享一下。
我正在制作的应用程序需要是跨平台的,所以我使用 Cocotron(一个在 Windows 上实现了大部分 Cocoa API 的开源项目)来实现这个目标,并且 cocotron 不支持上面提到的 stopTracking:... 方法。
幸运的是,在玩弄一个小测试程序时,我们发现如果你重写 NSSlider 的 mouseDown 方法并调用该方法的 super,它不会返回,直到释放鼠标按钮,所以你可以放在调用 super 之后,mouseDown 方法内的 mouseUp 中应该有任何代码。这有点肮脏,但它似乎是唯一适用于我们的案例的解决方案,所以我们将不得不接受它。
以防万一有人想知道如何在 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
我认为将 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)
}
}
我也有类似的需求,但需要 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)")
}
}
}
}