2009-03-03 6 views
17

Когда я отображать NSAlert, как это, я получаю ответ сразу:Подождите, пока [NSAlert beginSheetModalForWindow: ...];

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

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

ответ

3

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

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

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

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

+0

Нитки, в конечном счете, я должен идти. Я полагаю. Дерево объектов в конечном итоге станет больше и сложнее. – dreamlax

+0

Не видя своего приложения, это, очевидно, трудно сказать, но действительно ли вы уверены, что вам нужны потоки?Я никогда не сталкивался с тем случаем, когда ответ в методе обратного вызова был более сложным, чем потоковое приложение. –

0

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

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

+0

неправда, см. Мой ответ –

0

Когда один объект выходит из строя, прекратите обработку объектов в дереве, сделайте отметку о том, какой объект потерпел неудачу (при условии, что есть заказ, и вы можете выбрать, где вы остановились) и выбросить лист. Когда пользователь отклоняет лист, метод didEndSelector: снова начнет обработку с объекта, с которого он остановился, или нет, в зависимости от returnCode.

+0

Я просто перечитываю ваш вопрос, и я боюсь, что «я не могу разделить задачу на две области», вы говорите, что это невозможно. Извините, если мой ответ не поможет. – erikprice

5

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

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

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

8

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

[NSApp runModalForWindow:alert]; 

после beginSheetModalForWindow. Кроме того, вам нужно реализовать делегата, который улавливает действие «диалог закрыл» и вызывает [NSApp stopModal] в ответ.

+0

Я использую '[NSApp stopModalWithCode: returnCode];' так что 'runModalForWindow' получает правильный код. –

+0

Начиная с версии 10.9, -beginSheetModalForWindow: completeHandler: предпочтительнее использования делегата. Laurent 'stopModalWithCode: returnCode' в блоке завершения: пользователь щелкает, выполняется блок завершения, затем возвращается код возврата' runModalForWindow'. –

1

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

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

14

Мы создали a category on NSAlert to run alerts synchronously, так же, как прикладные модальные диалоги:

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

Спасибо за сообщение! Хотя фрагмент кода может ответить на вопрос, все равно замечательно добавить дополнительную информацию, например, объяснять и т. Д. – j0k

5

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

@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

Отлично! Просто суть того, что требуется, чтобы заставить его работать. Благодарю. – Holtwick

0

Это версия 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!!) 
    } 
} 
Смежные вопросы