Как правильно отобразить таблицу «прогресс» модально при использовании Grand Central Dispatch для обработки чего-либо?

#objective-c #cocoa #grand-central-dispatch

#objective-c #cocoa #grand-central-dispatch

Вопрос:

Я пытаюсь отобразить таблицу в окне, содержащем один индикатор выполнения, чтобы показать ход выполнения некоторой функции long, выполняемой асинхронно с использованием Grand Central Dispatch. У меня почти получилось, но не получается отобразить таблицу в фокусе, вероятно, потому, что я не использовал runModalForWindow: или что-то подобное.

Это примерно то, что я делаю в данный момент, это происходит в результате нажатия кнопки в главном окне:

     // Prepare sheet and show it...

    [NSApp beginSheet:progressSheet modalForWindow:window modalDelegate:nil didEndSelector:NULL contextInfo:NULL];

    [progressSheet makeKeyAndOrderFront:self];

    [progressBar setIndeterminate:NO];
    [progressBar setDoubleValue:0.f];
    [progressBar startAnimation:self];


    // Start computation using GCD...

    dispatch_async(dispatch_get_global_queue(0, 0), ^{

        for (int i = 0; i < 1000; i   ) {
            // Do some large computation here
            // ...

            // Update the progress bar which is in the sheet:
            dispatch_async(dispatch_get_main_queue(), ^{
                [progressBar setDoubleValue:(double)i];
            });
        }


        // Calculation finished, remove sheet on main thread

        dispatch_async(dispatch_get_main_queue(), ^{
            [progressBar setIndeterminate:YES];

            [NSApp endSheet:progressSheet];
            [progressSheet orderOut:self];
        });
    });
  

Это работает, за исключением того, что главное окно все еще находится в фокусе, лист не в фокусе, а индикатор выполнения не анимируется (если я не использую setUsesThreadedAnimation:YES на нем).

Проблема, с которой я сталкиваюсь, заключается в том, что я не уверен, как запустить таблицу модально, не блокируя основной поток, прежде чем я начну асинхронное вычисление?

Комментарии:

1. Запуск листа модально для окна не должен блокировать основной поток (в противном случае ничего после вашего -beginSheet: бита в начале метода выполняться не будет). Я использую что-то почти идентичное этому (модальный лист, представленный так же, как и вы, с индикатором выполнения) в своем приложении здесь, и он отлично обновляется при запуске блока GCD в фоновом режиме. Элементы управления в окне, из которого лист скользит вниз, выделены серым цветом, указывая на то, что окно потеряло фокус на листе, так что это тоже кажется нормальным. Может ли быть что-то еще, вызывающее множество действий в основном потоке?

2. @BradLarson Единственной другой вещью, о которой я мог подумать, которая могла вызывать проблемы, было dispatch_apply как часть основного вычисления, но замена его стандартным циклом не имела никакого значения. В противном случае в основном потоке практически ничего больше не выполняется. Я все еще могу в основном взаимодействовать с элементами управления в главном окне, за листом, и они по-прежнему отображаются в фокусе (т. Е. не выделены серым цветом). Например, во время отображения таблицы в главном окне появляется текстовое поле с кольцом фокусировки, и я все еще могу ввести в него текст (хотя я не могу выделить текст с помощью мыши).

Ответ №1:

Как заявил Брэд, это должно сработать.

Чтобы выполнить быстрый тест, я создал таблицу программно (обычно вы, вероятно, используете файл nib, но их сложно вставить в этот текст). Если я вызываю приведенный ниже код с кнопки в обычном окне Cocoa, он работает так, как ожидалось. Обратите внимание, что текстовое поле на листе является первым ответчиком, и если вы наберете текст на клавиатуре, пока оно открыто, оно примет ввод.

 #define maxloop 1000

- (IBAction)startTask:(id)sender
{
    // Prepare sheet and show it...

    breakLoop = NO;

    NSRect sheetRect = NSMakeRect(0, 0, 400, 114);

    NSWindow *progSheet = [[NSWindow alloc] initWithContentRect:sheetRect 
                                                      styleMask:NSTitledWindowMask 
                                                        backing:NSBackingStoreBuffered 
                                                          defer:YES];

    NSView *contentView = [[NSView alloc] initWithFrame:sheetRect];

    NSProgressIndicator *progInd = [[NSProgressIndicator alloc] initWithFrame:NSMakeRect(143, 74, 239, 20)];

    NSTextField *inputField = [[NSTextField alloc] initWithFrame:NSMakeRect(145, 48, 235, 22)];

    NSButton *cancelButton = [[NSButton alloc] initWithFrame:NSMakeRect(304, 12, 82, 32)];
    cancelButton.bezelStyle = NSRoundedBezelStyle;
    cancelButton.title = @"Cancel";
    cancelButton.action = @selector(cancelTask:);
    cancelButton.target = self;

    [contentView addSubview:progInd];
    [contentView addSubview:inputField];
    [contentView addSubview:cancelButton];

    [progSheet setContentView:contentView];


    [NSApp beginSheet:progSheet 
       modalForWindow:self.window 
        modalDelegate:nil 
       didEndSelector:NULL 
          contextInfo:NULL];

    [progSheet makeKeyAndOrderFront:self];

    [progInd setIndeterminate:NO];
    [progInd setDoubleValue:0.f];
    [progInd startAnimation:self];


    // Start computation using GCD...

    dispatch_async(dispatch_get_global_queue(0, 0), ^{

        for (int i = 0; i < maxloop; i  ) {

            [NSThread sleepForTimeInterval:0.01];

            if (breakLoop) 
            {
                break;
            }

            // Update the progress bar which is in the sheet:
            dispatch_async(dispatch_get_main_queue(), ^{
                [progInd setDoubleValue: (double)i/maxloop * 100];
            });
        }


        // Calculation finished, remove sheet on main thread

        dispatch_async(dispatch_get_main_queue(), ^{
            [progInd setIndeterminate:YES];

            [NSApp endSheet:progSheet];
            [progSheet orderOut:self];
        });
    });
}

- (IBAction)cancelTask:(id)sender 
{
    NSLog(@"Cancelling");
    breakLoop = YES;
}
  

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

Ответ №2:

У меня была точно такая же проблема. Методом проб и ошибок я нашел решение. Убедитесь, что окно вашего листа является (а) NSWindow, а не NSPanel (это может не иметь значения) и что в окне есть строка заголовка (которая, поскольку это используемый вами лист), отображаться не будет.

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

Комментарии:

1. Да, в моем окне не было строки заголовка, ее добавление устранило проблему для меня.