2

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];

}

结束编辑

我必须支持雪豹/狮子/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。

所以现在,我必须返回,并在我的应用程序主循环中添加不可维护的状态机逻辑,以防止用户执行各种危险的热键。

好吧,所以我笑着忍着,又遇到了一个障碍!

在以前的操作系统版本/SDK 中,[NSApp modalWindow] 将在处于此状态时返回一个窗口。现在没有了!Grrrrr...
NSDocument 在这种状态下没有 API 可以测试!

所以,现在没有机制来全局检查这个状态!我必须向我的状态机添加另一个状态变量。

任何人都有一个适用于所有操作系统版本和所有现有(和未来)SDK 的更清洁的解决方案?

4

2 回答 2

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.

}

于 2014-07-03T12:33:08.530 回答
1

也许这个答案太晚了,没有用,但是......在我的一个应用程序中,我在我的 NSApplication 派生类中实现了 -(IBAction)terminate:(id)sender ,它会有条件地调用 [super terminate] 以仅在以下情况下实际关闭应用程序所有打开的文档都被干净地保存了。我可能在 Apple 文档或其他示例中找到了其中的一些内容。

终止覆盖将遍历每个文档并关闭它(因为它已保存),或者在 NSDocument 派生类中调用文档的 canCloseDocumentWithDelegate 方法,将“self”和“terminate”作为 didSaveSelector。由于 terminate 方法失败并且除了使文档呈现 NSAlert 之外什么都不做,如果用户单击“是”或“否”,文档类中的警报将回调并重新运行终止例程。如果所有文档都是干净的,则应用程序将终止,因为 [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

应该这样做 - 没有循环......没有等待......

于 2014-03-14T02:03:06.293 回答