2009-03-03 23 views
17

Cuando muestro un NSAlert como este, tengo la respuesta de inmediato:Espere a que [NSAlert beginSheetModalForWindow: ...];

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

El problema es que esta aplicación es modal y mi solicitud está basada documento. Puedo mostrar la alerta en la ventana del documento actual mediante el uso de hojas, así:

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

El único problema con esto es que beginSheetModalForWindow: vuelve enseguida, así que no se puede pedir de manera fiable al usuario una pregunta y esperar una respuesta. Esto no sería un gran problema si pudiera dividir la tarea en dos áreas, pero no puedo.

Tengo un ciclo que procesa aproximadamente 40 objetos diferentes (que están en un árbol). Si un objeto falla, quiero que se muestre la alerta y preguntar al usuario si continuar o cancelar (continuar procesando en la rama actual), pero dado que mi aplicación está basada en documentos, las pautas de interfaz humana de Apple determinan el uso de hojas cuando la alerta es específico de un documento.

¿Cómo puedo mostrar la hoja de alerta y esperar una respuesta?

Respuesta

3

Lamentablemente, no hay mucho que pueda hacer aquí. Básicamente, tiene que tomar una decisión: rediseñar su aplicación para que pueda procesar el objeto de manera asíncrona o utilizar la arquitectura no aprobada y obsoleta de presentar alertas modales de la aplicación.

Sin conocer ninguna información sobre su diseño real y cómo procesa estos objetos, es difícil proporcionar más información. De la parte superior de mi cabeza, sin embargo, un par de pensamientos podría ser:

  • procesar los objetos en otro hilo que se comunica con el hilo principal a través de algún tipo de señal de bucle de ejecución o cola. Si el árbol de objetos de la ventana se interrumpe, indica el hilo principal que fue interrumpido y espera una señal del hilo principal con información sobre qué hacer (continuar esta rama o abortar). Luego, el hilo principal presenta la ventana de documento-modal y señala el hilo del proceso una vez que el usuario elige qué hacer.

Esto puede ser muy complicado para lo que necesita, sin embargo. En ese caso, mi recomendación sería ir con el uso obsoleto, pero realmente depende de los requisitos de usuario.

+0

Threads, en última instancia, ser el camino a seguir, supongo. El árbol de objetos eventualmente se hará más grande y más complicado. – dreamlax

+0

Sin ver su aplicación, es obviamente difícil de decir, pero ¿está seguro de que necesita hilos?Nunca me he encontrado con el caso en que poner la respuesta en el método de devolución de llamada era más complejo que enhebrar la aplicación. –

0

A diferencia de Windows, no creo que haya una forma de bloquear los cuadros de diálogo modales. La entrada (por ejemplo, el usuario haciendo clic en un botón) se procesará en el hilo principal, por lo que no hay forma de bloquearla.

Para su tarea, tendrá que pasar el mensaje por la pila y continuar donde lo dejó.

+0

no es cierto, consulte mi respuesta –

0

Cuando un objeto falla, detenga el procesamiento de los objetos en el árbol, tome nota de qué objeto falló (suponiendo que haya un pedido y pueda continuar donde lo dejó) y arroje la hoja. Cuando el usuario descarta la hoja, haga que el método didEndSelector: comience a procesar nuevamente desde el objeto que dejó, o no, según el returnCode.

+0

Acabo de volver a leer su pregunta y me temo que con "No puedo dividir la tarea en dos áreas" está diciendo que esto no es posible. Lo siento si mi respuesta no es útil. – erikprice

5

Por si acaso alguno viene buscando este (lo hice), he resuelto esto con lo siguiente:

@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 

A continuación, ejecuta una NSAlert forma sincrónica es tan simple como:

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

en cuenta que hay El potencial de problemas de reentrada es tal como se discutió, así que tenga cuidado si lo hace.

8

La solución es llamar

[NSApp runModalForWindow:alert]; 

después beginSheetModalForWindow. Además, debe implementar un delegado que capte la acción de "diálogo cerrado" y llama [NSApp stopModal] en respuesta.

+0

Utilizo '[NSApp stopModalWithCode: returnCode];' para que 'runModalForWindow' obtenga el código correcto. –

+0

A partir del 10.9, -beginSheetModalForWindow: completionHandler: es preferible a usar un delegado. El 'stopModalWithCode: returnCode' de Laurent funciona en el bloque de finalización: el usuario hace clic, el bloque de finalización se ejecuta, luego returnCode es devuelto por' runModalForWindow'. –

1

aquí es mi respuesta:

Crear una variable de clase 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; 
} 

espero que sea de alguna ayuda, Saludos --Hans

14

Hemos creado a category on NSAlert to run alerts synchronously, al igual que los cuadros de diálogo de aplicación-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]; 

El código está disponible a través de GitHub, y la versión actual publicada a continuación está completa.


Archivo de cabecera NSAlert+SynchronousSheet.h:

#import <Cocoa/Cocoa.h> 


@interface NSAlert (SynchronousSheet) 

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

@end 

archivo de implementación 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 
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; 
} 
+0

¡Gracias por publicar una respuesta! Si bien un fragmento de código podría responder a la pregunta, sigue siendo genial agregar información adicional, como explicar, etc. – j0k

5

Aquí es una categoría NSAlert que resuelve el problema (según lo sugerido por Philipp con el s Solución propuesta por Frederick y mejorada por Laurent P .: Utilizo un bloque de código en lugar de un delegado, por lo que se simplifica una vez más).

@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 
+0

¡Perfecto! Solo la esencia de lo que se requiere para que funcione. Gracias. – Holtwick

0

Esta es la versión de Laurent, et al., Más arriba, traducido a Swift 1.2 para Xcode 6.4 (última versión de trabajo a partir de hoy) y probado en mi aplicación. ¡Gracias a todos los que contribuyeron para que esto funcione! La documentación estándar de Apple no me dio pistas sobre cómo hacerlo, al menos no en ninguna parte que pudiera encontrar.

Un misterio sigue siendo para mí: por qué tuve que usar el doble signo de exclamación en la función final. NSApplication.mainWindow se supone que es solo una NSWindow opcional (¿NSWindow?), ¿Verdad? Pero el compilador dio el error mostrado hasta que utilicé el 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!!) 
    } 
} 
Cuestiones relacionadas