multi-window document best practices?

  • I've have come across some surprising behavior when trying to implement a document with two windows, each loaded from its own nib.

    In my NSDocument subclass, I have

    - (void) makeWindowControllers
    {
        NSWindowController* wc1 = [[[NSWindowController alloc] initWithWindowNibName:@"MNKDocument" owner:self]autorelease];
        [self addWindowController:wc1];
        [wc1 window]; // loads nib the first time it's called.

        NSLog(@"wc1->window = %@", [wc1 window]);

        NSWindowController* wc2 = [[[NSWindowController alloc] initWithWindowNibName:@"MNKDocumentSecondWindow" owner:self] autorelease];
        [self addWindowController:wc2];
        [wc2 window]; // loads nib the first time it's called.

        NSLog(@"wc2->window = %@", [wc2 window]);
    }

    wc1 is loading the default nib provided by Xcode's document-based app template. wc2 is loading a nib I made by simply duplicating the default nib.

    The "File's Owner" in both nibs is my document subclass. In the first nib file, the "File's Owner" has its "window" outlet connected to the window in the nib. Pretty vanilla.

    In the second nib, the File Owner's "window" outlet is disconnected. The window in the second nib is connected to the file owner's "secondWindow" outlet.

    After the nibs are loaded, I can see in the debugger that wc1->window points to the correct window, but wc2->window is nil. WTF?

    By experimenting, I have discovered that inside -[NSWindowController window], there is some magic happening. If owner->window is pointing at the window being loaded, the window controller's window member gets properly set. Otherwise, it does not.

    That seems wrong, or at least very unexpected. And undocumented.

    Can anyone explain what's going on and why? And what the "right" way to do multiple windowed docs is?

    _murat
  • On May 12, 2012, at 3:39 PM, "<mlist0987...>" <mlist0987...> wrote:

    > I've have come across some surprising behavior when trying to implement a document with two windows, each loaded from its own nib.
    >
    > In my NSDocument subclass, I have
    >
    > - (void) makeWindowControllers
    > {
    > NSWindowController* wc1 = [[[NSWindowController alloc] initWithWindowNibName:@"MNKDocument" owner:self]autorelease];
    > [self addWindowController:wc1];
    > [wc1 window]; // loads nib the first time it's called.
    >
    > NSLog(@"wc1->window = %@", [wc1 window]);
    >
    >
    > NSWindowController* wc2 = [[[NSWindowController alloc] initWithWindowNibName:@"MNKDocumentSecondWindow" owner:self] autorelease];
    > [self addWindowController:wc2];
    > [wc2 window]; // loads nib the first time it's called.
    >
    > NSLog(@"wc2->window = %@", [wc2 window]);
    > }
    >
    >
    > wc1 is loading the default nib provided by Xcode's document-based app template. wc2 is loading a nib I made by simply duplicating the default nib.
    >
    > The "File's Owner" in both nibs is my document subclass. In the first nib file, the "File's Owner" has its "window" outlet connected to the window in the nib. Pretty vanilla.
    >
    > In the second nib, the File Owner's "window" outlet is disconnected. The window in the second nib is connected to the file owner's "secondWindow" outlet.
    >
    > After the nibs are loaded, I can see in the debugger that wc1->window points to the correct window, but wc2->window is nil. WTF?

    Document-as-File's-Owner only makes sense if you aren't overriding -makeWindowControllers.

    Just make the window controllers themselves the File's Owner of the nib by passing 'nil' for the owner argument to the initializer in your override of -makeWindowControllers. Yes, that means a bit more refactoring might be necessary in your nibs.

    --Kyle Sluder
  • On May 12, 2012, at 4:45 PM, Kyle Sluder wrote:

    > Document-as-File's-Owner only makes sense if you aren't overriding -makeWindowControllers.

    I don't think that's the case. Certainly nothing in the docs suggest it.

    Went for a drive and reflected on this a bit. I think the fact that there's a setter for NSDocument's 'window' member but not a getter, and that the underlying _window member is private should have been clues. Furthermore, the documentation for setWindow: suggests that it has a role in the nib loading mechanism.

    So I think my mistake was setting the window outlet in one nib but not the other. My approach was thwarting the very mechanism on which I was depending.

    _murat
  • On May 12, 2012, at 5:42 PM, "<mlist0987...>" <mlist0987...> wrote:

    > On May 12, 2012, at 4:45 PM, Kyle Sluder wrote:
    >
    >> Document-as-File's-Owner only makes sense if you aren't overriding -makeWindowControllers.
    >
    > I don't think that's the case. Certainly nothing in the docs suggest it.

    You are correct that nothing in the docs prohibits passing the document as the file's owner argument for window controllers you construct yourself. And in the case of a single window controller, it might work—though this is not guaranteed.

    But if you have two or more window controllers, and you need to set the window for each of them, then clearly the document's window property is not the appropriate place for this. And it will result in multiple -awakeFomNib: messages being sent to your NSDocument.

    The document-loads-nib pattern is explicitly documented as a convenience wrapper around creating your own NSWindowController instance. The window outlet on NSDocument exists solely for the purpose of supporting this convenience. I wouldn't be surprised if Apple's only anticipated use of the 'owner' argument of NSWindowController's initializer is also to support this convenience.

    If you were to remove NSDocument from this equation entirely, it wouldn't make sense to pass anything other than the window controller as the File's Owner. Since you're not taking advantage of NSDocument's default NSWindowController instance, you probably shouldn't rely on pieces of that functionality to support your app.

    (The rest of your post seems to imply you have come to the same understanding.)

    --Kyle Sluder
  • On May 12, 2012, at 5:55 PM, Kyle Sluder wrote:

    > You are correct that nothing in the docs prohibits passing the document as the file's owner argument for window controllers you construct yourself. And in the case of a single window controller, it might work—though this is not guaranteed.

    Huh. In NSDocument.h, it is written "The default implementation of (makeWindowControllers) ... creates a new window controller ..., specifying this document as the nib file's owner..."

    Presumably, Apple is telling us this so we know how to do it ourselves. If this is a pattern we ought not emulate, this would be an ideal place to mention that.

    > But if you have two or more window controllers, and you need to set the window for each of them, then clearly the document's window property is not the appropriate place for this.

    It's not at all clear.

    > And it will result in multiple -awakeFomNib: messages being sent to your NSDocument.

    Heh, assuming awakeFromNib will only ever be called once is a newbie mistake I've made more than once ;)

    I presume you meant windowControllerDidLoadNib: not awakeFromNib. I would *expect* multiple windowControllerDidLoadNib: calls, once for each nib loaded. That's perhaps why windowControllerDidLoadNib: has a windowController arg; so you can know which nib is being loaded and act appropriately.

    > The document-loads-nib pattern is explicitly documented as a convenience wrapper around creating your own NSWindowController instance.

    Yes, it is convenient.

    > The window outlet on NSDocument exists solely for the purpose of supporting this convenience.

    I don't think so. I think it's there to support the convenience of having the nib's file's owner be the document, just like it is in the single window case.

    If it were important that nibs should be built differently for single and multiple window cases, I'd think the docs would mention it someplace, but they don't.

    _murat
  • Op 13 mei 2012, om 04:11 heeft <mlist0987...> het volgende geschreven:

    > If it were important that nibs should be built differently for single and multiple window cases, I'd think the docs would mention it someplace, but they don't

    They do.

    The documentation of awakeFromNib says
    It is recommended that you maintain a one-to-one correspondence between your File’s Owner objects and their associated nib files.

    You Should Subclass NSWindowController in Document-Based App Programming Guide for Mac says
    Reasons to Subclass NSWindowController: If your document requires or allows multiple windows for a single document, that is a good reason to subclass NSWindowController.
    How to Subclass NSWindowController: The NSWindowController subclass instance should be the File’s Owner for the nib file because that creates better separation between the view-related logic and the model-related logic.

    If each NIB has its own owner, awakeFromNib is only called once.

    Willeke
  • On May 13, 2012, at 3:07 PM, Willeke wrote:

    > How to Subclass NSWindowController: The NSWindowController subclass instance should be the File’s Owner for the nib...

    Thank you. I missed this, probably because I'm not actually subclassing NSWindowController. But it settles it as far as I'm concerned.

    _murat
previous month may 2012 next month
MTWTFSS
  1 2 3 4 5 6
7 8 9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28 29 30 31      
Go to today