Attendi [NSAlert beginSheetModalForWindow: & # 8230;];
-
03-07-2019 - |
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?
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.