21

このように NSAlert を表示すると、すぐに応答が返ってきます。

int response;
NSAlert *alert = [NSAlert alertWithMessageText:... ...];
response = [alert runModal];

問題は、これがアプリケーション モーダルであり、私のアプリケーションがドキュメント ベースであることです。次のように、シートを使用して現在のドキュメントのウィンドウにアラートを表示します。

int response;
NSAlert *alert = [NSAlert alertWithMessageText:... ...];
[alert beginSheetModalForWindow:aWindow
                  modalDelegate:self
                 didEndSelector:@selector(alertDidEnd:returnCode:contextInfo:)
                    contextInfo:&response];

//elsewhere
- (void) alertDidEnd:(NSAlert *) alert returnCode:(int) returnCode contextInfo:(int *) contextInfo
{
    *contextInfo = returnCode;
}

これに関する唯一の問題は、beginSheetModalForWindow:すぐに返されるため、ユーザーに確実に質問して応答を待つことができないことです。タスクを 2 つの領域に分割できれば大したことではありませんが、できません。

(ツリー内にある) 約 40 の異なるオブジェクトを処理するループがあります。1 つのオブジェクトが失敗した場合、アラートを表示してユーザーに続行するか中止する (現在のブランチで処理を続行する) かを尋ねたいのですが、私のアプリケーションはドキュメント ベースであるため、Apple ヒューマン インターフェイス ガイドラインでは、アラートが発生した場合はシートを使用するように指示されています。ドキュメントに固有です。

アラート シートを表示して応答を待つにはどうすればよいですか?

4

12 に答える 12

14

アプリケーション モーダル ダイアログと同様に、アラートを同期的に実行するためのカテゴリNSAlertを作成しました。

NSInteger result;

// Run the alert as a sheet on the main window
result = [alert runModalSheet];

// Run the alert as a sheet on some other window
result = [alert runModalSheetForWindow:window];

コードはGitHubから入手でき、完全を期すために現在のバージョンが以下に掲載されています。


ヘッダファイルNSAlert+SynchronousSheet.h:

#import <Cocoa/Cocoa.h>


@interface NSAlert (SynchronousSheet)

-(NSInteger) runModalSheetForWindow:(NSWindow *)aWindow;
-(NSInteger) runModalSheet;

@end

実装ファイルNSAlert+SynchronousSheet.m:

#import "NSAlert+SynchronousSheet.h"


// Private methods -- use prefixes to avoid collisions with Apple's methods
@interface NSAlert ()
-(IBAction) BE_stopSynchronousSheet:(id)sender;   // hide sheet & stop modal
-(void) BE_beginSheetModalForWindow:(NSWindow *)aWindow;
@end


@implementation NSAlert (SynchronousSheet)

-(NSInteger) runModalSheetForWindow:(NSWindow *)aWindow {
    // Set ourselves as the target for button clicks
    for (NSButton *button in [self buttons]) {
        [button setTarget:self];
        [button setAction:@selector(BE_stopSynchronousSheet:)];
    }

    // Bring up the sheet and wait until stopSynchronousSheet is triggered by a button click
    [self performSelectorOnMainThread:@selector(BE_beginSheetModalForWindow:) withObject:aWindow waitUntilDone:YES];
    NSInteger modalCode = [NSApp runModalForWindow:[self window]];

    // This is called only after stopSynchronousSheet is called (that is,
    // one of the buttons is clicked)
    [NSApp performSelectorOnMainThread:@selector(endSheet:) withObject:[self window] waitUntilDone:YES];

    // Remove the sheet from the screen
    [[self window] performSelectorOnMainThread:@selector(orderOut:) withObject:self waitUntilDone:YES];

    return modalCode;
}

-(NSInteger) runModalSheet {
    return [self runModalSheetForWindow:[NSApp mainWindow]];
}


#pragma mark Private methods

-(IBAction) BE_stopSynchronousSheet:(id)sender {
    // See which of the buttons was clicked
    NSUInteger clickedButtonIndex = [[self buttons] indexOfObject:sender];

    // Be consistent with Apple's documentation (see NSAlert's addButtonWithTitle) so that
    // the fourth button is numbered NSAlertThirdButtonReturn + 1, and so on
    NSInteger modalCode = 0;
    if (clickedButtonIndex == NSAlertFirstButtonReturn)
        modalCode = NSAlertFirstButtonReturn;
    else if (clickedButtonIndex == NSAlertSecondButtonReturn)
        modalCode = NSAlertSecondButtonReturn;
    else if (clickedButtonIndex == NSAlertThirdButtonReturn)
        modalCode = NSAlertThirdButtonReturn;
    else
        modalCode = NSAlertThirdButtonReturn + (clickedButtonIndex - 2);

    [NSApp stopModalWithCode:modalCode];
}

-(void) BE_beginSheetModalForWindow:(NSWindow *)aWindow {
    [self beginSheetModalForWindow:aWindow modalDelegate:nil didEndSelector:nil contextInfo:nil];
}

@end
于 2011-06-10T16:31:36.217 に答える
8

解決策は電話することです

[NSApp runModalForWindow:alert];

beginSheetModalForWindow の後。また、「ダイアログが閉じられました」アクションをキャッチし、それに応じて [NSApp stopModal] を呼び出すデリゲートを実装する必要があります。

于 2010-04-06T18:37:54.677 に答える
5

誰かがこれを探しに来た場合に備えて(私はそうしました)、次の方法でこれを解決しました:

@interface AlertSync: NSObject {
    NSInteger returnCode;
}

- (id) initWithAlert: (NSAlert*) alert asSheetForWindow: (NSWindow*) window;
- (NSInteger) run;

@end

@implementation AlertSync
- (id) initWithAlert: (NSAlert*) alert asSheetForWindow: (NSWindow*) window {
    self = [super init];

    [alert beginSheetModalForWindow: window
           modalDelegate: self didEndSelector: @selector(alertDidEnd:returnCode:) contextInfo: NULL];

    return self;
}

- (NSInteger) run {
    [[NSApplication sharedApplication] run];
    return returnCode;
}

- (void) alertDidEnd: (NSAlert*) alert returnCode: (NSInteger) aReturnCode {
    returnCode = aReturnCode;
    [[NSApplication sharedApplication] stopModal];
}
@end

次に、NSAlert を同期的に実行するのは次のように簡単です。

AlertSync* sync = [[AlertSync alloc] initWithAlert: alert asSheetForWindow: window];
int returnCode = [sync run];
[sync release];

説明したように再入可能性の問題が発生する可能性があるため、これを行う場合は注意してください。

于 2010-01-25T01:33:17.623 に答える
3

残念ながら、ここでできることはあまりありません。基本的に決定を下す必要があります。オブジェクトを非同期で処理できるようにアプリケーションを再構築するか、アプリケーションのモーダル アラートを表示する未承認の非推奨のアーキテクチャを使用するかです。

実際の設計とこれらのオブジェクトの処理方法に関する情報がなければ、それ以上の情報を提供することは困難です。ただし、私の頭の上では、次のような考えがいくつかあります。

  • ある種の実行ループ シグナルまたはキューを介してメイン スレッドと通信する別のスレッドでオブジェクトを処理します。ウィンドウのオブジェクト ツリーが中断されると、中断されたことをメイン スレッドに通知し、何をすべきか (この分岐を続行するか中止するか) に関する情報を含むメイン スレッドからのシグナルを待機します。次に、メイン スレッドはドキュメント モーダル ウィンドウを表示し、ユーザーが何をすべきかを選択した後、プロセス スレッドにシグナルを送ります。

ただし、これは必要なものに対して非常に複雑になる可能性があります。その場合、非推奨の使用方法をそのまま使用することをお勧めしますが、実際にはユーザーの要件によって異なります。

于 2009-03-03T02:29:21.027 に答える
1

ここに私の答えがあります:

グローバルクラス変数「NSInteger alertReturnStatus」を作成します

- (void)alertDidEndSheet:(NSWindow *)sheet returnCode:(NSInteger)returnCode contextInfo:(void *)contextInfo
{
    [[sheet window] orderOut:self];
    // make the returnCode publicly available after closing the sheet
    alertReturnStatus = returnCode;
}


- (BOOL)testSomething
{

    if(2 != 3) {

        // Init the return value
        alertReturnStatus = -1;

        NSAlert *alert = [[[NSAlert alloc] init] autorelease];
        [alert addButtonWithTitle:@"OK"];
        [alert addButtonWithTitle:@"Cancel"];
        [alert setMessageText:NSLocalizedString(@"Warning", @"warning")];
        [alert setInformativeText:@"Press OK for OK"];
        [alert setAlertStyle:NSWarningAlertStyle];
        [alert setShowsHelp:NO];
        [alert setShowsSuppressionButton:NO];

        [alert beginSheetModalForWindow:[self window] modalDelegate:self didEndSelector:@selector(alertDidEndSheet:returnCode:contextInfo:) contextInfo:nil];

        // wait for the sheet
        NSModalSession session = [NSApp beginModalSessionForWindow:[alert window]];
        for (;;) {
            // alertReturnStatus will be set in alertDidEndSheet:returnCode:contextInfo:
            if(alertReturnStatus != -1)
                break;

            // Execute code on DefaultRunLoop
            [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode 
                                     beforeDate:[NSDate distantFuture]];

            // Break the run loop if sheet was closed
            if ([NSApp runModalSession:session] != NSRunContinuesResponse 
                || ![[alert window] isVisible]) 
                break;

            // Execute code on DefaultRunLoop
            [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode 
                                     beforeDate:[NSDate distantFuture]];

        }
        [NSApp endModalSession:session];
        [NSApp endSheet:[alert window]];

        // Check the returnCode by using the global variable alertReturnStatus
        if(alertReturnStatus == NSAlertFirstButtonReturn) {
            return YES;
        }

        return NO;
    }
    return YES;
}

お役に立てば幸いです、乾杯 -- ハンス

于 2010-08-18T15:48:24.307 に答える
0
- (bool) windowShouldClose: (id) sender
 {// printf("windowShouldClose..........\n");
  NSAlert *alert=[[NSAlert alloc ]init];
  [alert setMessageText:@"save file before closing?"];
  [alert setInformativeText:@"voorkom verlies van laatste wijzigingen"];
  [alert addButtonWithTitle:@"save"];
  [alert addButtonWithTitle:@"Quit"];
  [alert addButtonWithTitle:@"cancel"];
  [alert beginSheetModalForWindow: _window modalDelegate: self
              didEndSelector: @selector(alertDidEnd: returnCode: contextInfo:)
                 contextInfo: nil];
  return false;
}
于 2012-10-06T07:56:46.243 に答える
0

Windows とは異なり、モーダル ダイアログをブロックする方法はないと思います。入力 (ユーザーがボタンをクリックするなど) はメイン スレッドで処理されるため、ブロックする方法はありません。

あなたのタスクでは、メッセージをスタックに渡してから、中断したところから続行する必要があります。

于 2009-03-03T02:03:36.643 に答える
0

1 つのオブジェクトが失敗したら、ツリー内のオブジェクトの処理を停止し、どのオブジェクトが失敗したかを書き留めて (順序があり、中断したところから再開できると仮定して)、シートを破棄します。ユーザーがシートをdidEndSelector:閉じると、returnCode.

于 2009-03-03T13:09:10.443 に答える
0

使用できますdispatch_group_wait(group, DISPATCH_TIME_FOREVER);

dispatch_group_t group = dispatch_group_create();
dispatch_group_enter(group);

NSAlert *alert = [[NSAlert alloc] init];
[alert setMessageText:@"alertMessage"];
[alert addButtonWithTitle:@"Cancel"];
[alert addButtonWithTitle:@"Ok"];

dispatch_async(dispatch_get_main_queue(), ^{
    [alert beginSheetModalForWindow:progressController.window completionHandler:^(NSModalResponse returnCode) {
         if (returnCode == NSAlertSecondButtonReturn) {
             // do something when the user clicks Ok

         } else {
             // do something when the user clicks Cancel
         }

         dispatch_group_leave(group);
     }];
});

dispatch_group_wait(group, DISPATCH_TIME_FOREVER);

//you can continue your code here

それが役立つことを願っています。

于 2018-09-24T13:07:08.133 に答える