2010-05-11 26 views
33

Tôi đã có một thanh trạng thái bật mở NSMenu, và tôi có một nhóm đại biểu và nó được nối một cách chính xác (-(void)menuNeedsUpdate:(NSMenu *)menu hoạt động tốt). Điều đó nói rằng, phương thức đó được thiết lập để được gọi trước khi menu được hiển thị, tôi cần lắng nghe và kích hoạt yêu cầu không đồng bộ, sau đó cập nhật menu trong khi nó đang mở và tôi không thể biết được .Apple cập nhật trình đơn Sân bay khi nó đang mở như thế nào? (Làm thế nào để thay đổi NSMenu khi nó đã được mở)

Cảm ơn :)

EDIT

Ok, Tôi bây giờ ở đây:

Khi bạn nhấp vào mục menu (trên thanh trạng thái), một selector được gọi là chạy một NSTask. Tôi sử dụng trung tâm thông báo để lắng nghe khi nhiệm vụ được hoàn tất, và viết:

[[NSRunLoop currentRunLoop] performSelector:@selector(updateTheMenu:) target:self argument:statusBarMenu order:0 modes:[NSArray arrayWithObject:NSEventTrackingRunLoopMode]]; 

và có:

- (void)updateTheMenu:(NSMenu*)menu { 
    NSMenuItem *mitm = [[NSMenuItem alloc] init]; 
    [mitm setEnabled:NO]; 
    [mitm setTitle:@"Bananas"]; 
    [mitm setIndentationLevel:2]; 
    [menu insertItem:mitm atIndex:2]; 
    [mitm release]; 
} 

Phương pháp này chắc chắn được gọi là bởi vì nếu tôi bấm ra khỏi menu và ngay lập tức trở lại vào nó, tôi nhận được một menu cập nhật với thông tin này trong đó. Vấn đề là nó không được cập nhật trong khi menu đang mở.

Trả lời

13

Vấn đề ở đây là bạn cần phải gọi lại của bạn để kích hoạt ngay cả trong chế độ theo dõi menu.

Ví dụ: - [NSTask waitUntilExit] "thăm dò vòng lặp chạy hiện tại bằng NSDefaultRunLoopMode cho đến khi tác vụ hoàn tất". Điều này có nghĩa là nó sẽ không được chạy cho đến khi menu đóng lại. Tại thời điểm đó, việc lập lịch cập nhậtTheMenu để chạy trên NSCommonRunLoopMode không giúp được gì - sau cùng thì không thể quay trở lại. Tôi tin rằng các nhà quan sát NSNotificationCenter cũng chỉ kích hoạt trong NSDefaultRunLoopMode.

Nếu bạn có thể tìm thấy một số cách để lên lịch cuộc gọi lại được chạy ngay cả trong chế độ theo dõi menu, bạn đã đặt; bạn chỉ có thể gọi updateTheMenu trực tiếp từ cuộc gọi lại đó.

- (void)updateTheMenu { 
    static BOOL flip = NO; 
    NSMenu *filemenu = [[[NSApp mainMenu] itemAtIndex:1] submenu]; 
    if (flip) { 
    [filemenu removeItemAtIndex:[filemenu numberOfItems] - 1]; 
    } else { 
    [filemenu addItemWithTitle:@"Now you see me" action:nil keyEquivalent:@""]; 
    } 
    flip = !flip; 
} 

- (void)applicationDidFinishLaunching:(NSNotification *)aNotification { 
    NSTimer *timer = [NSTimer timerWithTimeInterval:0.5 
              target:self 
             selector:@selector(updateTheMenu) 
             userInfo:nil 
              repeats:YES]; 
    [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes]; 
} 

Chạy trình đơn này và giữ menu Tệp và bạn sẽ thấy mục menu phụ xuất hiện và biến mất sau mỗi nửa giây. Rõ ràng "mỗi nửa giây" không phải là những gì bạn đang tìm kiếm, và NSTimer không hiểu "khi nhiệm vụ nền của tôi kết thúc". Nhưng có thể có một số cơ chế đơn giản như nhau mà bạn có thể sử dụng.

Nếu không, bạn có thể tự xây dựng nó ra khỏi một trong các lớp con NSPort — ví dụ: tạo NSMessagePort và ghi NSTask của bạn vào khi hoàn tất.

Trường hợp duy nhất bạn thực sự cần phải lên lịch cập nhật một cách rõ ràngTheMenu cách Rob Keniger mô tả ở trên là nếu bạn đang cố gắng gọi nó từ bên ngoài vòng lặp chạy. Ví dụ, bạn có thể sinh ra một luồng phát ra một tiến trình con và gọi waitpid (nó sẽ khóa cho đến khi quá trình được thực hiện), sau đó luồng đó sẽ phải gọi phương thức performSelector: target: arguments: order: thay vì gọi updateTheMenu trực tiếp.

+1

Bất chấp thời gian dài từ câu hỏi, tôi vẫn tò mò về điều này và tôi rất vui khi thấy tại sao những gì tôi đã không làm việc. Cảm ơn bạn! – Aaron

13

(Nếu bạn muốn thay đổi bố cục của menu, tương tự như cách menu Sân bay hiển thị thêm thông tin khi bạn chọn tùy chọn, hãy tiếp tục đọc. Nếu bạn muốn làm điều gì đó hoàn toàn khác, thì câu trả lời này có thể không có liên quan như bạn muốn.)

Khóa là -[NSMenuItem setAlternate:]. Ví dụ, giả sử chúng ta sẽ xây dựng một NSMenu có hành động Do something... trong đó. Bạn sẽ viết mã đó lên như một cái gì đó như:

NSMenu * m = [[NSMenu alloc] init]; 

NSMenuItem * doSomethingPrompt = [m addItemWithTitle:@"Do something..." action:@selector(doSomethingPrompt:) keyEquivalent:@"d"]; 
[doSomethingPrompt setTarget:self]; 
[doSomethingPrompt setKeyEquivalentModifierMask:NSShiftKeyMask]; 

NSMenuItem * doSomething = [m addItemWithTitle:@"Do something" action:@selector(doSomething:) keyEquivalent:@"d"]; 
[doSomething setTarget:self]; 
[doSomething setKeyEquivalentModifierMask:(NSShiftKeyMask | NSAlternateKeyMask)]; 
[doSomething setAlternate:YES]; 

//do something with m 

Bây giờ, bạn sẽ nghĩ rằng sẽ tạo ra một menu với hai mục trong đó: "Làm điều gì đó ..." và "Làm điều gì đó", và bạn 'd được một phần đúng. Bởi vì chúng ta đặt mục menu thứ hai là một mục thay thế, và vì cả hai mục menu có cùng một khóa tương đương (nhưng các mặt nạ sửa đổi khác nhau), thì chỉ có mặt hàng đầu tiên (ví dụ, mặc định là setAlternate:NO) sẽ hiển thị. Sau đó, khi bạn mở menu, nếu bạn nhấn mặt nạ sửa đổi đại diện cho mặt nạ thứ hai (tức là phím tùy chọn), thì mục menu sẽ biến đổi theo thời gian thực từ mục menu đầu tiên sang mục thứ hai.

Ví dụ: cách thức hoạt động của menu Apple. Nếu bạn nhấp một lần vào nó, bạn sẽ thấy một vài tùy chọn với dấu ba chấm sau chúng, chẳng hạn như "Khởi động lại ..." và "Tắt máy ...". HIG quy định rằng nếu có dấu ba chấm, nó có nghĩa là hệ thống sẽ nhắc người dùng xác nhận trước khi thực hiện hành động. Tuy nhiên, nếu bạn bấm phím tùy chọn (với menu vẫn mở), bạn sẽ thấy chúng thay đổi thành "Khởi động lại" và "Tắt máy". Dấu ba chấm biến mất, có nghĩa là nếu bạn chọn chúng trong khi phím tùy chọn được nhấn xuống, chúng sẽ thực thi ngay lập tức mà không nhắc người dùng xác nhận.

Chức năng chung tương tự cũng đúng cho các menu trong các mục trạng thái. Bạn có thể có thông tin mở rộng là các mục "thay thế" đối với thông tin thông thường chỉ hiển thị bằng phím tùy chọn được nhấn. Một khi bạn hiểu được nguyên tắc cơ bản, nó thực sự khá dễ thực hiện mà không cần nhiều thủ thuật.

+4

Thông tin vô cùng hữu ích, mặc dù không phải những gì tôi đang thực hiện. Tôi chắc chắn sẽ tìm thấy một sử dụng cho nó mặc dù. Tôi thực sự sau khi trình đơn tiêm các mạng mà nó tìm thấy trong khi nó đang mở. Cảm ơn – Aaron

16

Theo dõi chuột của menu được thực hiện ở chế độ vòng lặp chạy đặc biệt (NSEventTrackingRunLoopMode).Để sửa đổi menu, bạn cần gửi một tin nhắn để nó sẽ được xử lý trong chế độ theo dõi sự kiện. Cách đơn giản nhất để làm điều này là sử dụng phương pháp này NSRunLoop:

[[NSRunLoop currentRunLoop] performSelector:@selector(updateTheMenu:) target:self argument:yourMenu order:0 modes:[NSArray arrayWithObject:NSEventTrackingRunLoopMode]] 

Bạn cũng có thể chỉ định các chế độ như NSRunLoopCommonModes và thông điệp sẽ được gửi trong bất kỳ chế độ chạy vòng lặp thông thường, bao gồm NSEventTrackingRunLoopMode.

phương pháp cập nhật của bạn sau đó sẽ làm một cái gì đó như thế này:

- (void)updateTheMenu:(NSMenu*)menu 
{ 
    [menu addItemWithTitle:@"Foobar" action:NULL keyEquivalent:@""]; 
    [menu update]; 
} 
+0

Gonna cho shot này tối nay, cảm ơn! – Aaron

+0

Điều này dường như không hoạt động. Tôi yêu cầu dữ liệu của mình qua NSTask, đợi thông báo, khi nhận được thông báo đó, tôi điền một đối tượng dữ liệu có thể truy cập được vào toàn bộ lớp và gọi đường NSRunLoop của bạn gọi phương thức updateTheMenu. Trình đơn không cập nhật trực tiếp tuy nhiên, tôi phải bấm vào nó và sau đó mở lại nó trước khi thông tin cập nhật xuất hiện. – Aaron

+1

Nếu bạn sử dụng 'NSRunLoopCommonModes' thay vì' NSEventTrackingRunLoopMode' thì nó có hoạt động không? –

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