Pergunta

Quando eu exibir um NSAlert como este, eu obter a resposta imediatamente:

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

O problema é que este é aplicação-modal e meu pedido é documento base. I exibe o alerta na janela do documento atual usando folhas, como esta:

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

O único problema com isto é que beginSheetModalForWindow: retorna imediatamente para que eu não posso pedir de forma confiável ao usuário uma pergunta e esperar por uma resposta. Isso não seria um grande negócio se eu poderia dividir a tarefa em duas áreas, mas eu não posso.

Eu tenho um loop que processos de cerca de 40 objetos diferentes (que estão em uma árvore). Se um objeto falhar, eu quero que o alerta para mostrar e perguntar ao usuário se deseja continuar ou cancelar (continuar processando na filial atual), mas desde a minha aplicação é baseada documento, os Human Interface Guidelines da Apple ditar usar folhas quando o alerta é específica a um documento.

Como posso exibir a folha de alerta e esperar por uma resposta?

Foi útil?

Solução

Infelizmente, não há muito que você pode fazer aqui. Você basicamente tem que tomar uma decisão:. Re-arquitetar seu aplicativo para que ele possa processar o objeto de forma assíncrona ou usar o não aprovada, depreciado arquitetura de apresentar alertas modais de aplicação

Sem saber qualquer informação sobre o seu projeto real e como você processa esses objetos, é difícil dar qualquer informação adicional. Em cima da minha cabeça, no entanto, um par de pensamentos podem ser:

  • Processar os objectos no outro segmento que se comunica com o segmento principal através de algum tipo de sinal loop de execução ou fila. Se árvore de objetos da janela for interrompido, ele sinaliza o principal segmento que foi interrompido e espera em um sinal a partir do segmento principal com informações sobre o que fazer (continuar neste ramo ou abort). O thread principal em seguida, apresenta a janela e sinais de documento-modal do segmento do processo após os escolhe usuário o que fazer.

Isto pode ser realmente sobre-complicada para o que você precisa, no entanto. Nesse caso, a minha recomendação seria apenas ir com o uso obsoleto, mas realmente depende de suas necessidades do usuário.

Outras dicas

Nós criamos um em NSAlert para alertas serão executadas em sincronia , assim como diálogos de aplicação-modal:

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

O código está disponível via GitHub , ea versão atual postada abaixo para ser completo.


Header arquivo NSAlert+SynchronousSheet.h:

#import <Cocoa/Cocoa.h>


@interface NSAlert (SynchronousSheet)

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

@end

Arquivo de implementação 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

A solução é chamada

[NSApp runModalForWindow:alert];

depois beginSheetModalForWindow. Além disso, você precisa implementar um delegado que chama a caixa de diálogo "fechou" ação e chamadas [NSApp stopModal] em resposta.

Aqui está uma categoria NSAlert que resolve a questão (como sugerido por Philipp com a solução proposta por Frederick e melhorado por Laurent P .: Eu uso um bloco de código em vez de um delegado, por isso é simplificada, mais uma vez).

@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

Apenas no caso de alguém vem olhando para este (eu), eu resolvi isso com o seguinte:

@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

Em seguida, executando um NSAlert síncrona é tão simples como:

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

Note que há potencial para problemas de reentrada como discutido, por isso tome cuidado se fazer isso.

aqui está a minha resposta:

Criar uma variável de classe mundial '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;
}

A esperança que vai ser de alguma ajuda, Felicidades --Hans

Esta é a versão de Laurent, et al., Acima, traduzida em Swift 1,2 para 6,4 Xcode (última versão de trabalho a partir de hoje) e testados na minha aplicação. Graças a todos os que contribuíram para fazer este trabalho! A documentação padrão da Apple me deu nenhuma pista de como ir sobre isso, pelo menos não em qualquer lugar que eu poderia encontrar.

Um mistério permanece para mim: por que eu tive que usar o ponto de exclamação dupla na função final. NSApplication.mainWindow é suposto ser apenas um opcional NSWindow (NSWindow?), Certo? Mas o compilador deu o erro mostrado até que eu usei o segundo '!'.

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!!)
    }
}

Ao contrário do Windows eu não acredito que há uma maneira de bloquear em diálogos modais. A entrada (por exemplo, o usuário clicar em um botão) serão processados ??no seu segmento principal, então não há nenhuma maneira de bloquear.

Para a sua tarefa que você terá que passar a mensagem a pilha e depois continuar de onde parou.

Quando um objeto falhar, parar de processar os objetos na árvore, fazer uma nota de qual objeto falhou (assumindo que há uma ordem e você pode pegar onde você parou), e vomitar a folha. Quando o usuário descarta a folha, ter o método didEndSelector: começar a processar novamente a partir do objeto que ele parou com, ou não, dependendo do returnCode.

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

Você pode usar 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

Espero que ajude.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top