2012-02-08 17 views
7

クリックしたときにアクションを送信するNSButtonを作成したいですが、1〜2秒間押すとNSMenuが表示されます。この質問hereとまったく同じですが、その答えは私の問題を解決しないので、私は再度尋ねることにしました。遅延NSMenuのNSButton - Objective-C/Cocoa

たとえば、Finderに移動して新しいウィンドウを開き、いくつかのフォルダをナビゲートしてから、戻るボタンをクリックします。前のフォルダに移動します。次に、戻るボタンをクリックしたままにします。メニューが表示されます。私はNSPopUpButtonでこれを行う方法を知らない。

答えて

8

NSSegmentedControlを使用してください。

setMenu:forSegment:をコントロールに送信してメニューを追加します(IBのmenuコンセントに何かを接続することはできません)。コントロールにアクションを接続してください(これは重要です)。

あなたの説明どおりに動作するはずです。

+1

NSSegmentedControlの奥行きを設定できないのは残念です。大きなボタンに貼り付けられたメニューが必要です。 – zrxq

+0

完璧に作業しました!ありがとう! – Alex

+0

素敵なトリックで、IBの周りを少し遊んで、このような本当にエレガントなコントロールを得ることができます。 –

5

NSPopUpButtonのサブクラスを作成し、mouseDown/mouseUpイベントを上書きします。

の実装を呼び出す前に、マウスがまだ押されている場合のみ、mouseDownイベントの遅延が発生します。

は、ボタンのtarget/actionを焼成前mouseUpイベントがnilselectedMenuItemを設定する(したがってselectedMenuItemIndex-1になります)があります。

唯一の他の問題は、将来のクリックに対してマウスが押された瞬間に1回のクリックのタイマーが起動する急速なクリックを処理することです。 NSTimerを使用して無効にする代わりに、私はmouseDownイベントの簡単なカウンタを選択し、カウンタが変更された場合には救済します。ここで

が、私は私のサブクラスで使用しているコードです:

// MyClickAndHoldPopUpButton.h 
@interface MyClickAndHoldPopUpButton : NSPopUpButton 

@end 

// MyClickAndHoldPopUpButton.m 
@interface MyClickAndHoldPopUpButton() 

@property BOOL mouseIsDown; 
@property BOOL menuWasShownForLastMouseDown; 
@property int mouseDownUniquenessCounter; 

@end 

@implementation MyClickAndHoldPopUpButton 

// highlight the button immediately but wait a moment before calling the super method (which will show our popup menu) if the mouse comes up 
// in that moment, don't tell the super method about the mousedown at all. 
- (void)mouseDown:(NSEvent *)theEvent 
{ 
    self.mouseIsDown = YES; 
    self.menuWasShownForLastMouseDown = NO; 
    self.mouseDownUniquenessCounter++; 
    int mouseDownUniquenessCounterCopy = self.mouseDownUniquenessCounter; 

    [self highlight:YES]; 

    float delayInSeconds = [NSEvent doubleClickInterval]; 
    dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, delayInSeconds * NSEC_PER_SEC); 
    dispatch_after(popTime, dispatch_get_main_queue(), ^(void){ 
    if (self.mouseIsDown && mouseDownUniquenessCounterCopy == self.mouseDownUniquenessCounter) { 
     self.menuWasShownForLastMouseDown = YES; 
     [super mouseDown:theEvent]; 
    } 
    }); 
} 

// if the mouse was down for a short enough period to avoid showing a popup menu, fire our target/action with no selected menu item, then 
// remove the button highlight. 
- (void)mouseUp:(NSEvent *)theEvent 
{ 
    self.mouseIsDown = NO; 

    if (!self.menuWasShownForLastMouseDown) { 
    [self selectItem:nil]; 

    [self sendAction:self.action to:self.target]; 
    } 

    [self highlight:NO]; 
} 

@end 
+1

美しい!これはまさに私が探していたものです。あまりにも悪いことに、このようなことのためにApp Kitには標準コントロールがない(これはAppleがこのUIコンベンションを多くのアプリで使っているので奇妙なことである)。 – aapierce

+1

'delayInSeconds'については、' NSEventの使用を検討してください。定数 '0.2'の代わりにdoubleClickInterval'を使用します。これにより、ユーザーのマウス操作の好みに応じて遅延が調整されます。ダブルクリック時間が短く、遅延時間がより長いユーザーの方が、ダブルクリック時間が長いユーザーの場合は、遅延時間が短縮されます。 –

+1

ありがとう@GrahamMiln、私はそれを行うために私の答えを更新しました。 –

1

誰もがまだこれを必要とする場合は、ここではないセグメント化された制御プレーンNSButton、に基づいて、私の解決策です。

NSButtonをサブクラス化して、現在の実行ループ内でタイマーを開始するカスタムmouseDownを実装します。 mouseUpでは、タイマーが起動していないかどうかを確認してください。その場合は、キャンセルしてデフォルトの操作を実行してください。

これは非常に簡単な方法で、IBで使用できるNSButtonで動作します。

以下のコード:

- (void)mouseDown:(NSEvent *)theEvent { 
    [self setHighlighted:YES]; 
    [self setNeedsDisplay:YES]; 

    _menuShown = NO; 
    _timer = [NSTimer scheduledTimerWithTimeInterval:0.3 target:self selector:@selector(showContextMenu:) userInfo:nil repeats:NO]; 

    [[NSRunLoop currentRunLoop] addTimer:_timer forMode:NSDefaultRunLoopMode]; 
} 

- (void)mouseUp:(NSEvent *)theEvent { 
    [self setHighlighted:NO]; 
    [self setNeedsDisplay:YES]; 

    [_timer invalidate]; 
    _timer = nil; 

    if(!_menuShown) { 
     [NSApp sendAction:[self action] to:[self target] from:self]; 
    } 

    _menuShown = NO; 
} 

- (void)showContextMenu:(NSTimer*)timer { 
    if(!_timer) { 
     return; 
    } 

    _timer = nil; 
    _menuShown = YES; 

    NSMenu *theMenu = [[NSMenu alloc] initWithTitle:@"Contextual Menu"]; 

    [[theMenu addItemWithTitle:@"Beep" action:@selector(beep:) keyEquivalent:@""] setTarget:self]; 
    [[theMenu addItemWithTitle:@"Honk" action:@selector(honk:) keyEquivalent:@""] setTarget:self]; 

    [theMenu popUpMenuPositioningItem:nil atLocation:NSMakePoint(self.bounds.size.width-8, self.bounds.size.height-1) inView:self]; 

    NSWindow* window = [self window]; 

    NSEvent* fakeMouseUp = [NSEvent mouseEventWithType:NSLeftMouseUp 
               location:self.bounds.origin 
             modifierFlags:0 
              timestamp:[NSDate timeIntervalSinceReferenceDate] 
              windowNumber:[window windowNumber] 
               context:[NSGraphicsContext currentContext] 
              eventNumber:0 
              clickCount:1 
               pressure:0.0]; 

    [window postEvent:fakeMouseUp atStart:YES]; 

    [self setState:NSOnState]; 
} 

私はGitHubの上working sampleを掲載しました。