NSDocument仍然是软件维护的噩梦。
其他人有问题,他们希望同步处理某些阻止对话框?
任何人都可以验证这是否是“Apple批准”的解决方案?
static BOOL sWaitingForDidSaveModally = NO;
BOOL gWaitingForDidSaveCallback = NO; // NSDocument dialog calls didSave: when done
...
gWaitingForDidSaveCallback = true;
[toDocument saveDocumentWithDelegate:self
didSaveSelector:@selector(document:didSave:contextInfo:)
contextInfo:nil];
if ( gWaitingForDidSaveCallback )
{
// first, dispatch any other potential alerts synchronously
while ( gWaitingForDidSaveCallback && [NSApp modalWindow] )
[NSApp runModalForWindow: [NSApp modalWindow]];
if ( gWaitingForDidSaveCallback )
{
sWaitingForDidSaveModally = YES;
[NSApp runModalForWindow: [NSApp mbWindow]]; // mbWindow is our big (singleton) window
sWaitingForDidSaveModally = NO;
}
}
...
- (void)document:(NSDocument *)doc didSave:(BOOL)didSave contextInfo:(void *)contextInfo
{
[self recordLastSaveURL];
gWaitingForDidSaveCallback = NO;
if ( sWaitingForDidSaveModally )
[NSApp stopModal];
}
我必须支持Snow Leopard / Lion / ML
应用终止是一个丑陋的过程。 当用户决定退出,并且文档有需要保存的更改时,我称之为:
gWaitingForDidSaveCallback = true;
[toDocument saveDocumentWithDelegate:self
didSaveSelector:@selector(document:didSave:contextInfo:)
contextInfo:nil];
我真的非常希望这个调用是同步的,但在最新的Lion中,这会挂起我的应用程序:
while ( gWaitingForDidSaveCallback )
{
// didSave: callback clears sWaitingForDidSaveCallback
// do my own synchronous wait for now
[[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceReferenceDate:0.05]];
}
我最好的猜测是关闭了一个窗口关闭按钮的mouseDown: 令人困惑的是NSDocument。
所以现在,我必须返回,并使用不可维护的状态机逻辑来管理我的应用程序主循环,以防止用户执行各种危险的热键。
好的,所以我咧嘴一笑,忍受了另一个障碍!
在以前的OS版本/ SDK中,[NSApp modalWindow]会返回一个窗口
是在这种状态。现在它没有! Grrrrr ...
NSDocument没有API来测试它何时处于这种状态!
所以,现在没有机制来全局检查这种状态! 我必须在状态机中添加另一个状态变量。
任何人都有一个更清晰的解决方案可以解决这个问题,该解决方案适用于所有操作系统版本以及所有现有(以及将来)的SDK?
答案 0 :(得分:2)
更好的方法是将未保存的文档保存在链中。这很容易:
// Catch application terminate event
-(NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication *)sender
{
NSDocumentController *dc = [NSDocumentController sharedDocumentController];
for (NSInteger i = 0; i < [[dc documents] count]; i++)
{
Document *doc = [[dc documents] objectAtIndex:i];
if ([doc isDocumentEdited])
{
// Save first unsaved document
[doc saveDocumentWithDelegate:self
didSaveSelector:@selector(document:didSave:contextInfo:)
contextInfo:(__bridge void *)([NSNumber numberWithInteger:i + 1])]; // Next document
return NSTerminateLater; // Wait until last document in chain will be saved
}
}
return NSTerminateNow; // All documents are saved or there are no open documents. Terminate.
}
...
// Document saving finished
-(void)document:(NSDocument *)doc didSave:(BOOL)didSave contextInfo:(void *)contextInfo
{
if (didSave) // Save button pressed
{
NSDocumentController *dc = [NSDocumentController sharedDocumentController];
NSInteger nextIndex = [(__bridge NSNumber *)contextInfo integerValue];
for (NSInteger i = nextIndex; i < [[dc documents] count]; i++)
{
Document *doc = [[dc documents] objectAtIndex:nextIndex];
if ([doc isDocumentEdited])
{
// Save next unsaved document
[doc saveDocumentWithDelegate:self
didSaveSelector:@selector(document:didSave:contextInfo:)
contextInfo:(__bridge void *)([NSNumber numberWithInteger:nextIndex + 1])]; // Next document
return;
}
}
[NSApp replyToApplicationShouldTerminate:YES]; // All documents saved. Terminate.
}
else [NSApp replyToApplicationShouldTerminate:NO]; // Saving canceled. Terminate canceled.
}
答案 1 :(得分:1)
也许这个答案为时已晚,无法使用但是...在我实现的一个应用程序中 - (IBAction)终止:我的NSApplication派生类中的(id)发送者有条件地调用[super terminate]来实际关闭仅当所有打开的文档都已完全保存时才应用。我可能在Apple文档或其他示例中找到了一些。
终止覆盖将遍历每个文档并关闭它(因为它已保存),或者在NSDocument派生类中调用文档的canCloseDocumentWithDelegate方法传递“自我”。并且&#39;终止&#39;作为didSaveSelector。由于terminate方法失败并且除了使文档呈现NSAlert之外什么都不做,因此如果用户单击YES或NO,文档类中的警报将回调并重新运行终止例程。如果所有文档都是干净的,应用程序将终止,因为[super terminate]将被调用。如果存在任何更脏的文档,则重复该过程。
例如:
@interface MyApplication : NSApplication
@end
@implementation MyApplication
- (IBAction)terminate:(id)sender
{
//Loop through and find any unsaved document to warn the user about.
//Close any saved documents along the way.
NSDocument *docWarn = NULL;
NSArray *documents = [[NSDocumentController sharedDocumentController] documents];
for(int i = 0; i < [documents count]; i++)
{
NSDocument *doc = [documents objectAtIndex:i];
if([doc isDocumentEdited])
{
if(docWarn == NULL || [[doc windowForSheet] isKeyWindow])
docWarn = doc;
}
else
{
//close any document that doesn't need saving. this will
//also close anything that was dirty that the user answered
//NO to on the previous call to this routine which triggered
//a save prompt.
[doc close];
}
}
if(docWarn != NULL)
{
[[docWarn windowForSheet] orderFront:self];
[[docWarn windowForSheet] becomeFirstResponder];
[docWarn canCloseDocumentWithDelegate:self shouldCloseSelector:@selector(terminate:) contextInfo:NULL];
}
else
{
[super terminate:sender];
}
}
@end
后来在文档派生类中:
typedef struct {
void * delegate;
SEL shouldCloseSelector;
void *contextInfo;
} CanCloseAlertContext;
@interface MyDocument : NSDocument
@end
@implementation MyDocument
- (void)canCloseDocumentWithDelegate:(id)inDelegate shouldCloseSelector:(SEL)inShouldCloseSelector contextInfo:(void *)inContextInfo
{
// This method may or may not have to actually present the alert sheet.
if (![self isDocumentEdited])
{
// There's nothing to do. Tell the delegate to continue with the close.
if (inShouldCloseSelector)
{
void (*callback)(id, SEL, NSDocument *, BOOL, void *) = (void (*)(id, SEL, NSDocument *, BOOL, void *))objc_msgSend;
(callback)(inDelegate, inShouldCloseSelector, self, YES, inContextInfo);
}
}
else
{
NSWindow *documentWindow = [self windowForSheet];
// Create a record of the context in which the panel is being
// shown, so we can finish up when it's dismissed.
CanCloseAlertContext *closeAlertContext = malloc(sizeof(CanCloseAlertContext));
closeAlertContext->delegate = (__bridge void *)inDelegate;
closeAlertContext->shouldCloseSelector = inShouldCloseSelector;
closeAlertContext->contextInfo = inContextInfo;
// Present a "save changes?" alert as a document-modal sheet.
[documentWindow makeKeyAndOrderFront:nil];
NSBeginAlertSheet(@"Would you like to save your changes?", @"Yes", @"Cancel", @"No", documentWindow, self,
@selector(canCloseAlertSheet:didEndAndReturn:withContextInfo:), NULL, closeAlertContext, @"%");
}
}
- (void)canCloseAlertSheet:(NSWindow *)inAlertSheet didEndAndReturn:(int)inReturnCode withContextInfo:(void *)inContextInfo
{
CanCloseAlertContext *canCloseAlertContext = inContextInfo;
void (*callback)(id, SEL, NSDocument *, BOOL, void* ) = (void (*)(id, SEL, NSDocument *, BOOL, void* ))objc_msgSend;
if (inAlertSheet) [inAlertSheet orderOut:self];
// The user's dismissed our "save changes?" alert sheet. What happens next depends on how the dismissal was done.
if (inReturnCode==NSAlertAlternateReturn)
{
//Cancel - do nothing.
}
else if (inReturnCode==NSAlertDefaultReturn)
{
//Yes - save the current document
[self saveDocumentWithDelegate:(__bridge id)canCloseAlertContext->delegate
didSaveSelector:canCloseAlertContext->shouldCloseSelector contextInfo:canCloseAlertContext->contextInfo];
}
else
{
// No - just clear the dirty flag and post a message to
// re-call the shouldCloseSelector. This should be
// the app:terminate routine.
[self clearDirtyFlag];
if (canCloseAlertContext->shouldCloseSelector)
{
(callback)((__bridge id)canCloseAlertContext->delegate,
canCloseAlertContext->shouldCloseSelector, self, YES, canCloseAlertContext->contextInfo);
}
}
// Free up the memory that was allocated in -canCloseDocumentWithDelegate:shouldCloseSelector:contextInfo:.
free(canCloseAlertContext);
}
@end
应该这样做 - 没有循环......没有等待......