Domanda

Quando visualizzo un NSAlert come questo, ottengo subito la risposta:

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

Il problema è che si tratta di un'applicazione modale e la mia applicazione è basata su documenti. Visualizzo l'avviso nella finestra del documento corrente usando i 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 è che beginSheetModalForWindow: ritorna immediatamente, quindi non posso fare in modo affidabile una domanda all'utente e attendere una risposta. Questo non sarebbe un grosso problema se potessi dividere l'attività in due aree, ma non posso.

Ho un ciclo che elabora circa 40 oggetti diversi (che si trovano in un albero). Se un oggetto non riesce, voglio che l'avviso mostri e chieda all'utente se continuare o interrompere (continua l'elaborazione nel ramo corrente), ma poiché la mia applicazione è basata su documenti, le Linee guida per l'interfaccia utente di Apple impongono di utilizzare i fogli quando l'avviso è specifico per un documento.

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

È stato utile?

Soluzione

Sfortunatamente, non c'è molto che puoi fare qui. Fondamentalmente devi prendere una decisione: riprogetta la tua applicazione in modo che possa elaborare l'oggetto in modo asincrono o utilizzare l'architettura non approvata e obsoleta della presentazione degli avvisi modali dell'applicazione.

Senza conoscere alcuna informazione sul tuo progetto reale e su come elabori questi oggetti, è difficile fornire ulteriori informazioni. Dalla parte superiore della mia testa, tuttavia, un paio di pensieri potrebbero essere:

  • Elabora gli oggetti in un altro thread che comunica con il thread principale attraverso una sorta di segnale o coda di loop di esecuzione. Se l'albero degli oggetti della finestra viene interrotto, segnala al thread principale che è stato interrotto e attende un segnale dal thread principale con informazioni su cosa fare (continua questo ramo o interrompi). 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 può essere davvero complicato per quello che ti serve, comunque. In tal caso, la mia raccomandazione sarebbe quella di seguire solo l'utilizzo deprecato, ma dipende davvero dalle esigenze dell'utente.

Altri suggerimenti

Abbiamo creato una su NSAlert per eseguire avvisi in modo sincrono , proprio come i dialoghi 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

La soluzione è chiamare

[NSApp runModalForWindow:alert];

dopo beginSheetModalForWindow. Inoltre, devi implementare un delegato che intercetta la finestra di dialogo " che ha chiuso " azione e chiama [NSApp stopModal] in risposta.

Ecco una categoria NSAlert che risolve il problema (come suggerito da Philipp con la soluzione proposta da Federico e migliorata da Laurent P .: Uso un blocco di codice anziché un delegato, quindi viene semplificato ancora una volta).

@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

Nel caso in cui qualcuno venga a cercarlo (l'ho fatto), l'ho risolto 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

Quindi eseguire un NSAlert in modo sincrono è semplice come:

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

Nota che esiste il potenziale per i problemi di rientro come discusso, quindi fai attenzione se lo fai.

ecco la mia risposta:

Crea una variabile di classe globale '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 possa esserti di aiuto, Saluti --Hans

Questa è la versione di Laurent, et al., sopra, tradotta in Swift 1.2 per Xcode 6.4 (ultima versione funzionante ad oggi) e testata nella mia app. Grazie a tutti coloro che hanno contribuito a far funzionare questo! La documentazione standard di Apple non mi ha dato indizi su come procedere, almeno non ovunque io abbia potuto trovare.

Un mistero rimane per me: perché ho dovuto usare il doppio punto esclamativo nella funzione finale. NSApplication.mainWindow dovrebbe essere solo una 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!!)
    }
}

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

Per la tua attività dovrai passare il messaggio in pila e poi continuare da dove eri rimasto.

Quando un oggetto ha esito negativo, interrompere l'elaborazione degli oggetti nella struttura, prendere nota di quale oggetto ha avuto esito negativo (supponendo che esista un ordine e che si possa riprendere da dove si era interrotto) e lanciare il foglio. Quando l'utente elimina il foglio, fare in modo che il metodo didEndSelector: riprenda l'elaborazione dall'oggetto con cui era stato interrotto o meno, a seconda del 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;
}

Puoi utilizzare dispatch_group_wait (gruppo, 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

Spero che sia d'aiuto.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top