Question

Lorsque j'affiche une alerte NSAlert comme celle-ci, je reçois immédiatement la réponse:

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

Le problème est qu’il s’agit d’une application modale et que mon application est basée sur un document. J'affiche l'alerte dans la fenêtre du document en cours à l'aide de feuilles, comme ceci:

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

Le seul problème avec ceci est que beginSheetModalForWindow: revient tout de suite, donc je ne peux pas poser de question fiable à l'utilisateur et attendre une réponse. Ce ne serait pas grave si je pouvais scinder la tâche en deux domaines, mais je ne le pourrais pas.

J'ai une boucle qui traite environ 40 objets différents (situés dans un arbre). Si un objet échoue, je veux que l'alerte soit affichée et demande à l'utilisateur s'il souhaite continuer ou non (poursuivre le traitement dans la branche actuelle), mais comme mon application est basée sur un document, les directives d'interface utilisateur Apple dictent l'utilisation de feuilles lorsque l'alerte est activée. spécifique à un document.

Comment puis-je afficher la feuille d'alerte et attendre une réponse?

Était-ce utile?

La solution

Malheureusement, vous ne pouvez pas faire grand chose ici. Vous devez en principe prendre une décision: réorganisez votre application afin qu’elle puisse traiter l’objet de manière asynchrone ou utiliser l’architecture non approuvée et dépréciée de la présentation des alertes modales de l’application.

Sans connaître aucune information sur votre conception actuelle ni sur la manière dont vous traitez ces objets, il est difficile de donner des informations supplémentaires. Cependant, quelques réflexions pourraient être:

  • Traitez les objets dans un autre thread qui communique avec le thread principal via un type de signal de boucle d'exécution ou une file d'attente. Si l'arborescence des objets de la fenêtre est interrompue, elle signale au thread principal qu'elle a été interrompue et attend un signal du thread principal avec des informations sur ce qu'il faut faire (continuer cette branche ou abandonner). Le thread principal présente ensuite la fenêtre modale de document et le signale au thread de processus après que l'utilisateur ait choisi quoi faire.

Cela peut toutefois s'avérer extrêmement compliqué pour ce dont vous avez besoin. Dans ce cas, je vous conseillerais simplement d’utiliser l’utilisation obsolète, mais cela dépend vraiment de vos besoins.

Autres conseils

Nous avons créé une catégorie sur NSAlert pour exécuter des alertes de manière synchrone , tout comme les dialogues modaux d'application:

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

Le code est disponible via GitHub , et la version actuelle publiée ci-dessous par souci d'exhaustivité.

Fichier d'en-tête NSAlert + SynchronousSheet.h :

#import <Cocoa/Cocoa.h>


@interface NSAlert (SynchronousSheet)

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

@end

Fichier d'implémentation 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 solution consiste à appeler

[NSApp runModalForWindow:alert];

après beginSheetModalForWindow. En outre, vous devez implémenter un délégué qui détecte que la & boîte de dialogue a été fermée " action, et appelle [NSApp stopModal] en réponse.

Voici une catégorie de NSAlert qui résout le problème (comme suggéré par Philipp avec la solution proposée par Frederick et améliorée par Laurent P.: J'utilise un bloc de code au lieu d'un délégué, donc il est encore simplifié).

@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

Juste au cas où quelqu'un chercherait ceci (je l'ai fait), j'ai résolu ceci avec le texte suivant:

@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

Ensuite, exécuter un NSAlert de manière synchrone est aussi simple que:

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

Notez qu'il existe un risque de problèmes de ré-entrée, comme discuté. Soyez donc prudent si vous procédez ainsi.

voici ma réponse:

Créez une variable de 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;
}

J'espère que ça vous sera utile, À votre santé --Hans

Ceci est la version de Laurent, et al., ci-dessus, traduite en Swift 1.2 pour Xcode 6.4 (la dernière version opérationnelle en date à ce jour) et testée dans mon application. Merci à tous ceux qui ont contribué à faire ce travail! La documentation standard d’Apple ne m’a donné aucune indication quant à la manière de procéder, du moins nulle part ailleurs.

Il me reste un mystère: pourquoi j'ai dû utiliser le double point d'exclamation dans la fonction finale. NSApplication.mainWindow est censé être simplement une option NSWindow (NSWindow?), Non? Mais le compilateur a donné l’erreur montrée jusqu’à ce que j’utilise le second '!'.

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

Contrairement à Windows, je ne pense pas qu’il existe un moyen de bloquer les dialogues modaux. L'entrée (par exemple, l'utilisateur qui clique sur un bouton) sera traitée sur votre thread principal, il n'y a donc aucun moyen de le bloquer.

Pour votre tâche, vous devrez soit faire passer le message dans la pile, puis continuer là où vous l'avez laissé.

Lorsqu'un objet échoue, arrêtez de traiter les objets dans l'arborescence, notez quel objet a échoué (en supposant qu'il existe un ordre et que vous puissiez reprendre où vous l'avez laissé), puis ouvrez la feuille. Lorsque l'utilisateur rejette la feuille, faites en sorte que la méthode didEndSelector: reprenne le traitement à partir de l'objet qu'il a laissé ou non, en fonction du code de retour .

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

Vous pouvez utiliser dispatch_group_wait (groupe, 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

J'espère que cela aide.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top