2012-02-08 20 views
7

Tôi muốn tạo một NSButton gửi một hành động khi được bấm, nhưng khi được bấm trong 1 hoặc 2 giây, nó sẽ hiển thị NSMenu. Chính xác giống như câu hỏi này here, nhưng vì câu trả lời đó không giải quyết được vấn đề của tôi, tôi quyết định hỏi lại.NSButton bị trễ NSMenu - Mục tiêu-C/Cacao

Ví dụ: đi tới Trình tìm kiếm, mở một cửa sổ mới, điều hướng qua một số thư mục và sau đó nhấp vào nút quay lại: bạn chuyển đến thư mục trước đó. Bây giờ bấm và giữ nút quay lại: một menu được hiển thị. Tôi không biết làm thế nào để làm điều này với một NSPopUpButton.

Trả lời

8

Sử dụng NSSegmentedControl.

Thêm menu bằng cách gửi setMenu:forSegment: tới điều khiển (kết nối mọi thứ với cổng ra menu trong IB sẽ không thực hiện thủ thuật). Có một hành động được kết nối với điều khiển (điều này là quan trọng).

Nên hoạt động chính xác như bạn mô tả.

+1

Quá xấu, bạn không thể đặt chiều cao tùy chỉnh cho NSSegmentedControl - Tôi cần menu đó được đính kèm với một nút lớn. – zrxq

+0

Đã hoạt động hoàn hảo! Cảm ơn! – Alex

+0

Bí quyết tuyệt vời, với một chút chơi xung quanh trong IB bạn có thể có được một kiểm soát thực sự thanh lịch như thế này. –

5

Tạo lớp con của NSPopUpButton và ghi đè các sự kiện mouseDown/mouseUp.

Có sự chậm trễ sự kiện mouseDown trong giây lát trước khi thực hiện triển khai super và chỉ khi chuột vẫn bị giữ.

Có trường hợp mouseUp thiết lập selectedMenuItem để nil (và do đó selectedMenuItemIndex sẽ -1) trước khi bắn của nút target/action.

Vấn đề duy nhất khác là xử lý các nhấp chuột nhanh chóng, trong đó thời gian cho một lần nhấp có thể kích hoạt tại thời điểm chuột rơi xuống cho một số nhấp chuột trong tương lai. Thay vì sử dụng NSTimer và vô hiệu hóa nó, tôi đã chọn để có một truy cập đơn giản cho sự kiện mouseDown và bảo lãnh nếu bộ đếm đã thay đổi.

Dưới đây là đoạn code tôi đang sử dụng trong phân lớp của tôi:

// 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

Đẹp! Điều này thật đúng với gì mà tôi đã tìm kiếm. Quá xấu không có một điều khiển tiêu chuẩn trong App Kit cho loại điều này (đó là lẻ bởi vì Apple sử dụng quy ước giao diện người dùng này trong * rất nhiều * của các ứng dụng riêng của nó). – aapierce

+1

Đối với 'delayInSeconds', hãy xem xét sử dụng' NSEvent.doubleClickInterval' thay vì hằng số '0,2'. Điều này sẽ điều chỉnh độ trễ theo tùy chọn xử lý chuột của người dùng. Nhanh hơn, chậm trễ hơn, đối với người dùng có thời gian nhấp đúp ngắn và chậm hơn, chậm trễ hơn, cho người dùng có thời gian nhấp đúp dài hơn. –

+1

Cảm ơn @GrahamMiln, tôi đã cập nhật câu trả lời của mình để làm điều đó. –

1

Nếu ai vẫn cần này, đây là giải pháp của tôi dựa trên một NSButton đồng bằng, không một điều khiển phân đoạn.

NSButton phân lớp và triển khai mouseDown tùy chỉnh bắt đầu hẹn giờ trong vòng chạy hiện tại. Trong mouseUp, hãy kiểm tra xem bộ hẹn giờ có không kích hoạt hay không. Trong trường hợp đó, hủy nó và thực hiện hành động mặc định.

Đây là một cách tiếp cận rất đơn giản, nó hoạt động với bất kỳ NSButton nào bạn có thể sử dụng trong IB.

Mã dưới đây:

- (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]; 
} 

tôi đã đăng một working sample trên GitHub của tôi.

Các vấn đề liên quan