Подождите [NSAlert beginSheetModalForWindow:...];
Когда я показываю NSAlert, как это, я сразу получаю ответ:
int response;
NSAlert *alert = [NSAlert alertWithMessageText:... ...];
response = [alert runModal];
Проблема в том, что это приложение-модальное, а мое приложение основано на документе. Я показываю предупреждение в текущем окне документа, используя листы, например:
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;
}
Единственная проблема заключается в том, что beginSheetModalForWindow:
возвращает сразу, поэтому я не могу надежно задавать пользователю вопрос и ждать ответа. Это не было бы большой проблемой, если бы я мог разделить задачу на две области, но я не могу.
У меня есть цикл, который обрабатывает около 40 различных объектов (которые находятся в дереве). Если один объект выходит из строя, я хочу, чтобы предупреждение отображалось и запрашивало у пользователя, продолжать или прерывать (продолжить обработку в текущей ветке), но поскольку мое приложение основано на документе, в Руководстве пользователя Apple по физическому интерфейсу требуется использовать листы, когда предупреждение специфичный для документа.
Как я могу отобразить лист предупреждений и дождаться ответа?
Ответы
Ответ 1
К сожалению, здесь мало что можно сделать. Вы в основном должны принять решение: перепроектировать ваше приложение, чтобы оно могло обрабатывать объект асинхронным образом или использовать недопустимую, устаревшую архитектуру представления модальных предупреждений приложения.
Без знания какой-либо информации о вашем фактическом дизайне и способах обработки этих объектов трудно дать дополнительную информацию. Однако, с головы до ног, может быть несколько мыслей:
- Обработать объекты в другом потоке, который связывается с основным потоком через какой-либо сигнал цикла или очередь. Если дерево оконных объектов прервано, он сигнализирует основному потоку, что он был прерван, и ждет сигнала от основного потока с информацией о том, что делать (продолжите эту ветвь или прервите). Затем основной поток представляет окно с документами и сигнализирует поток процесса после того, как пользователь решит, что делать.
Это может быть действительно чрезмерно сложно для того, что вам нужно. В этом случае моя рекомендация состояла бы в том, чтобы просто использовать устаревшее использование, но это действительно зависит от ваших требований пользователя.
Ответ 2
Мы создали категорию на NSAlert
для синхронного запуска предупреждений, так же, как диалоговые окна приложения:
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];
Код доступен через GitHub, а текущая версия представлена ниже для полноты.
Заголовочный файл NSAlert+SynchronousSheet.h
:
#import <Cocoa/Cocoa.h>
@interface NSAlert (SynchronousSheet)
-(NSInteger) runModalSheetForWindow:(NSWindow *)aWindow;
-(NSInteger) runModalSheet;
@end
Файл реализации NSAlert+SynchronousSheet.m
:
#import "NSAlert+SynchronousSheet.h"
// Private methods -- use prefixes to avoid collisions with Apple 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 documentation (see NSAlert 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
Ответ 3
Решение состоит в вызове
[NSApp runModalForWindow:alert];
после beginSheetModalForWindow. Кроме того, вам нужно реализовать делегат, который улавливает действие "диалог закрыл" и вызывает [NSApp stopModal] в ответ.
Ответ 4
На всякий случай, когда кто-нибудь ищет это (я сделал), я решил это со следующим:
@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
Затем запуск NSAlert синхронно выполняется так же, как:
AlertSync* sync = [[AlertSync alloc] initWithAlert: alert asSheetForWindow: window];
int returnCode = [sync run];
[sync release];
Обратите внимание, что существует вероятность проблем с повторным подключением, как обсуждалось, поэтому будьте осторожны при этом.
Ответ 5
Вот категория NSAlert, которая решает проблему (как предложил Филипп с решением, предложенным Фредериком и улучшенным Laurent P.: я использую блок кода вместо делегата, поэтому он снова упрощается).
@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
Ответ 6
вот мой ответ:
Создать глобальную переменную класса '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;
}
Надеюсь, что это поможет,
ура
--Hans
Ответ 7
В отличие от Windows я не верю, что есть способ блокировать модальные диалоги. Вход (например, пользователь, нажавший кнопку) будет обработан на вашем основном потоке, чтобы исключить возможность блокировки.
Для вашей задачи вам нужно либо передать сообщение в стек, а затем продолжить, когда вы остановились.
Ответ 8
Когда один объект выходит из строя, прекратите обработку объектов в дереве, запишите, какой объект был сбой (при условии, что есть заказ, и вы можете выбрать, где вы остановились), и бросить лист. Когда пользователь отклоняет лист, метод didEndSelector:
снова запускает обработку с объекта, с которого он остановился, или нет, в зависимости от returnCode
.
Ответ 9
- (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;
}
Ответ 10
Это версия Laurent и др., выше, переведенная в Swift 1.2 для Xcode 6.4 (последняя рабочая версия на сегодняшний день) и протестирована в моем приложении. Спасибо всем, кто внес свой вклад в эту работу! Стандартная документация от Apple не давала мне никаких указаний относительно того, как это происходит, по крайней мере, нигде, где я мог бы найти.
Мне остается одна загадка: почему мне пришлось использовать двойной восклицательный знак в финальной функции. NSApplication.mainWindow должен быть просто необязательным NSWindow (NSWindow?), Правильно? Но компилятор дал ошибку, пока я не использовал второй "!".
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!!)
}
}