Search code examples
objective-ccocoaparentnspanel

Cocoa: NSPanel loses parent and other strange behaviour


I have an issue with an NSPanel acting strangely and have created a Sample App to demonstrate this behaviour.

The App was generated from Xcode 4's template and simply creates a panel and then opens and closes it based on button presses:

enter image description here

The strange behaviour I have observed:

  1. Under Lion, after opening the panel for the first time, the panel follows the main window around, which is correct behaviour. However after closing it and then re-opening it no longer follows the main window around.
  2. Under Snow Leopard, when closing the panel the main window is also closed!

EDIT: Just to be clear; the behaviour I expect is for the panel to follow the main window around when the main window is moved; and for that to be true after the panel is closed and subsequently re-opened. Also I expected the panel and main window to behave the same way under Snow Leopard and Lion.

The important part of the code is here:

@implementation MyAppDelegate

- (void)dealloc
{
    [_panel release];
    [super dealloc];
}

- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
    _panel = [[NSPanel alloc]
             initWithContentRect:NSMakeRect(400, 400, 200, 100)
             styleMask:NSUtilityWindowMask|NSClosableWindowMask|NSTitledWindowMask
             backing:NSBackingStoreBuffered
             defer:YES];
    [_panel setTitle:@"A Panel"];
}

- (IBAction)openPanel:(id)sender
{
    [_panel setParentWindow:[self window]];
    [_panel makeKeyAndOrderFront:sender];
}

- (IBAction)closePanel:(id)sender
{
    [_panel orderOut:sender];
}

@end

Note: I added the unnecessary setParentWindow call to the openPanel method to assert that the parent window is reset every time the panel was opened; it made no difference however.

Can someone please explain what I'm doing wrong?

EDIT: There is some confusion about the missing @synthesize window = _window from the implementation file, but I have just opened another project sample that I created to test for memory leak behaviour and it's not in there either. I am using Xcode 4.4, so it's possibly a bug in that, however I don't think the project templates have changed.


Solution

  • You're not supposed to set the parent-child relationship from the child, but from the parent. The setParentWindow: docs say:

    This method should be called from a subclass when it is overridden by a subclass’s implementation. It should not be called otherwise.

    Instead, use addChildWindow:ordered:, like so:

    - (IBAction)openPanel:(id)sender
    {
        [[self window] addChildWindow:_panel ordered:NSWindowAbove];
        //[_panel setParentWindow:[self window]];
        [_panel makeKeyAndOrderFront:sender];
    }
    

    I didn't test this under Snow Leopard, but it fixes the behavior for me when run on Lion.

    Rob Keniger notes below that on Snow Leopard you should also do [[self window] removeChildWindow:_panel] before ordering the panel out. (I assume this is also a good idea on Lion.)