2012-02-08 3 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 이벤트가 잠시 지연됩니다.

mouseUp 이벤트가 버튼의이 target/ action을 발사하기 전에 selectedMenuItem ( -1 따라서 selectedMenuItemIndex이 될 것입니다) nil에를 설정했습니다.

유일한 다른 문제는 빠른 클릭을 처리하는 것입니다. 여기서 빠른 클릭을 처리하는 것입니다. 즉, 향후 클릭을 위해 마우스를 눌렀을 때 한 번의 클릭에 대한 타이머가 작동 할 수 있습니다. NSTimer을 사용하고이를 무효 화하는 대신에 나는 mouseDown 이벤트에 대한 간단한 카운터를 가지며 카운터가 변경된 경우 구제 조치를합니다. 사람이 여전히 필요하면 여기에 일반 NSButton 아닌 세그먼트 컨트롤을 기반으로 내 솔루션입니다,

// 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에는 표준 컨트롤이 없다. (애플이이 UI 컨벤션을 많은 앱에서 사용하기 때문에 이상하다.) – aapierce

+1

'delayInSeconds'의 경우'NSEvent '사용을 고려하십시오.상수 '0.2'대신 'doubleClickInterval'을 사용합니다. 이렇게하면 사용자의 마우스 처리 환경 설정에 따라 지연이 조정됩니다. 더블 클릭 시간이 짧고 사용자가 더블 클릭 시간이 길어질수록 지연 시간은 더 짧아집니다. –

+1

감사합니다. @GrahamMiln, 저의 대답을 업데이트했습니다. –

1

:

여기에 내 서브 클래스에서 사용하고 코드입니다.

하위 클래스 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을 게시했습니다.