NSSliderをサブクラス化して、ジョグダイヤルと呼ばれるコントロールを作成しようとしています。基本的に私が必要としているのは、常に中央から始まり、左または右に移動すると、コンテナに現在の値を通知する通知を頻繁に送信するスライダーです(設定できる属性によって決定されます)。つまみを離すと真ん中に戻ります。スライダーを中央に戻し、スライダーのmouseUpイベントで通知の送信を停止する機能を実装したいと思っていましたが、何らかの理由で、AppleはスライダーのmouseDownイベントの後にMouseUpイベントを無効にし、すべてのスライダー機能を処理しているようですより低いレベルで。とにかく、mouseUpイベントを取り戻すことはできますか?そうでない場合、誰かが合理的な回避策を提案できますか?
8 に答える
スーパークラスの実装が呼び出されていないことに気付いた場合は常に、クラスの実装mouseDragged:
が追跡ループに入っていることが原因である可能性があります。これは確かにを含む多くのサブクラスに当てはまります。mouseUp:
mouseDown:
NSControl
NSSlider
マウスアップを検出するためのより良い方法は、セルをサブクラス化し、適切な追跡方法をオーバーライドすることです。この場合、おそらく必要です- (void)stopTracking:(NSPoint)lastPoint at:(NSPoint)stopPoint inView:(NSView *)controlView mouseIsUp:(BOOL)flag
が、startTracking:
およびcontinueTracking:
バリアントは、実行しようとしていることにも役立つ可能性があります。
これは私にとってはうまくいきます(そして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で、スライダーを「連続」として指定します。これにより、スライダーを動かしたときにアクションメッセージが表示されます。次に、アクションメソッドで次のようにします。
[NSObject cancelPreviousPerformRequestsWithTarget: self
selector: @selector(finishTrack) object: nil ];
[self performSelector: @selector(finishTrack) withObject: nil
afterDelay: 0.0];
マウスを離すと、finishTrack
メソッドが呼び出されます。
Kperryuaのアイデアが最もクリーンな解決策を提供するように思われるので、それを受け入れられた答えとしてマークしますが、特定の状況で機能する少しのハックを使用しなくなったので、それも共有できると思いました。
私が作成しているアプリはクロスプラットフォームである必要があるため、この目標を達成するためにCocotron(WindowsでCocoa APIの多くを実装するオープンソースプロジェクト)を使用しています。cocotronは上記のstopTracking:...メソッドをサポートしていません。
幸いなことに、小さなテストプログラムで遊んでいると、NSSliderのmouseDownメソッドをオーバーライドしてそのメソッドのスーパーを呼び出すと、マウスボタンを離すまで戻らないことがわかりました。 superを呼び出した後、mouseDownメソッド内のmouseUpにコードを含める必要があります。これは少し汚いハックですが、私たちの場合に機能する唯一の解決策のようですので、それを使用する必要があります。
誰かがSwift3で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
同様のニーズがありましたが、NSTouchBarのスライダーが必要でした。私は、MarcPrud'hommeauxやMartinMajewskiと同様の「迅速で汚い」アプローチを使用しましたが、検査するイベントはわずかに異なります。これは、タッチバーのスライダーの連続値と最終値の両方を追跡できるようにする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)")
}
}
}
}
ターゲットにコードを追加するよりも、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)
}
}