サブクラス化NSSLIDER:マウスアップイベントが欠落するための回避策が必要です(Cocoa OSX)
質問
ジョグダイヤルと呼ばれるコントロールを作成するために、nssliderをサブクラス化しようとしています。基本的に私が必要なのは、常に中央から始まり、左または右に移動すると、通知が頻繁に送信される(属性によって決定される属性で決定)、そのコンテナに現在の値を通知します。ノブを行かせてください、それは真ん中に戻ります。スライダーを中央に戻し、スライダーのマウスアップイベントで通知の送信を停止する機能を実装したいと思っていましたが、何らかの理由で、Appleはスライダーでのマウスダウンイベントの後にMouseUpイベントを無効にし、すべてのスライダー機能を処理するようです低レベルで。とにかく、MouseUpイベントを取り戻すことができますか?そうでない場合、誰かが合理的な回避策を提案できますか?
解決
スーパークラスの実装に気付いたときはいつでも mouseDragged:
また mouseUp:
呼び出されていません、それはクラスの実装が mouseDown:
追跡ループに入ります。これは確かに多くの人に当てはまります NSControl
を含むサブクラス NSSlider
.
マウスを検出するより良い方法は、セルをサブクラス化し、適切な追跡方法をオーバーライドすることです。この場合、おそらく欲しいでしょう - (void)stopTracking:(NSPoint)lastPoint at:(NSPoint)stopPoint inView:(NSView *)controlView mouseIsUp:(BOOL)flag
, 、 しかし startTracking:
と continueTracking:
バリアントは、あなたがやろうとしていることにも役立つかもしれません。
他のヒント
そのような状況には使用していない(しかし発明しなかった)トリックがあります。まず、IBでは、スライダーを「連続」として指定するため、スライダーが移動されるとアクションメッセージが表示されます。次に、アクションメソッドでは、これを行います。
[NSObject cancelPreviousPerformRequestsWithTarget: self
selector: @selector(finishTrack) object: nil ];
[self performSelector: @selector(finishTrack) withObject: nil
afterDelay: 0.0];
マウスが放出された後、 finishTrack
メソッドが呼び出されます。
これは私にとってうまくいきます(そして、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
}
}
Kperryuaのアイデアは最もクリーンなソリューションを提供するので、それを受け入れられた答えとしてマークしますが、特定の状況で働いたハックを少し使用して終わったので、それを共有するかもしれないと思いました。
私が作っているアプリはクロスプラットフォームである必要があるため、この目標を達成するためにココトロン(WindowsのココアAPIの多くを実装するオープンソースプロジェクト)を使用しています。ココトロンはストップトラックをサポートしません。
幸いなことに、小さなテストプログラムで遊んでいる間、私たちは、NSSLiderのMousedownメソッドをオーバーライドしてその方法のスーパーを呼び出すと、マウスボタンがリリースされるまで戻らないので、ただ置くことができることが発見されました。スーパーへの呼び出し後、マウスタウンメソッド内のマウスアップにあるコードは何でも。これは少し汚いハックですが、私たちのケースで機能する唯一のソリューションであるように思われますので、私たちはそれを使わなければなりません。
Swift 3で誰かがこれを達成する方法を疑問に思っている場合に備えて、状態は継続的に設定されています。
@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:
(ムーズダウンと呼ばれますが、ノブがリリースされたときに呼ばれます)
注:このアプローチにより、スライダーが最初の応答者になり、他の現在の最初の応答者をキャンセルします
注: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のスライダーのために。 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)")
}
}
}
}