Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to correctly display a "progress" sheet modally while using Grand Central Dispatch to process something?

I'm trying to display a sheet on a window containing a single progress bar, to show the progress of some long function running asynchronously using Grand Central Dispatch. I've almost got it, but can't get the sheet to appear to be in focus, probably because I haven't used runModalForWindow: or similar.

This is approximately what I'm doing at the moment, it happens as a result of a button press on the main window:

    // 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];
        });
    });

This works, except the main window is still in focus, the sheet is out of focus, and the progress bar doesn't animate (unless I use setUsesThreadedAnimation:YES on it).

The problem I think I'm having is that I'm not sure how to run the sheet modally without blocking the main thread before I start the asynchronous computation?

like image 295
Robert Avatar asked Nov 08 '11 16:11

Robert


2 Answers

As stated by Brad, it should work.

To do a quick test, I created a sheet programmatically (normally, you would probably use a nib file, but they are hard to paste into this text). If I call the code below from a button in a normal Cocoa window, it works as expected. Notice that the text field on the sheet is first responder, and if you type on the keyboard while it is open, it will accept the input.

#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;
}

Apologies for the ugly sheet, but apart from that this code works as expected, so the issue you are seeing is probably unrelated to GCD.

like image 111
Monolo Avatar answered Oct 18 '22 12:10

Monolo


I had exactly the same problem. With some trial and error, I found the solution. Make sure your sheet's window is (a) an NSWindow not an NSPanel (this may not matter) and that the window has a Title Bar (which, as it's a sheet you're using) will not be displayed.

I turned the Title Bar off for that reason, but somehow it's required to correctly achieve focus. Ticking the Title Bar checkbox gives my progress bar sheet focus.

like image 45
Tony Smith Avatar answered Oct 18 '22 13:10

Tony Smith