tags:

views:

32

answers:

2

I am using a custom subclass of NSDocument and a custom subclass of NSWindowController. The problem is that I cannot reference my custom document from my custom window controller.

In IB, in the TKDocument NIB I have File's Owner set to TKWindowController.

In my TKDocument subclass I have:

- (void) makeWindowControllers {
     TKWindowController *controller = [[TKWindowController alloc] init];
     [self addWindowController:controller];
}

Then in my TKWindowController subclass I overrode setDocument to make sure it was being called:

- (void) setDocument(NSDocument *) document {
     NSLog(@"setDocument:%@", document);
     [super setDocument:document];
}

and then (again in TKWindowController) my action which references the document itself:

- (IBAction) plotClicked:(id) sender {
     TKDocument *doc = [self document];
     NSLog(@"plotClicked %@", doc);
}

The NSLog in setDocument outputs the string returned by my [TKDocument description] override as I'd expect; I only put it there to see if it was being called. However, doc in plotClicked is null.

What might I have done wrong?

EDIT: I believe the problem is to do with NIBs. My Document has its own NIB with File's Owner set to the custom controller as mentioned above. The plotClicked action is fired from a menu item in MainMenu.xib. I believe it's hitting a new instance of the controller which isn't associated with the current, active document.

So, how do I link the two? My question is really this: How do I obtain a handle to the current active document (or its windowcontroller) from MainMenu.xib?

Thanks

A: 

init is not a designated initializer of NSWindowController. You want one of these: – initWithWindow:, – initWithWindowNibName:, – initWithWindowNibName:owner:, or – initWithWindowNibPath:owner:.

Also, from the docs:

In your class’s initialization method, be sure to invoke on super either one of the initWithWindowNibName:... initializers or the initWithWindow: initializer. Which one depends on whether the window object originates in a nib file or is programmatically created.

Joshua Nozzi
Thanks, though I don't think that's it as my `init` method includes a call to `[super initWithWindowNibName:@"TKDocument"]`. If better practice, I will call `initWithWindowNibName` directly from the Document and override it in the Controller to perform my other initialization.
Tim Kemp
TimG: It's fine to call your custom `init` if your custom `init` calls one of NSWindowController's initializers.
Peter Hosey
+1  A: 

My Document has its own NIB with File's Owner set to the custom controller as mentioned above.

The File's Owner of a document nib should be the document. Consider that suspect #1.

The plotClicked action is fired from a menu item in MainMenu.xib. I believe it's hitting a new instance of the controller which isn't associated with the current, active document.

Did you put a window controller inside your main menu nib? If not, then that isn't the problem, since you must have wired up your plotClicked: menu item to the First Responder, and the window controller and its document will be in the responder chain.

If you did, then there's the solution: delete the window controller from the MainMenu nib and hook up your menu item to the First Responder, so that the action message goes down the responder chain, which will enable it to hit the document or window controller.

How do I obtain a handle to …?

The only Handles on the Mac come from Carbon; those Handles do not exist in Cocoa.

Peter Hosey
Peter, thank you. I did have a window controller, which I subsequently removed. I have now connected the menu item to the First Responder and it does now work. I'm not sure *why* it works: I know the event is trickling down the responder chain, but the action plotClicked on my window controller is unconnected in the document nib. I need to read more about this.
Tim Kemp
You said that the File's Owner should be the document. Is this still the case when using a custom window controller? If so, how do I access the controller in the NIB? I did not think it was correct to instantiate a custom window controller in the document NIB. Thanks for your earlier clear answer to a pretty vague question.
Tim Kemp
You don't instantiate the window controller in any nib, as its job is to load the nib. Why do you want to access the controller, and not the document, from the objects in the nib?
Peter Hosey
As for the action: You don't need to hook up the window controller's (or document's) actions to anything directly. As I said, the document and its window controller(s) will be in the responder chain at the appropriate times, so as long as your menu items and controls are targeted at the First Responder, they will hit the document/WCs when appropriate and not when not. Menu items will also disable themselves when no active responder can handle them. Relevant documentation: http://developer.apple.com/mac/library/documentation/Cocoa/Conceptual/EventOverview/
Peter Hosey
I am trying to separate calculation logic from display logic. I put calculation logic, which is basically the model, in my document subclass. Interacting with and visualizing the model is handled by the controller. My thinking with the design was to have the controller handle user interaction (mouse events, button clicks, calling for re-calculation and so on); to do this I'd need access to the controller from the nib. The separation of duties is pretty clear in my app and I didn't want to mingle the two in my document class. I got the idea from Apple's documentation. Happy to learn better tho.
Tim Kemp
Events are typically handled by views and translated into something higher-level, either actions or changing a property. A typical document-based app has the document acting as controller, not model; as the controller, it owns the model, which consists of one or more other objects, and it owns the window(s) and their views by way of its window controller(s). Imagine a wall between Model-Land and View-Land; an NSDocument and its window controller(s) stand atop that wall, with the NSDocument on the Model edge facing Model-Land and the window controller(s) on the View edge facing View–Land.
Peter Hosey
Peter, thank you. I understand conceptually what you're saying. Your wall analogy fits exactly with what I'm trying to achieve. I just don't understand how to reference the controller from IB if it's not the file owner though. I could use the response chain for actions, but what about data bindings? The NSWindowController class ref also says, "The window controller is usually the owner of the nib." Apologies: I am trying to make sense of two conflicting but authoritative sources. I definitely appreciate your explanations. My b/g is C systems programming and Java. Cocoa is rather different.
Tim Kemp
You need to bind your views to some kind of object controller, usually an array controller. Through that, you can bind to the window controller if you really want to. But remember what I said: It's the *document* that should own the model. As such, it's the document that you should bind your object controllers to.
Peter Hosey
Got it. Thank you sir.
Tim Kemp