"You can create a window on a secondary thread."
"You can create a window on a secondary thread."
- Subject: "You can create a window on a secondary thread."
- From: Jerry Krinock <email@hidden>
- Date: Tue, 06 Mar 2007 11:39:02 -0800
- Thread-topic: "You can create a window on a secondary thread."
My app downloads data from a server, which can take a long time or fail, so
I want a custom Progress Dialog (progress bar, some text and a "Cancel"
button).
There is ample documentation and disussion explaining how to run such a long
task in a secondary thread. But in my case the app cannot continue until
the downloaded data is processed; I therefore ^do^ want the app to block
during the download, except that my "Cancel" button must be responsive.
So I thought, instead of running the long task in a secondary thread, I want
to run my Progress Dialog in a secondary thread. In Apple's "Multithreading
Programming Topics", it says "You can create a window on a secondary thread.
The Application Kit ensures that the data structures associated with a
window are deallocated on the main thread to avoid race conditions."
When I try this, though, where my Dialog should be on the screen I get a
whited-out box instead. I've added an empty port to the run loop in the
secondary thread, run it, and it seems to run fine.
Is there some little detail that I'm missing, or am I going about this
completely the wrong way?
Thanks,
Jerry Krinock
***** TEST CODE SNIPPET *****
BOOL done = NO ;
SSProgressDialog* progressDialog = [SSProgressDialog progressDialog] ;
SEL sel = @selector(startThreadedWithCancelButtonAndShowLocalizedText:) ;
[NSThread detachNewThreadSelector:sel
toTarget:progressDialog
withObject:@"Work is never done!!!"] ;
while (!done) {
if ([progressDialog userCancelled]) {
done = YES ;
}
NSDate later = [NSDate dateWithTimeIntervalSinceNow:1.123] ;
[NSThread sleepUntilDate:later] ;
NSLog(@"DebugLog: 15779 main thread waking") ;
}
***** Interface of class SSProgressDialog *****
// New method I'm adding to support the "Cancel" button is
// startThreadedWithCancelButtonAndShowLocalizedText:
// Other methods in this class have been tested and work
// Code is peppered with [[self window] display] to try and fix problem
@interface SSProgressDialog : NSWindowController
{
IBOutlet NSProgressIndicator* progressBar;
IBOutlet NSTextField* progressText;
NSButton* _cancelButton ; // added programatically
int originalLimit ;
BOOL _userCancelled ;
BOOL _done ;
}
- (void)startAndShowLocalizedText:(NSString*)localizedText ;
- (void)startWithCancelButtonAndShowLocalizedText:
(NSString*)localizedText ;
-(void)startThreadedWithCancelButtonAndShowLocalizedText:
(NSString*)localizedText ;
- (void)setDeterminateWithLimit:(int)newLimit ;
// Must receive the next message every time nDone is incremented.
- (void)updateTo:(int)nDone
newLimit:(int)newLimit
newLocalizedText:(NSString*)newLocalizedText
centered:(BOOL)centered ;
// If you want limit to remain unchanged (the usual case), pass
newLimit=0
// To change to indeterminate, pass newLimit = NSNotFound
// If you want localizedText to remain unchanged, pass
newLocalizedText=nil
- (void)start ;
- (void)pause ;
- (void)stop ;
- (BOOL)isVisible ;
- (BOOL)userCancelled ;
+ (SSProgressDialog*) progressDialog ;
+ (SSProgressDialog*) progressDialogWithLocalizedText:
(NSString*)localizedText ;
+ (SSProgressDialog*) progressDialogWithCancelButtonAndLocalizedText:
(NSString*)localizedText ;
@end
***** Implementation of class SSProgressDialog *****
#import "SSProgressDialog.h"
#import "SSUtils.h"
@implementation SSProgressDialog
// Accessor Macros (setter is retain/release/assign)
SSAOm(NSButton*, cancelButton, setCancelButton) ;
// Action for "Cancel" button:
- (IBAction)cancel:(id)sender {
_userCancelled = YES ;
}
- (BOOL)userCancelled {
return _userCancelled ;
}
- (void)startAndShowLocalizedText:(NSString*)localizedText {
[progressText setStringValue:localizedText] ;
[self performSelector:@selector(show)] ;
[[self window] display] ;
}
- (void)startWithCancelButtonAndShowLocalizedText:
(NSString*)localizedText {
NSWindow* window = [self window] ;
NSView* contentView = [window contentView] ;
NSRect frameContentView = [contentView frame] ;
float buttonRightEdge =
frameContentView.origin.x
+ frameContentView.size.width
- SS_BUTTON_CLEARANCE_EXTERIOR ;
NSButton* cancelButton = SSCreateButton(
@"cancel",
[NSFont systemFontOfSize:12.0],
0.0,
buttonRightEdge,
SS_BUTTON_CLEARANCE_EXTERIOR) ;
[cancelButton setButtonType:NSMomentaryPushInButton] ;
[cancelButton setBezelStyle:NSRoundedBezelStyle] ;
[cancelButton setTarget:self] ;
[cancelButton setAction:@selector(cancel:)] ;
[cancelButton setKeyEquivalent:@"\r"] ;
float bottomIncrease =
[cancelButton frame].size.height
+ 2 * SS_BUTTON_CLEARANCE_EXTERIOR ;
SSResizeWindowAndContent(window, 0.0, 0.0, 0.0, bottomIncrease) ;
[contentView addSubview:cancelButton] ;
[self setCancelButton:cancelButton] ;
[cancelButton release] ;
[self startAndShowLocalizedText:localizedText] ;
}
- (void)startThreadedWithCancelButtonAndShowLocalizedText:
(NSString*)localizedText {
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init] ;
[self startWithCancelButtonAndShowLocalizedText:localizedText] ;
NSRunLoop* runLoop = [NSRunLoop currentRunLoop] ;
[runLoop addPort:[NSPort port] forMode:NSDefaultRunLoopMode] ;
// Code ripped from Apple's "Run Loops" Cocoa document:
double resolution = 1.0;
BOOL isRunning;
_done = NO ;
do {
NSLog(@"DebugLog: 2474 SSProgressDialog loop is running") ;
[[self window] display] ;
NSDate* next = [NSDate dateWithTimeIntervalSinceNow:resolution];
isRunning = [runLoop runMode:NSDefaultRunLoopMode
beforeDate:next];
} while (isRunning && !_done);
[pool release] ;
NSLog(@"DebugLog: 3145 SSProgressDialog thread is done") ;
}
- (void)show
{
[progressText display] ;
[progressBar displayIfNeeded] ;
[progressBar setUsesThreadedAnimation:TRUE] ;
[progressBar setDisplayedWhenStopped:FALSE] ;
[progressBar startAnimation:self] ;
[progressBar setIndeterminate:YES] ;
if (![[self window] isVisible]) {
// Progress has just started
[self showWindow:self] ;
[NSApp activateIgnoringOtherApps:YES] ;
[[self window] makeKeyAndOrderFront:self] ;
}
else {
// We're just updating the title or the progress
// Don't disburb the user who may be using another app!
}
}
- (void)setDeterminateWithLimit:(int)newLimit
{
originalLimit = newLimit ;
[progressBar setMaxValue:100.0] ;
// This is the default, but we re-set it here, in case
// this progressBar is being re-used.
[progressBar setIndeterminate:NO] ;
[progressBar setDoubleValue:0.0] ;
[progressBar displayIfNeeded] ;
}
- (void)updateTo:(int)nDone
newLimit:(int)newLimit
newLocalizedText:(NSString*)newLocalizedText
centered:(BOOL)centered {
int progressIncrement =
nDone*100/originalLimit - (nDone-1)*100/originalLimit ;
// Note: The above calcs are all integer.
// It keeps account of the remainder.
[progressBar incrementBy:progressIncrement] ;
if (newLimit == NSNotFound) {
[progressBar setIndeterminate:YES] ;
}
else if (newLimit > 0) {
int newMaxValue =
(newLimit > 100) ? (100*newLimit)/originalLimit : newLimit ;
[progressBar setMaxValue:newMaxValue] ;
}
[progressBar displayIfNeeded] ;
if (newLocalizedText) {
[progressText setStringValue:newLocalizedText] ;
[[progressText cell] setAlignment:
(centered ? NSCenterTextAlignment : NSLeftTextAlignment)] ;
[progressText display] ;
}
}
- (void)start {
[progressBar startAnimation:self] ;
}
- (void)pause {
[progressBar stopAnimation:self] ;
}
- (void)stop
{
[progressBar stopAnimation:self] ;
[progressText setObjectValue:@""] ;
[progressText display] ;
[self close] ;
_done = YES ;
}
- (BOOL)isVisible {
return ([[self window] isVisible]) ;
}
- init {
self = [super initWithWindowNibName:@"SSProgressDialog"];
if (self != 0) {
_userCancelled = NO ;
}
return self;
}
- (void)dealloc
{
[self setCancelButton:nil] ;
[super dealloc];
}
- (void)awakeFromNib {
[[self window] setTitle:
[[NSProcessInfo processInfo] processName]] ;
}
+ (SSProgressDialog*) progressDialog {
SSProgressDialog* instance = [[SSProgressDialog alloc] init] ;
return [instance autorelease] ;
}
+ (SSProgressDialog*) progressDialogWithLocalizedText:
(NSString*)localizedText {
SSProgressDialog* instance = [self progressDialog] ;
[instance startAndShowLocalizedText:localizedText] ;
return instance ;
}
+ (SSProgressDialog*)progressDialogWithCancelButtonAndLocalizedText:
(NSString*)localizedText {
SSProgressDialog* instance = [self progressDialog] ;
[instance startWithCancelButtonAndShowLocalizedText:localizedText] ;
return instance ;
}
@end
_______________________________________________
Cocoa-dev mailing list (email@hidden)
Do not post admin requests or moderator comments to the list.
Contact the moderators at cocoa-dev-admins(at)lists.apple.com
Help/Unsubscribe/Update your Subscription:
This email sent to email@hidden