36

NSMenu を開くステータスバー項目があり、デリゲート セットがあり、正しく接続されています (正常に動作し-(void)menuNeedsUpdate:(NSMenu *)menuます)。とは言っても、そのメソッドはメニューが表示される前に呼び出されるように設定されています。それをリッスンして非同期リクエストをトリガーし、後でメニューが開いている間に更新する必要がありますが、それがどのように行われるのかわかりません.

ありがとう :)

編集

わかりました、私は今ここにいます:

メニュー項目 (ステータス バー内) をクリックすると、NSTask を実行するセレクターが呼び出されます。通知センターを使用して、そのタスクがいつ終了するかをリッスンし、次のように記述します。

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

そして持っています:

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

メニューをクリックしてすぐにメニューに戻ると、この情報を含む更新されたメニューが表示されるため、このメソッドは確実に呼び出されます。問題は、メニューが開いている間は更新されないことです。

4

3 に答える 3

18

メニューのマウス トラッキングは、特別な実行ループ モード ( NSEventTrackingRunLoopMode) で行われます。メニューを変更するには、イベント追跡モードで処理されるようにメッセージをディスパッチする必要があります。これを行う最も簡単な方法は、次の方法を使用することですNSRunLoop

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

モードを として指定することもできますNSRunLoopCommonModes。メッセージは、 を含む一般的な実行ループ モードのいずれかで送信されますNSEventTrackingRunLoopMode

update メソッドは次のようになります。

- (void)updateTheMenu:(NSMenu*)menu
{
    [menu addItemWithTitle:@"Foobar" action:NULL keyEquivalent:@""];
    [menu update];
}
于 2010-05-11T05:08:19.747 に答える
14

(メニューのレイアウトを変更したい場合は、オプションをクリックすると空港メニューが詳細情報を表示するのと同様に、読み続けてください。まったく別のことをしたい場合は、この回答はあなたほど関連性がないかもしれませんしたい。)

キーは-[NSMenuItem setAlternate:]です。例として、アクションを含む を作成するNSMenuDo something...します。次のようにコーディングします。

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

これで、"Do something..." と "Do something" という 2 つの項目を含むメニューが作成されると思われるかもしれませんが、それは部分的に正しいでしょう。2 番目のメニュー項目を別のメニュー項目に設定し、両方のメニュー項目の同等のキーが同じ (ただし修飾マスクが異なる) ため、最初の項目 (つまり、デフォルトである項目) のみsetAlternate:NOが表示されます。次に、メニューを開いているときに、2 番目の修飾マスク (つまり、オプション キー) を表す修飾マスクを押すと、メニュー項目が最初のメニュー項目から 2 番目のメニュー項目にリアルタイムで変換されます。

たとえば、これが Apple メニューの仕組みです。一度クリックすると、「再起動...」や「シャットダウン...」など、省略記号が付いたいくつかのオプションが表示されます。HIG は、省略記号がある場合、アクションを実行する前にシステムがユーザーに確認を求めることを意味することを指定します。ただし、(メニューを開いたまま) オプション キーを押すと、「再起動」と「シャットダウン」に変わることに気付くでしょう。省略記号は消えます。つまり、オプション キーを押したまま省略記号を選択すると、ユーザーに確認を求めずにすぐに実行されます。

ステータス項目のメニューについても、同じ一般的な機能が当てはまります。展開された情報を、オプション キーを押したときにのみ表示される通常の情報に「代替」項目にすることができます。基本原理を理解すれば、多くのトリックを使わずに実際に実装するのは非常に簡単です。

于 2010-05-11T04:55:37.847 に答える
13

ここでの問題は、メニュー トラッキング モードでもコールバックをトリガーする必要があることです。

たとえば、-[NSTask waitUntilExit] は、「タスクが完了するまで、NSDefaultRunLoopMode を使用して現在の実行ループをポーリングします」。これは、メニューが閉じるまで実行されないことを意味します。その時点で、updateTheMenu を NSCommonRunLoopMode で実行するようにスケジュールしても役に立ちません。結局のところ、時間を遡ることはできません。NSNotificationCenter オブザーバーも NSDefaultRunLoopMode でのみトリガーされると思います。

メニュー追跡モードでも実行されるコールバックをスケジュールする方法を見つけることができれば、準備は完了です。そのコールバックから updateTheMenu を直接呼び出すことができます。

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

これを実行して [ファイル] メニューを押したままにすると、追加のメニュー項目が 0.5 秒ごとに表示および非表示になります。明らかに、「0.5 秒ごと」は探しているものではなく、NSTimer は「バックグラウンド タスクがいつ終了するか」を理解していません。しかし、同じように単純なメカニズムを使用できる場合もあります。

そうでない場合は、NSPort サブクラスの 1 つから自分で構築できます。たとえば、NSMessagePort を作成し、完了したら NSTask にそのポートに書き込みます。

上記の Rob Keniger の方法で updateTheMenu を明示的にスケジュールする必要がある唯一のケースは、実行ループの外から呼び出そうとする場合です。たとえば、子プロセスを起動して waitpid を呼び出すスレッドを生成できます (プロセスが完了するまでブロックされます)。その場合、そのスレッドは updateTheMenu を直接呼び出す代わりに performSelector:target:argument:order:modes: を呼び出す必要があります。

于 2012-04-25T23:56:40.600 に答える