Подтвердить что ты не робот

Подождите [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 по физическому интерфейсу требуется использовать листы, когда предупреждение специфичный для документа.

Как я могу отобразить лист предупреждений и дождаться ответа?

4b9b3361

Ответ 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!!)
    }
}