等待[NSAlert beginSheetModalForWindow:...];

dre*_*lax 19 macos cocoa alerts document-based

当我显示这样的NSAlert时,我立刻得到了回复:

int response;
NSAlert *alert = [NSAlert alertWithMessageText:... ...];
response = [alert runModal];
Run Code Online (Sandbox Code Playgroud)

问题是这是应用程序模式,我的应用程序是基于文档的.我使用工作表在当前文档的窗口中显示警报,如下所示:

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;
}
Run Code Online (Sandbox Code Playgroud)

唯一的问题是beginSheetModalForWindow:直接返回,所以我无法可靠地询问用户问题并等待回复.如果我可以将任务分成两个区域,那么这不会是一件大事,但我不能.

我有一个循环来处理大约40个不同的对象(在树中).如果一个对象出现故障,我希望警报显示并询问用户是继续还是中止(继续在当前分支处理),但由于我的应用程序是基于文档的,因此Apple人机界面指南要求在警报为时使用工作表特定于文件.

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

Phi*_*ipp 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];
Run Code Online (Sandbox Code Playgroud)

代码可以通过GitHub获得,并且下面发布了当前版本的完整性.


头文件NSAlert+SynchronousSheet.h:

#import <Cocoa/Cocoa.h>


@interface NSAlert (SynchronousSheet)

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

@end
Run Code Online (Sandbox Code Playgroud)

实施文件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
Run Code Online (Sandbox Code Playgroud)


Fre*_*man 7

解决方案是打电话

[NSApp runModalForWindow:alert];
Run Code Online (Sandbox Code Playgroud)

在beginSheetModalForWindow之后.此外,您需要实现捕获"对话框已关闭"操作的委托,并调用[NSApp stopModal]作为响应.


Lau*_*ent 7

这里是解决这一问题的NSAlert类别(如通过与菲利普·弗雷德里克提出,由洛朗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
Run Code Online (Sandbox Code Playgroud)


mjm*_*jmt 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
Run Code Online (Sandbox Code Playgroud)

然后同步运行NSAlert就像:

AlertSync* sync = [[AlertSync alloc] initWithAlert: alert asSheetForWindow: window];
int returnCode = [sync run];
[sync release];
Run Code Online (Sandbox Code Playgroud)

请注意,如所讨论的那样存在重新引发问题的可能性,因此如果这样做,请小心.


Jas*_*oco 3

不幸的是,您在这里无能为力。您基本上必须做出决定:重新架构您的应用程序,以便它可以以异步方式处理对象,或者使用未经批准、已弃用的架构来呈现应用程序模式警报。

如果不知道有关您的实际设计以及如何处理这些对象的任何信息,则很难提供任何进一步的信息。不过,我的脑海中浮现出一些想法可能是:

  • 通过某种运行循环信号或队列与主线程通信的另一个线程中处理对象。如果窗口的对象树被中断,它会向主线程发出信号,表明它已被中断,并等待来自主线程的信号,其中包含有关要执行的操作的信息(继续此分支或中止)。然后,主线程会显示文档模式窗口,并在用户选择要执行的操作后向进程线程发出信号。

然而,这对于您的需要来说可能确实过于复杂。在这种情况下,我的建议是仅使用已弃用的用法,但这实际上取决于您的用户需求。