2009-03-03 14 views
17

Quando ho visualizzare un NSAlert come questo, ottengo la risposta subito:Attendi [NSAlert beginSheetModalForWindow: ...];

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

Il problema è che questa è l'applicazione-modale e la mia domanda è documento basato. Visualizzo l'avviso nella finestra del documento corrente utilizzando fogli, in questo modo:

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

L'unico problema con questo è che beginSheetModalForWindow: rendimenti prova subito quindi non posso in modo affidabile chiedere all'utente una domanda e attendere una risposta. Questo non sarebbe un grosso problema se potessi dividere il compito in due aree, ma non posso.

Ho un ciclo che elabora circa 40 oggetti diversi (che si trovano in un albero). Se un oggetto fallisce, voglio che l'avviso mostri e chieda all'utente se continuare o interrompere (continuare l'elaborazione nel ramo corrente), ma poiché la mia applicazione è basata su documenti, le linee guida di Apple Human Interface dettano l'uso dei fogli quando l'avviso è specifico per un documento.

Come posso visualizzare il foglio di avviso e attendere una risposta?

risposta

3

Sfortunatamente, non c'è molto che tu possa fare qui. Fondamentalmente devi prendere una decisione: ri-progettare l'applicazione in modo che possa elaborare l'oggetto in modo asincrono o utilizzare l'architettura non approvata e deprecata di presentare avvisi modali dell'applicazione.

Senza conoscere alcuna informazione sul tuo progetto attuale e su come elabora questi oggetti, è difficile fornire ulteriori informazioni. Tuttavia, un paio di pensieri potrebbero essere:

  • Elaborare gli oggetti in un altro thread che comunica con il thread principale tramite una sorta di segnale di ciclo di esecuzione o coda. Se l'albero degli oggetti della finestra viene interrotto, segnala il thread principale che è stato interrotto e attende un segnale dal thread principale con informazioni su cosa fare (continuare questo ramo o interrompere). Il thread principale presenta quindi la finestra modale del documento e segnala il thread del processo dopo che l'utente ha scelto cosa fare.

Questo potrebbe essere davvero troppo complicato per quello che ti serve, comunque. In tal caso, la mia raccomandazione sarebbe di andare con l'uso deprecato, ma in realtà dipende dalle esigenze dell'utente.

+0

Thread in ultima analisi, la strada da percorrere, suppongo. L'albero degli oggetti alla fine diventerà più grande e più complicato. – dreamlax

+0

Senza vedere la tua app, è ovviamente difficile da dire, ma sei davvero sicuro di aver bisogno di discussioni?Non ho mai incontrato il caso in cui mettere la risposta nel metodo di callback era più complesso rispetto al threading dell'applicazione. –

0

A differenza di Windows, non credo che ci sia un modo per bloccare le finestre di dialogo modali. L'input (ad esempio l'utente che fa clic su un pulsante) verrà elaborato sul thread principale, quindi non c'è modo di bloccarlo.

Per il tuo compito dovrai passare il messaggio in cima allo stack e poi continuare da dove eri rimasto.

+0

non è vero, vedere la mia risposta –

0

Quando un oggetto fallisce, interrompe l'elaborazione degli oggetti nell'albero, annota quale oggetto non è riuscito (supponendo che ci sia un ordine e puoi riprendere da dove hai interrotto) e sollevare il foglio. Quando l'utente chiude il foglio, fare in modo che il metodo didEndSelector: ricominci di nuovo dall'oggetto con cui si è interrotto o meno, a seconda dello returnCode.

+0

Ho appena riletto la tua domanda e temo che con "Non posso dividere l'attività in due aree" stai dicendo che questo non è possibile. Scusa se la mia risposta non è utile. – erikprice

5

Solo nel caso qualcuno viene alla ricerca di questo (ho fatto io), ho risolto questo con il seguente:

@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 

Poi in esecuzione un NSAlert sincrono è così semplice come:

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

Nota lì è un potenziale per problemi di re-entrancy come discusso, quindi fai attenzione se lo fai.

8

La soluzione è quella di chiamare

[NSApp runModalForWindow:alert]; 

dopo beginSheetModalForWindow. Inoltre, è necessario implementare un delegato che rilevi l'azione "la finestra di dialogo è chiusa" e chiama [NSApp stopModal] in risposta.

+0

Io uso '[NSApp stopModalWithCode: returnCode];' così 'runModalForWindow' ottiene il codice corretto. –

+0

A partire dal 10.9, -beginSheetModalForWindow: completionHandler: è preferibile utilizzare un delegato. 'StopModalWithCode: returnCode' di Laurent nel blocco di completamento funziona: L'utente fa clic, il blocco di completamento viene eseguito, quindi il codice restituito viene restituito da' runModalForWindow'. –

1

Ecco la mia risposta:

Creare una variabile di classe mondiale '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; 
} 

Spero che sarà di qualche aiuto, acclamazioni --Hans

14

Abbiamo creato a category on NSAlert to run alerts synchronously, proprio come le finestre di dialogo modali dell'applicazione:

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

Il codice è disponibile tramite GitHub e la versione corrente pubblicata di seguito per completezza.


file di intestazione NSAlert+SynchronousSheet.h:

#import <Cocoa/Cocoa.h> 


@interface NSAlert (SynchronousSheet) 

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

@end 

file di implementazione 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

Grazie per aver postato una risposta! Mentre un frammento di codice può rispondere alla domanda è comunque bello aggiungere alcune informazioni aggiuntive, come spiegare, ecc. – j0k

5

Ecco una categoria NSAlert che risolve il problema (come suggerito da Philipp con il s olizione proposta da Federico e migliorata da Laurent P .: Uso un blocco di codice invece di un delegato, quindi è ancora una volta semplificato).

@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

Perfetto! Solo l'essenza di ciò che è necessario per farlo funzionare. Grazie. – Holtwick

0

Questa è la versione di Laurent, et al., Sopra, tradotto in Swift 1.2 per Xcode 6.4 (la versione più recente di lavoro a partire da oggi) e testato nella mia app. Grazie a tutti coloro che hanno contribuito a fare questo lavoro! La documentazione standard di Apple non mi ha dato alcun indizio su come procedere, almeno non in qualsiasi punto che potrei trovare.

Un mistero mi rimane: perché ho dovuto usare il doppio punto esclamativo nella funzione finale. NSApplication.mainWindow dovrebbe essere solo un NSWindow opzionale (NSWindow?), Giusto? Ma il compilatore ha dato l'errore mostrato fino a quando non ho usato il secondo '!'.

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