My NSDocument subclass (MyDocument) is quite large and I was thinking about making a MyDocumentResponder (subclass of NSResponder) to handle document responder events in order to clean up the code. This seems better to me than a category on MyDocument.
MyDocumentResponder would be instantiated in the MyDocument nib and would have a property that hooks it to its owning MyDocument.
Within MyDocumentResponder, I can implement acceptsFirstResponder, but how do I correctly hook it into the responder chain (and remove it if the document loses its front-most status?
Should I hook it in by adding becomeFirstResponder and resignFirstResponder to MyDocument? In this case, should it go in front of or behind MyDocument?
Related
Is it possible to have validateMenuItem work with NSViewControllers? The docs say NSViewController is a subclass of NSResponder so I would think it would participate in the responder chain, but I have never been able to get it to work. I can make it work with NSView, no problem.
The particular use case I have is a menu item in the MainMenu.xib, connected to First Responder. The selector from my view controller subclass shows up in the First Responder pop up list of selectors in MainMenu, so I would think it would work, but it doesn't. The validateMenuItem method of my subclass is never called. (The one in the app delegate is called, but not for the item which is connected to First Responder.)
I have a menu item whose state should depend on whichever NSDocument is open. From my understanding, to make its state change dynamically I should use the NSMenu delegate method menuNeedsUpdate:.
It seems like I would want to have the menu's delegate be the First Responder in MainMenu.xib. However, Interface Builder won't let me set it as the Main Menu's delegate. How can I make a delegate which will be able to access the currently active document?
I generally make such changes in the validateMenuItem: method being called before the menu is shown. The receiver of the action is asked whether the item is to be enabled or not. But you can do pretty much any change there. Since 10.5 it is also safe to add and remove items during such a call.
So I'm building a program that features the use of the IKImageBrowserView component as a subview in an NSWindow. As a side note, I have a controller object called ImageBrowserController which subclasses NSWindow and is set as the delegate of the NSWindow object of my app.
I have sent IKImageBrowserView the message setCanControlQuickLookPanel:YES to enable it to automatically use the QuickLook functionality to preview image files when the IKImageBrowserView is a first responder to receive key events. Then it took me a while to figure out how to make the IKImageBrowserView a first responder which I finally got working by overriding acceptsFirstResponder inside my ImageBrowserController.
Now I understand that as the delegate to the NSWindow, ImageBrowserController has a place in the responder chain after the event gets triggered on NSWindow. And I understand that as a subview of NSWindow, IKImageBrowserView is in line to be passed events for event handling. What I don't get is where the connection is between the ImageBrowserController being a first responder and the event somehow making it to the IKImageBrowserView. I didn't set NSWindow or IKImageBrowserView as first responders explicitly. So why isn't it necessary for me to implement event handling inside my ImageBrowserController?
EDIT: So after reading the accepted answer and going back to my code I tried removing the acceptsFirstResponder override in my ImageBrowserController and the QuickLook functionality still triggered just like the accepted answer said it would. Commenting out the setCanControlQuickLookPanel:YES made the app beep at me when I tried to invoke QuickLook functionality via the spacebar. I'm getting the feeling that my troubles were caused by user error of XCode in hitting the RUN button instead of the BUILD button after making changes to my code (sigh).
Some of what you are saying regarding the interactions between your objects does not make sense, and it is hard to address your stated question without some background.
As you say, your window delegate has a place at the end of the responder chain, after the window itself. The key point I think you are missing is that GUI elements, such as your IKImageBrowserView, will be at the beginning of the chain, and any one of them in a given window could be the current firstResponder.
When your application gets an event, it passes it off to the key window (which is just the window which currently accepts "key" (i.e., "keystroke") events). That window begins by asking its firstResponder to handle the event. If that object refuses, it passes the event to its own nextResponder, usually its superview, which either handles it or passes it on, until the event has either been handled or passed all the way up to the window object itself. Only then will the window (if it does not handle the event itself) ask its delegate to handle the event.
This means that the connection between the window delegate and the IKImageBrowserView is only through the Responder Chain, and its nature is simply that if the view declines to handle any given event, the delegate may eventually be asked to handle it, if no other object in between them handles it first.
Your window delegate does not need to be a firstResponder. Nor does overriding acceptsFirstResponder on the window delegate have any effect on one of the window's subviews.*
Your window delegate also does not need to (and, indeed should not) be a subclass of NSWindow. All it needs is to be a subclass of NSObject which implements whatever methods from the NSWindowDelegate Protocol you are interested in, and methods to handle any events you might want to catch if they are not handled by other objects.
So, the answer to your explicit question at the end is (and I do not mean this sarcastically): you only need to implement event handling in your window delegate if you want it to handle events itself.
*: IKImageBrowserView already responds YES to acceptsFirstResponder. If there are no other subviews in your window, it will automatically be the firstResponder when your application starts. You can set this initialFirstResponder explicitly in Interface Builder by connecting that outlet on the window to whatever object you want.
I have a document based cocoa application with an item in the application menu hooked up to an IBAction. Clicking the item needs to perform a task that uses an IBOutlet in the main nib file which is using another class, MyDocument. Creating 2 objects of the same class, one in each nib seems to not be working. How can I access the outlet?
Actions for menu items are often sent to the first responder so that whatever is currently selected can act on it.
It sounds like this action is something that works on the current document, then it should be implemented by the document. In this case have the menu send it's action to the first responder and then put the action method in the MyDocument class.
If the action you are trying to send is a custom one: in the Main Menu nib select the First Responder item, add your method name, then connect the menu item's selector to the action.
Read the Responders section of the Cocoa Event-Handling Guide for more info.
To summarize the above, in your NIB/XIB file, in interface builder make the connection to the First Responder object, not to Files Owner or anything else. You'll still be offered a lit of actions across potential first responders.
Cocoa then takes that selector and looks for it, starting with the NSView (if any) that's currently the first responder, then with the NSDocument that's currently in use, then with it's window controller etc etc all the way up to the Application delegate. The first object it checks that actually implements that method, it will use that object (after validating it with that same object).
So:
#interface MyDocumentTypeA : NSDocument {
}
-(void)myMenuAction:(id)sender;
-
#interface MyDocumntTypeB : NSDocument {
}
// -myMenuAction: not implemented here
-
#interface MyApplicationDelegate ... {
}
-(void)myMenuAction:(id)sender;
-
In Interface builder (or even programmatically), if you've linked the "action" of the menu item to a selector named "myMenuAction:" on the First Responder (which equates to not specifying a target when done programmatically), for the above two document subclasses the following will happen.
For MyDocumentTypeA, when the user selects that menu item, MyDocumentTypeA's -myMenuAction: will be invoked. Since MyDocumentTypeB does not implement this action, Cocoa will continue to look up the responder chain until it gets to your application delegate, which does implement it, so it will be invoked here instead.
If Cocoa finds no objects in the responder chain that implement the method, the menu item remains disabled.
There is a way how to do this, I've posted the answer in a similar thread: Access IBOutlet from other class (ObjC)
I have an document-based Cocoa application with a TextView and I would like to capture clicks on it, so I'm trying to intercept Window events like mouseDown, mouseUp, etc. then relate them to my TextView.
I've tried a two things:
1.) I made the TextView the initial first responder for the Window of my document, and overrode the mouseDown event on my document class, but it's not hitting.
2.) I subclassed NSWindow and override mouseDown, and set that subclass to my Window's class in my document xib. That event didn't hit either.
I noticed that the Window's delegate is already set to my File's Owner which is my NSDocument subclass. Why don't the events fire on my NSDocument if my document subclass is the delegate for my Window?
It's not clear why you would expect NSDocument to handle -mouseDown: events for a view in a window. NSDocument doesn't respond to -mouseDown:. NSTextView (as its name suggests) is a subclass of NSView, which is a subclass of NSResponder, which does respond to -mouseDown:.
You should give the Cocoa Event-Handling Guide a good read.
It's also not clear why you want to handle the events and pass them on to views yourself. Cocoa takes care of all of this stuff for you and will likely do a far better job of it. You should clarify your overall goal (as in "why do you want to intercept clicks and forward them to views yourself?") - there may be a far better (and likely easier) way to accomplish it.