2009-03-03 11 views
17

Wenn ich ein NSAlert so anzuzeigen, erhalte ich die Antwort sofort:Warten auf [NSAlert beginSheetModalForWindow: ...];

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

Das Problem ist, dass dies anwendungs ​​modal und meine Anwendung ist Dokument basiert. Ich zeige die Warnung im Fenster des aktuellen Dokuments durch Blätter verwendet wird, wie folgt aus:

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

Das einzige Problem dabei ist, dass beginSheetModalForWindow: kehrt sofort, so kann ich nicht zuverlässig dem Benutzer eine Frage stellen und auf eine Antwort warten. Das wäre keine große Sache, wenn ich die Aufgabe in zwei Bereiche aufteilen könnte, aber ich kann nicht.

Ich habe eine Schleife, die etwa 40 verschiedene Objekte (die in einem Baum sind) verarbeitet. Wenn ein Objekt ausfällt, möchte ich, dass der Alarm angezeigt wird und der Benutzer gefragt wird, ob er fortfahren oder abbrechen soll (Fortsetzung der Verarbeitung im aktuellen Zweig). Da meine Anwendung jedoch dokumentbasiert ist, schreiben die Richtlinien für Apple-Benutzeroberflächen die Verwendung von Blättern vor spezifisch für ein Dokument.

Wie kann ich die Warnmeldung anzeigen und auf eine Antwort warten?

Antwort

3

Leider können Sie hier nicht viel tun. Sie müssen im Grunde eine Entscheidung treffen: Re-Architect Ihre Anwendung, so dass sie das Objekt in einer asynchronen Weise verarbeiten kann, oder verwenden Sie die nicht genehmigte veraltete Architektur der Darstellung von anwendungsmodalen Warnungen.

Ohne Informationen über Ihr tatsächliches Design und wie Sie diese Objekte verarbeiten, ist es schwierig, weitere Informationen zu geben. Aus der Spitze von dem Kopf, allerdings könnte ein Paar Gedanken sein:

  • Verfahren die Objekte in einem anderen Thread, der mit dem Hauptfaden durch eine Art von Laufschleifensignal oder Warteschlange kommuniziert. Wenn der Objektbaum des Fensters unterbrochen wird, signalisiert er dem Hauptthread, dass er unterbrochen wurde, und wartet auf ein Signal vom Hauptthread mit Informationen darüber, was zu tun ist (diese Verzweigung fortsetzen oder abbrechen). Der Haupt-Thread präsentiert dann das Dokument-modale Fenster und signalisiert dem Prozess-Thread, nachdem der Benutzer wählt, was zu tun ist.

Dies kann jedoch für das, was Sie brauchen, wirklich zu kompliziert sein. In diesem Fall wäre meine Empfehlung, nur mit der veralteten Verwendung zu gehen, aber es hängt wirklich von Ihren Benutzeranforderungen ab.

+0

Threads meine letztlich der Weg zu gehen, nehme ich an. Der Objektbaum wird schließlich größer und komplizierter. – dreamlax

+0

Ohne deine App zu sehen, ist es offensichtlich schwer zu sagen, aber bist du dir wirklich sicher, dass du Threads brauchst?Ich bin nie auf den Fall gestoßen, bei dem die Antwort in der Callback-Methode komplexer war als das Threading der App. –

0

Im Gegensatz zu Windows glaube ich nicht, dass es eine Möglichkeit gibt, modale Dialoge zu blockieren. Die Eingabe (z. B. der Benutzer, der auf eine Schaltfläche klickt) wird auf Ihrem Hauptthread verarbeitet, so dass es keine Möglichkeit gibt, zu blockieren.

Für Ihre Aufgabe müssen Sie entweder die Nachricht auf dem Stapel weiterleiten und dann weitermachen, wo Sie aufgehört haben.

+0

nicht wahr, siehe meine Antwort –

0

Wenn ein Objekt ausfällt, beenden Sie die Verarbeitung der Objekte in der Baumstruktur, notieren Sie, welches Objekt fehlgeschlagen ist (vorausgesetzt, es gibt eine Bestellung und Sie können dort weitermachen, wo Sie aufgehört haben) und werfen Sie das Blatt hoch. Wenn der Benutzer das Blatt entlässt, lassen Sie die didEndSelector:-Methode erneut von dem Objekt starten, mit dem es abgebrochen wurde, oder nicht, abhängig von der returnCode.

+0

Ich lese gerade Ihre Frage erneut und ich befürchte, dass, indem ich "die Aufgabe nicht in zwei Bereiche aufteilen kann", Sie sagen, dass dies nicht möglich ist. Entschuldigung, wenn meine Antwort nicht hilfreich ist. – erikprice

5

Nur falls jemand kommt für diese Suche (ich), löste ich dies mit den folgenden:

@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 

Dann wird ein NSAlert synchron läuft so einfach wie: es

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

Hinweis Wie bereits erwähnt, besteht Potenzial für Re-Enrancy-Probleme. Seien Sie also vorsichtig, wenn Sie dies tun.

8

Die Lösung ist

[NSApp runModalForWindow:alert]; 

nach beginSheetModalForWindow zu nennen. Außerdem müssen Sie einen Delegaten implementieren, der die Aktion "Dialog hat beendet" abruft und als Antwort [NSApp stopModal] aufruft.

+0

Ich benutze '[NSApp stopModalWithCode: returnCode];', so dass 'runModalForWindow' den richtigen Code bekommt. –

+0

Ab 10.9, -beginSheetModalForWindow: completionHandler: wird gegenüber der Verwendung eines Delegaten bevorzugt. Laurents 'stopModalWithCode: returnCode' im Completion Block funktioniert: Der Benutzer klickt, der Completion Block wird ausgeführt, dann wird der returnCode von' runModalForWindow' zurückgegeben. –

1

hier ist meine Antwort:

eine variable globale Klasse erstellen

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

Hoffe, dass es eine Hilfe sein werden, Prost --Hans

14

Wir haben a category on NSAlert to run alerts synchronously, wie anwendungs-modale Dialoge:

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

Der Code ist verfügbar unter GitHub, und die aktuelle Version, die unten für Vollständigkeit geschrieben wird.


Header-Datei NSAlert+SynchronousSheet.h:

#import <Cocoa/Cocoa.h> 


@interface NSAlert (SynchronousSheet) 

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

@end 

Implementierungsdatei 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

Danke für das Posten einer Antwort! Während ein Code-Snippet die Frage beantworten kann, ist es immer noch großartig, zusätzliche Informationen hinzuzufügen, wie zum Beispiel erklären, etc. – j0k

5

Hier ist eine NSAlert Kategorie, die das Problem löst (wie von Philipp vorgeschlagen mit die s Lösung vorgeschlagen von Frederick und verbessert von Laurent P .: Ich benutze einen Codeblock anstelle eines Delegaten, so ist es wieder vereinfacht).

@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

Perfekt! Nur das Wesentliche dessen, was erforderlich ist, damit es funktioniert. Vielen Dank. – Holtwick

0

Dies ist die Version von Laurent et al., Oben, übersetzt in Swift 1.2 für Xcode 6.4 (neueste Arbeitsversion ab heute) und in meiner App getestet. Danke an alle, die dazu beigetragen haben, dass das funktioniert! Die Standard-Dokumentation von Apple gab mir keine Hinweise darauf, wie das geht, zumindest nicht überall, wo ich es finden könnte.

Ein Rätsel bleibt mir: Warum musste ich das doppelte Ausrufezeichen in der letzten Funktion verwenden. NSApplication.mainWindow soll nur ein optionales NSWindow (NSWindow?) Sein, oder? Aber der Compiler hat den Fehler angezeigt, bis ich das zweite '!' Verwendet habe.