子类化 NSSlider:需要解决丢失鼠标向上事件的问题(Cocoa OSX)
我正在尝试对 NSSlider 进行子类化以创建一个称为滚轮的控件。基本上我需要的是一个滑块,它总是从中间开始,当它移动到左侧或右侧时,它会经常发送通知(由可以设置的属性确定),通知其容器其当前值,然后当您放开旋钮,它会回到中间。我希望实现将滑块返回到中间并停止在滑块的 mouseUp 事件中发送通知的功能,但似乎由于某种原因,苹果在滑块上的 mouseDown 事件之后禁用了 MouseUp 事件并处理所有滑块功能处于较低水平。无论如何我可以恢复 mouseUp 事件吗?如果没有,谁能建议一个合理的解决方法?
I am trying to subclass NSSlider to create a control called a jog dial. Basically what I need is a slider which always starts at the middle and when it is moved to the left or right it will send notifications every so often (determined by an attribute one can set) informing its container of its current value, then when you let you go of the knob, it will return to the middle. I was hoping to implement the functionality to return the slider to the middle and stop sending notifications in the mouseUp event of the slider but it seems that for some reason apple disables the MouseUp event after a mouseDown event on the slider and handles all the slider functionality at a lower level. Is there anyway I can get the mouseUp event back? If not can anyone suggest a reasonable workaround?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(8)
每当您发现超类的
mouseDragged:
或mouseUp:
实现没有被调用时,很可能是因为该类的mouseDown:
实现进入一个跟踪循环。对于许多NSControl
子类(包括NSSlider
)来说确实如此。检测鼠标松开的更好方法是对单元进行子类化并覆盖适当的跟踪方法。在这种情况下,您可能需要 - (void)stopTracking:(NSPoint)lastPoint at:(NSPoint)stopPoint inView:(NSView *)controlView mouseIsUp:(BOOL)flag,但是
startTracking :
和continueTracking:
变体可能对您想要做的事情也很有用。Whenever you notice that a superclass's implementation of
mouseDragged:
ormouseUp:
is not getting called, it's most likely because the class's implementation ofmouseDown:
enters a tracking loop. This is certainly true of manyNSControl
subclasses includingNSSlider
.A better way to detect a mouse up is to subclass the cell and override the appropriate tracking method. In this case, you probably want
- (void)stopTracking:(NSPoint)lastPoint at:(NSPoint)stopPoint inView:(NSView *)controlView mouseIsUp:(BOOL)flag
, but thestartTracking:
andcontinueTracking:
variants might prove useful as well for what you're trying to do.对于这种情况,我使用(但不是发明)一个技巧。首先,在 IB 中,将滑块指定为“连续”,以便在滑块移动时您会收到操作消息。然后,在action方法中,执行以下操作:
释放鼠标后,将调用
finishTrack
方法。There is a trick that I use (but didn't invent) for such situations. First, in IB, designate the slider as "continuous", so that you'll get action messages as the slider is moved. Then, in the action method, do this:
After the mouse is released, the
finishTrack
method will be called.这对我有用(并且比继承 NSSlider 更容易):
This works for me (and is easier than subclassing NSSlider):
看来 Kperryua 的想法将提供最干净的解决方案,所以我将其标记为已接受的答案,但我最终使用了一些适合我的具体情况的黑客,所以我想我也可以分享它。
我正在制作的应用程序需要跨平台,因此我使用 Cocotron(一个在 Windows 上实现大部分 Cocoa API 的开源项目)来实现此目标,而 cocotron 不支持上面提到的 stopTracking:... 方法。
幸运的是,在玩一个小测试程序时,我们发现,如果您重写 NSSlider 的 mouseDown 方法并调用该方法的 super,它不会返回,直到释放鼠标按钮,因此您可以将调用 super 后,任何代码都应位于 mouseDown 方法内的 mouseUp 中。这是一个有点肮脏的黑客,但它似乎是唯一适用于我们的情况的解决方案,因此我们将不得不采用它。
It seems that Kperryua's idea would provide the cleanest solution so I will mark that as the accepted answer, but I ended using a bit of a hack that worked for my specific situation so I thought I might share that as well.
The app that I am making needs to be cross platform so I am using Cocotron (an open source project that implements much of the Cocoa API on Windows) to achieve this goal and cocotron does not support the stopTracking:... method mentioned above.
Luckily, while playing around with a little test program we made it was discovered that if you override the the mouseDown method of the NSSlider and call the super of that method, it does not return until the mouse button is released, so you can just put whatever code should be in mouseUp inside the mouseDown method after the call to super. This is a bit of a dirty hack but it seems to be the only solution that will work in our case so we are going to have to go with it.
以防万一有人想知道如何在 Swift 3 中使用 NSSlider 实现这一点,即状态设置为连续:
Just in case someone is wondering how to achieve this in Swift 3 with an NSSlider thats state is set to continuous:
这只能通过子类化来完成(@mprudhom 等方法对于已知版本不会 100% 工作)
对于拖动的开始,您需要捕获
becomeFirstResponder
(为此您还需要提供needsPanelToBecomeKey
)为了结束拖动,您需要子类化
mouseDown:
(它称为mouseDown,但当旋钮释放时它会被调用)注意:这种方法将使您的滑块第一响应者,取消任何其他当前的第一响应者
注意:来自 mprudhom 的方法
This can be done only by subclassing (methods like from @mprudhom won't work 100% for know release)
For the beginning of the drag, you need to catch
becomeFirstResponder
(for this you need to also provideneedsPanelToBecomeKey
)for the end of dragging, you need to subclass
mouseDown:
(its called mouseDown, but it gets called when knob released)Note: this approach will make your slider the first responder, canceling any other current first responder
Note: approach from mprudhom
我有类似的需求,但需要 NSTouchBar 上的滑块。我使用了与 Marc Prud'hommeaux 和 Martin Majewski 类似的“快速而肮脏”的方法,只是要检查的事件略有不同。下面是 Swift 代码,可让您跟踪触摸栏上滑块的连续值和最终值。
I had a similar need, but for a slider on an NSTouchBar. I used a similar "quick-and dirty" approach as Marc Prud'hommeaux and Martin Majewski, just slightly different events to inspect. Here's the Swift code that allows you to track both the continuous value AND the final value of the slider on a touchbar.
我认为对 NSSlider 进行子类化比在目标中添加额外的代码更好。
此方法转发完整的 mouseup 和 mousedown 事件(可能对获取按键修饰符有用),并准备好在需要时处理其他类型的事件。
该滑块存储鼠标按下时的初始值,因此我们可以获取鼠标松开时的初始值和最终值。这对于可撤消的操作非常有用。
I think it is better to subclass the NSSlider than to add extra code in the target.
This method forwards the complete mouseup and mousedown events ( may be useful to get key modifiers ), and is ready to deal with other kind of events if needed.
This slider stores the initial value on mouse down, so we can get the initial and final values in the mouse up. This is very useful for undoable actions.