代码之家  ›  专栏  ›  技术社区  ›  dreamlax

等待[NSAlert beginSheetModalForWindow:…];

  •  18
  • dreamlax  · 技术社区  · 16 年前

    当我像这样显示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: 立即返回,因此我无法可靠地向用户提问并等待响应。如果我能将任务分为两个方面,这并不是什么大不了的事,但我不能。

    如何显示警报表并等待响应?

    11 回复  |  直到 16 年前
        1
  •  14
  •   Philipp    14 年前

    我们创建了一个 category on NSAlert to run alerts synchronously ,就像应用程序模式对话框一样:

    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
    
        2
  •  8
  •   Frederik Slijkerman    13 年前

    解决办法是打电话

    [NSApp runModalForWindow:alert];
    

        3
  •  8
  •   Laurent    11 年前

    下面是一个NSAlert类别,它解决了这个问题(正如Philipp所建议的由Frederick提出并由Laurent P改进的解决方案:我使用一个代码块而不是委托,因此它再次被简化)。

    @implementation NSAlert (Cat)
    
    -(NSInteger) runModalSheetForWindow:(NSWindow *)aWindow
    {
        [self beginSheetModalForWindow:aWindow completionHandler:^(NSModalResponse returnCode)
            { [NSApp stopModalWithCode:returnCode]; } ];
        NSInteger modalCode = [NSApp runModalForWindow:[self window]];
        return modalCode;
    }
    
    -(NSInteger) runModalSheet {
        return [self runModalSheetForWindow:[NSApp mainWindow]];
    }
    
    @end
    
        4
  •  5
  •   mjmt    15 年前

    为了防止有人来找这个(我找到了),我用以下方法解决了这个问题:

    @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];
    

    请注意,正如所讨论的,存在重新进入问题的可能性,因此在进行此操作时要小心。

        5
  •  3
  •   Jason Coco superfell    16 年前

    然而,对于你需要的东西来说,这可能真的太复杂了。在这种情况下,我的建议是只使用不推荐的用法,但这实际上取决于您的用户需求。

        6
  •  2
  •   Vadim    5 年前

    extension NSAlert {
    
      /// Runs this alert as a sheet.
      /// - Parameter sheetWindow: Parent window for the sheet.
      func runSheetModal(for sheetWindow: NSWindow) -> NSApplication.ModalResponse {
        beginSheetModal(for: sheetWindow, completionHandler: NSApp.stopModal(withCode:))
        return NSApp.runModal(for: sheetWindow)
      }
    }
    
        7
  •  1
  •   Bibiko    14 年前

    创建全局类变量“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;
    }
    

    希望能有所帮助, --汉斯

        8
  •  1
  •   Michael L. Mehr    9 年前

    这是上面Laurent等人的版本,已翻译成适用于Xcode 6.4的Swift 1.2(截至今天的最新工作版本),并在我的应用程序中进行了测试。感谢所有为这项工作做出贡献的人!苹果公司的标准文档没有给我任何关于如何进行这项工作的线索,至少在我能找到的任何地方都没有。

    还有一个谜团留给我:为什么我必须在最后一个函数中使用双感叹号。NSApplication.mainWindow应该只是一个可选的NSWindow(NSWindow?),对吗?但是编译器给出了显示的错误,直到我使用第二个“!”为止。

    extension NSAlert {
        func runModalSheetForWindow( aWindow: NSWindow ) -> Int {
            self.beginSheetModalForWindow(aWindow) { returnCode in
                NSApp.stopModalWithCode(returnCode)
            }
            let modalCode = NSApp.runModalForWindow(self.window as! NSWindow)
            return modalCode
        }
    
        func runModalSheet() -> Int {
            // Swift 1.2 gives the following error if only using one '!' below:
            // Value of optional type 'NSWindow?' not unwrapped; did you mean to use '!' or '?'?
            return runModalSheetForWindow(NSApp.mainWindow!!)
        }
    }
    
        9
  •  0
  •   Andrew Grant    16 年前

    与Windows不同,我不相信有一种方法可以阻止模态对话框。输入(例如,用户单击按钮)将在主线程上处理,因此无法阻止。

        10
  •  0
  •   erikprice    16 年前

    当一个对象出现故障时,停止处理树中的对象,记下哪个对象出现故障(假设有一个订单,您可以从您停止的地方继续),然后扔掉工作表。当用户撤销工作表时,请 didEndSelector: 方法从它离开的对象重新开始处理,或不开始处理,具体取决于 returnCode .

        11
  •  0
  •   Cyrille    12 年前
    - (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;
    }
    
        12
  •  0
  •   Marcelo Sarquis    6 年前

    你可以用 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
    

    希望有帮助。