PDA

View Full Version : Few Advanced Cocoa Questions




Littleodie914
Jul 8, 2007, 06:39 PM
Hey all, I'm back again with a few cocoa questions I've yet to find the answers to on Google. To the experienced programmer these should be a piece of cake. :)

1) In a Document-based app, how do you connect menu items to the methods? I have an IPController class, instantiated in both MyDocument.nib and MainMenu.nib. I want to connect a menu item to the class so it can show a help panel existing in MyDocument.nib. Is this possible, or do I have to have an duplicate instance to the same panel in both NIBs?

(The "Help..." menu should open it from MainMenu, and there's a button on a MyDocument window that opens it too.)

2) Toolbar items. Lets say I have a method in IPController.m that opens a sheet. There's an NSButton on the window itself whose action is connected to the method in said IPController class. Now, for the toolbar to work, all the code for it is in MyDocument.m. Problem is connecting the action of the toolbar item to the method in IPController. Any ideas?

Right now I have the action set to "-(void)blahHelper()" which tells an instance of IPController to performSelector(). This doesn't work though. The original method in IPController opens a new sheet, but when I call the method through the toolbar item, I get a "Model session requires modal window" problem.

Thanks for the read and the help, I hope I made this easy enough to understand. :)



Littleodie914
Jul 8, 2007, 08:14 PM
Ah, and I almost forgot a third problem I ran into:

3) There's a simple NSSearchField in my toolbar, and the only search predicate it is registered for is "Task Name." Problem is, this isn't activated by default. So when you type something into the bar initially, nothing happens. You have to pull the menu down from the magnifying class and select "Task Name" before it will search correctly. Any way to programmatically set this? I couldn't find anything in IB, but my previous search field I used that wasn't in a toolbar did it automatically. :eek:

Eraserhead
Jul 9, 2007, 03:37 AM
Hey all, I'm back again with a few cocoa questions I've yet to find the answers to on Google. To the experienced programmer these should be a piece of cake. :)

1) In a Document-based app, how do you connect menu items to the methods? I have an IPController class, instantiated in both MyDocument.nib and MainMenu.nib. I want to connect a menu item to the class so it can show a help panel existing in MyDocument.nib. Is this possible, or do I have to have an duplicate instance to the same panel in both NIBs?

I do it like this:

-(IBAction)actionName:(id)sender{
MyDocument *currentDocument=(MyDocument *)[[NSDocumentController sharedDocumentController] currentDocument];
if(currentDocument==nil){
currentDocument=(MyDocument *)[[NSApp orderedDocuments] objectAtIndex:0];
if(currentDocument==nil){
NSLog(@"currentDocument playing up.");
NSBeep();
return;
}
}
[currentDocument theActionIWantToRun];
}


The other two, I have no idea.

janey
Jul 9, 2007, 03:41 AM
I don't have answers to your questions, but have you considered asking in #macdev on freenode?

Nutter
Jul 9, 2007, 07:52 AM
1) In a Document-based app, how do you connect menu items to the methods? I have an IPController class, instantiated in both MyDocument.nib and MainMenu.nib. I want to connect a menu item to the class so it can show a help panel existing in MyDocument.nib. Is this possible, or do I have to have an duplicate instance to the same panel in both NIBs?


First of all, why do you have two instances of this IPController class? If the only reason is so that you can make connections in your NIBs, it's almost certainly better to set up connections between objects in different NIBs programmatically, using the File's Owner object as the entry point to the graph of objects in your NIB.

To answer your question, I would implement the action method for opening the panel in your NSDocument subclass. Then, just connect the menu item to the "First Responder" proxy in MainMenu.nib, adding an action name that matches the name of your NSDocument subclass' action method.

The great thing about doing it this way is that Cocoa will automatically enable and disable the menu depending on whether or not a document is the main/key window.

The responder chain is a wonderful thing. You can read more about it here:
http://developer.apple.com/documentation/Cocoa/Conceptual/CocoaFundamentals/CoreAppArchitecture/chapter_7_section_6.html

robbieduncan
Jul 9, 2007, 08:03 AM
First of all, why do you have two instances of this IPController class? If the only reason is so that you can make connections in your NIBs, it's almost certainly better to set up connections between objects in different NIBs programmatically, using the File's Owner object as the entry point to the graph of objects in your NIB.

To answer your question, I would implement the action method for opening the panel in your NSDocument subclass. Then, just connect the menu item to the "First Responder" proxy in MainMenu.nib, adding an action name that matches the name of your NSDocument subclass' action method.

The great thing about doing it this way is that Cocoa will automatically enable and disable the menu depending on whether or not a document is the main/key window.

The responder chain is a wonderful thing. You can read more about it here:
http://developer.apple.com/documentation/Cocoa/Conceptual/CocoaFundamentals/CoreAppArchitecture/chapter_7_section_6.html

This is the correct solution. I was going to wait till I was home to post something similar but you've saved me the trouble which is ace :D

I did an ultra-simple demo of this that I posted here in the past to show how it worked. I wonder if it's still around...


Edit to add: it's in this thread (http://forums.macrumors.com/showthread.php?t=226894&highlight=robbieduncan) from ages back that basically asked the same thing in a different way...

Eraserhead
Jul 9, 2007, 09:30 AM
This is the correct solution. I was going to wait till I was home to post something similar but you've saved me the trouble which is ace :D

So my way is wrong/worse :o, oh well it's better that way in my application as my enable/disable requirements are a little more complex as I want to disable most actions when a sheet is displayed.

robbieduncan
Jul 9, 2007, 10:53 AM
So my way is wrong/worse :o, oh well it's better that way in my application as my enable/disable requirements are a little more complex as I want to disable most actions when a sheet is displayed.

If it works the it's not necessarily wrong as such. This just saves having extra actions to simply forward the message to another object, saves working out the document object as the system can do it and: more importantly would automatically disable the actions when a sheet was showing :p

Littleodie914
Jul 9, 2007, 11:07 AM
Ah, thanks so much! That's much easier than all the ways I was trying to go about it.

One problem though, after the connections, the menu items are permanently disabled. So while it would probably work, I can't click on the little buggers! :rolleyes:

robbieduncan
Jul 9, 2007, 11:14 AM
Ah, thanks so much! That's much easier than all the ways I was trying to go about it.

One problem though, after the connections, the menu items are permanently disabled. So while it would probably work, I can't click on the little buggers! :rolleyes:

If they are disabled then Cocoa cannot find a method with the correct signature anywhere in the responder chain. Can you post your code (zip the project without any generated files)?

Littleodie914
Jul 9, 2007, 11:25 AM
If they are disabled then Cocoa cannot find a method with the correct signature anywhere in the responder chain. Can you post your code (zip the project without any generated files)?Gah, would if I could, the project directory zipped is a little over 3 megs :o

Final build project zipped is only 300k, so I'm not too scared yet! Just to make sure I've got this right though:

1) I have a method in MyDocument.m called openHelpWindow:, and is an IBAction.
2) In MainMenu.nib, I added an action to First Responder called "openHelpWindow:" and connected the menu item to that action.

Wah-lah? :confused:

Edit: The connections work right, I just connected a button on MyDocument.nib to its First Responder with the same method, openHelpWindow:, and it worked fine. So the problem is just that the menu item's aren't correctly being enabled.

robbieduncan
Jul 9, 2007, 11:34 AM
Just the code (not all the intermediate build files and so on) is 3Mb? :eek:

Do you have anything else that might be setting the enabled status of the menus? A validateMenuItem method for example?

Littleodie914
Jul 9, 2007, 11:46 AM
Just the code (not all the intermediate build files and so on) is 3Mb? :eek:

Do you have anything else that might be setting the enabled status of the menus? A validateMenuItem method for example?Here we go, turns out I accidentally copied a few frameworks in there when linking.

robbieduncan
Jul 9, 2007, 11:48 AM
Here we go, turns out I accidentally copied a few frameworks in there when linking.

Cool. I'm at work (on a PC worse luck) right now: I'll take a look when I get home...

kainjow
Jul 9, 2007, 11:52 AM
Here we go, turns out I accidentally copied a few frameworks in there when linking.

You forgot the iLifeControls framework :)

Littleodie914
Jul 9, 2007, 12:00 PM
You forgot the iLifeControls framework :)Gah, sorry about that! :)

robbieduncan
Jul 9, 2007, 01:10 PM
OK I'm confused.

In MyDocument.m there is a method called - (IBAction)openHelpWindow:(id)sender. In MainMenu.nib the FirstResponder has an action called openHelpPanel: but not openHelpWindow: So the action has not been connected.

I also cannot work out which menu item you think is sending an openHelpMenu: action?

robbieduncan
Jul 9, 2007, 01:13 PM
Also I can't build as it appears that your project expects the iLifeControls framework to be in /System/Library/Frameworks. That's exceptionally bad. Nothing should be adding stuff to there. The Framework should be self contained within the app.

Littleodie914
Jul 9, 2007, 01:25 PM
Ah, got the menu items working! You were right, I had changed the method names around in a fit of frustration, and forgot to change First Responder. :D

As for the framework issue, I'm still a bit confused on how the build phases work. Lets say I have two frameworks, DiscRecording.framework, stored in /Sys/Lib/Frameworks/ and iLifeControls.framework, on my desktop.

Do I drag each one into the Linked Frameworks folder in my app source list? Do I copy them into the destination group's folder? Do I need to create a new "copy files" or "link binary with library" build phase? There just seem to be way too many ways to configure these little guys. ;)

kainjow
Jul 9, 2007, 01:32 PM
As for the framework issue, I'm still a bit confused on how the build phases work. Lets say I have two frameworks, DiscRecording.framework, stored in /Sys/Lib/Frameworks/ and iLifeControls.framework, on my desktop.

Do I drag each one into the Linked Frameworks folder in my app source list? Do I copy them into the destination group's folder? Do I need to create a new "copy files" or "link binary with library" build phase? There just seem to be way too many ways to configure these little guys. ;)

If it's a System framework, you drag it in, but do not copy it over and do not create a copy files build phase. If it's a custom framework, you usually copy it into your project folder and you always create a copy files build phase.

robbieduncan
Jul 9, 2007, 01:33 PM
DiscRecording.framework is built in so it is part of the System. Therefore it gets to live in /System

iLifeControls is not. It should be built for embedding and the linked via the Linked Frameworks AND copied in a Copy Files phase.

There's a video tutorial (http://rentzsch.com/cocoa/embeddedFrameworks)

Littleodie914
Jul 10, 2007, 12:28 PM
If it's a System framework, you drag it in, but do not copy it over and do not create a copy files build phase. If it's a custom framework, you usually copy it into your project folder and you always create a copy files build phase.

DiscRecording.framework is built in so it is part of the System. Therefore it gets to live in /System

iLifeControls is not. It should be built for embedding and the linked via the Linked Frameworks AND copied in a Copy Files phase.

There's a video tutorial (http://rentzsch.com/cocoa/embeddedFrameworks)Thanks so much guys, I think I've got it figured out now. :D

I guess the last thing I'm having trouble with is connecting two different classes. Simple example: Programmatically (in MyDocument.m) setting the action of a toolbar item to a method in IPController.m. :) I've tried performSelector, but it doesn't quite work the same. (For example, the IPController method opens a sheet, but when called within MyDocument, results in a "modal window session" type error.)

GeeYouEye
Jul 10, 2007, 12:37 PM
In the iLifeControls project, make sure you have the installation path set to @executable_path/../Frameworks

kainjow
Jul 10, 2007, 12:39 PM
I guess the last thing I'm having trouble with is connecting two different classes. Simple example: Programmatically (in MyDocument.m) setting the action of a toolbar item to a method in IPController.m. :) I've tried performSelector, but it doesn't quite work the same. (For example, the IPController method opens a sheet, but when called within MyDocument, results in a "modal window session" type error.)

If I have this right, each MyDocument has an instance of IPController, correct? Why not just create in MyDocument an IBOutlet for IPController, and connect that up in the nib, since you instantiate IPController in the nib anyways. Then in MyDocument.m you can set your toolbar items' targets to controller, and use setAction: to set a selector that controller responds to. Makes sense? :)

Also, it is best to have one window/view per nib if you can. Then you're not wasting memory/speed loading all those objects into memory. With a different nib for each window/view, that nib only gets loaded when it's needed. For example, the Preferences isn't called very often, so putting that in a separate nib causes that nib to only load when you open the Preferences the first time.

Littleodie914
Jul 10, 2007, 12:50 PM
If I have this right, each MyDocument has an instance of IPController, correct? Why not just create in MyDocument an IBOutlet for IPController, and connect that up in the nib, since you instantiate IPController in the nib anyways. Then in MyDocument.m you can set your toolbar items' targets to controller, and use setAction: to set a selector that controller responds to. Makes sense? :)

Also, it is best to have one window/view per nib if you can. Then you're not wasting memory/speed loading all those objects into memory. With a different nib for each window/view, that nib only gets loaded when it's needed. For example, the Preferences isn't called very often, so putting that in a separate nib causes that nib to only load when you open the Preferences the first time.Ah great, thanks for the tip with the NIBs, I had no idea.

And I'll give the IPController outlet a shot, I don't know why I didn't think of that before, it seems like the most logical way to do it! :)

Littleodie914
Jul 10, 2007, 01:13 PM
Hm, now I'm getting a "parse error before IPController" with the line:

IBOutlet IPController *myController;

I'm also using

#import "IPController.h"

at the top, any clues? :confused:

kainjow
Jul 10, 2007, 01:18 PM
Use @class IPController; instead, and use #import "IPController.h" in MyDocument.m

Littleodie914
Jul 10, 2007, 01:35 PM
Use @class IPController; instead, and use #import "IPController.h" in MyDocument.mAh hah, thanks, I've never used the @class thing before. Does it just tell MyDocument that the IPController is actually a class?

MongoTheGeek
Jul 10, 2007, 02:02 PM
Ah hah, thanks, I've never used the @class thing before. Does it just tell MyDocument that the IPController is actually a class?

Its a flag for the compiler to accept the fact that there is a class of object called IPController and not to ask to many questions about it for now.

Think forward definition. But in this case its mostly to shrink header files.