Вопрос

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

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

Это было полезно?

Решение

К сожалению, здесь вы мало что можете сделать.По сути, вы должны принять решение:перепроектируйте свое приложение так, чтобы оно могло обрабатывать объект асинхронным образом или использовать неутвержденную, устаревшую архитектуру представления модальных оповещений приложения.

Не зная никакой информации о вашем реальном дизайне и о том, как вы обрабатываете эти объекты, трудно предоставить какую-либо дополнительную информацию.Тем не менее, у меня в голове может возникнуть пара мыслей.:

  • Обрабатывайте объекты в другом потоке, который взаимодействует с основным потоком через какой-либо сигнал цикла выполнения или очередь.Если дерево объектов окна прерывается, оно сигнализирует основному потоку о том, что оно было прервано, и ожидает сигнала от основного потока с информацией о том, что делать (продолжить эту ветвь или прервать).Затем основной поток представляет окно document-modal и сигнализирует потоку процесса после того, как пользователь выберет, что делать.

Однако это может быть действительно слишком сложно для того, что вам нужно.В этом случае моей рекомендацией было бы просто использовать устаревшее использование, но это действительно зависит от ваших требований пользователя.

Другие советы

Мы создали категория на 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];

Код доступен через ГитХаб, и текущая версия размещена ниже для полноты картины.


Заголовочный файл 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'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

Решение состоит в том, чтобы вызвать

[NSApp runModalForWindow:alert];

после beginSheetModalForWindow.Кроме того, вам необходимо реализовать делегат, который перехватывает действие "диалоговое окно закрыто" и вызывает [NSApp stopModal] в ответ.

Вот категория NSAlert, которая решает проблему (как предложил Филипп с решением, предложенным Фредериком и улучшенным Лораном П.:Я использую блок кода вместо делегата, так что это еще раз упрощается).

@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

На всякий случай, если кто-нибудь придет искать это (я так и сделал), я решил это следующим образом:

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

Обратите внимание, что, как уже обсуждалось, возможны проблемы с повторным входом, поэтому будьте осторожны, делая это.

вот мой ответ:

Создайте глобальную переменную класса '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;
}

Надеюсь, это поможет, Приветствия --Ханс

Это версия 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!!)
    }
}

В отличие от Windows, я не верю, что есть способ заблокировать модальные диалоги.Входные данные (например,пользователь, нажимающий на кнопку), будет обработан в вашем основном потоке, так что блокировать невозможно.

Для выполнения вашей задачи вам нужно будет либо передать сообщение вверх по стеку, а затем продолжить с того места, на котором вы остановились.

Когда один объект выходит из строя, прекратите обработку объектов в дереве, запишите, какой объект вышел из строя (при условии, что есть заказ и вы можете продолжить с того места, где остановились), и выбросьте лист вверх.Когда пользователь закрывает лист, попросите didEndSelector: метод начинает обработку снова с объекта, на котором она была прервана, или не делает этого, в зависимости от 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;
}

Вы можете использовать dispatch_group_wait(group, 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

Надеюсь, это поможет.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top