问题
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?
回答1:
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.
回答2:
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.
来源:https://stackoverflow.com/questions/8054202/how-to-correctly-display-a-progress-sheet-modally-while-using-grand-central-di