 |
 |
Cocoa and dynamic menus
|
 |
|
 |
|
Mac Enthusiast
Join Date: Apr 2000
Location: Belgium
Status:
Offline
|
|
Does anyone have any idea of how to make a menu dynamic (as you would do when representing a filesystem in an hierarchic menu, each submenu only loads its contents when it is selected by the user) in Cocoa? I've tried subclassing NSMenuItem and overriding a bunch of messages (like title and submenu) but none of them do the trick. title is cached and submenu is called on all submenus at once when the root menu opens.
|
|
|
| |
|
|
|
 |
|
 |
|
Mac Enthusiast
Join Date: Sep 2000
Location: Vermont, USA
Status:
Offline
|
|
I had a similar problem with caching for an NSPopUpButton. I never did get the exact behavior I wanted. Good luck.
|
|
|
| |
|
|
|
 |
|
 |
|
Mac Elite
Join Date: Feb 2001
Location: Vancouver, WA
Status:
Offline
|
|
For our dynamic menus (such as the Script menus in OmniOutliner and OmniGraffle), we override -submenu, and do our own caching so as not to kill performance.
I'm not sure if it's in our latest source release (if not, there'll be another release soon), but you can find the code in OAScriptMenuItem in the OmniAppKit framework.
|
|
|
| |
|
|
|
 |
|
 |
|
Mac Enthusiast
Join Date: Apr 2000
Location: Belgium
Status:
Offline
|
|
How do I override the caching then? I understood that the submenus are only loaded once, and all at the same time, when the menu is first opened.
|
|
|
| |
|
|
|
 |
|
 |
|
Mac Elite
Join Date: Feb 2001
Location: Vancouver, WA
Status:
Offline
|
|
If you override -submenu you should find that it's getting called every time the menu is pulled open (as well as on every other event that NSApp processes!), and the results aren't getting cached like they are when you override -title. (Because -submenu is called so often, you'll want to make sure to either do your own caching or have the code that generates your dynamic menu be extremely efficient.) At least, that's the way it worked when we implemented our Scripts menu, and we haven't seen it break since...
|
|
|
| |
|
|
|
 |
|
 |
|
Senior User
Join Date: Nov 2000
Status:
Offline
|
|
-(BOOL)validateMenuItem:(NSMenuItem *)item;
see; /Developer/Examples/AppKit/MenuMadness/
The sample code looks pretty straight forward.
Code:
// Summary version..
-(BOOL)validateMenuItem:(NSMenuItem *)item
{
int tag = [item tag];
// I'm assuming -not quite sure how this works.
// I didn't bother testing it or reading the docs - sosumi
if (tag == notenabled) return NO;
if (tag == interesting) {
[item setTitle: @"dynamic title"];
// and so on and so forth
}
return YES;
}
|
|
|
| |
|
|
|
 |
|
 |
|
Mac Enthusiast
Join Date: Apr 2000
Location: Belgium
Status:
Offline
|
|
Well, I tried overriding submenu and it gets called when the menu opens, for every submenu. So it's not very suitable for say a menu representation of a hierarchic filesystem (since that would cause the whole filesystem to be scanned when the menu opens, instead of doing it one bit at the time when appropriate, ie scan a folder when the folder's submenu is opened).
As for IamBob's suggestion; that only works on submenus with a fixed number of menuitems. Try inserting menuitems.
|
|
|
| |
|
|
|
 |
|
 |
|
Professional Poster
Join Date: Dec 2000
Location: Chicago, Illinois
Status:
Offline
|
|
Originally posted by Evinyatar:
Well, I tried overriding submenu and it gets called when the menu opens, for every submenu. So it's not very suitable for say a menu representation of a hierarchic filesystem (since that would cause the whole filesystem to be scanned when the menu opens, instead of doing it one bit at the time when appropriate, ie scan a folder when the folder's submenu is opened).
No. -submenu gets called as soon as you click on one menuitem, and then you can dynamically create all the menu items that you need in the submenu. This seems like a pretty obvious way to do things:
Code:
-(NSMenu *)submenu
{
//we'll assume we have a global NSMenu* called cachedMenu
if(!cachedMenu)
{
cachedMenu = [[NSMenu alloc] init];
//use efficient code to add instances of this subclass of NSMenuItem
//to cachedMenu
}
return cachedMenu;
}
It would probably be very helpful to have an initialized of your subclass of NSMenuItem to take an argument of the last path so you can store that and figure out what the next paths will be.
A for IamBob's suggestion; that only works on submenus with a fixed number of menuitems. Try inserting menuitems.
I'm not sure how often or when validateMenuItem is called, but I think it's only called when the root menu is opened, but I could be wrong about that.
Matt
|
|
|
| |
|
|
|
 |
|
 |
|
Senior User
Join Date: Nov 2000
Status:
Offline
|
|
I'm not sure how often or when validateMenuItem is called, but I think it's only called when the root menu is opened, but I could be wrong about that.
I'm not sure how often or when it's called either but I'd hope that it gets called before a menu or submenu is opened.
I'm still thinking validateMenuItem is what you want. It seems like the most logical place to "validate" your menu items. heh, imagine that!
Ok, I'm curious now. Time to bust out with the MenuMadness example and see what's what. 
|
|
|
| |
|
|
|
 |
|
 |
|
Mac Enthusiast
Join Date: Apr 2000
Location: Belgium
Status:
Offline
|
|
Originally posted by Ghoser777:
No. -submenu gets called as soon as you click on one menuitem, and then you can dynamically create all the menu items that you need in the submenu.
Then I can only guess that NSStatusItems handle their menus differently than the regular menubar, because when I put a printf("submenu\n"); in the submenu message handler, I get a whole list of 'submenu' in the stdio when I open my menu, which only has one submenu which in turn have several submenus. And I get no additional 'submenu' in the stdio when I open a submenu either. I guess this just proves again how inconsistent Apple is, and that NSStatusItems are just a really dirty hack.
|
|
|
| |
|
|
|
 |
|
 |
|
Mac Enthusiast
Join Date: Apr 2000
Location: Belgium
Status:
Offline
|
|
Originally posted by IamBob:
I'm not sure how often or when it's called either but I'd hope that it gets called before a menu or submenu is opened.
Oddly it isn't being called at all here. I double checked to make sure autoEnableItems is on and it is (both noAutoenable and supressAutoenable flags are off when I checked them in the debugger). It is supposed to go in the app's delegate, right?
|
|
|
| |
|
|
|
 |
|
 |
|
Senior User
Join Date: Nov 2000
Status:
Offline
|
|
Automatic Menu Enabling
When you use automatic menu enabling, NSMenu updates the status of every menu item whenever a user event occurs. To update the status of a menu item, NSMenu searches the appropriate objects to find one that responds to the menu item's action method. If it can't find such an object, it disables the menu item. If it does find such an object, it then checks to see if that object responds to the validateMenuItem: method. If validateMenuItem: is not implemented in that object, the menu item is enabled. If it is implemented, the return value of validateMenuItem: indicates whether the menu item should be enabled or disabled.
These are the objects that NSMenu searches, in the order it searches them:
1. The NSMenuItem's target. If the target is non-NULL, the search ends here whether the target responds or not.
2. The key window's responder chain, starting with its first responder.
3. The key window itself.
4. The key window's delegate.
5. The main window's responder chain, starting with its first responder.
6. The main window itself.
7. The main window's delegate.
8. The NSApplication object.
9. The NSApplication object's delegate.
Quoted from: /Developer/Documentation/Cocoa/TasksAndConcepts/ProgrammingTopics/MenuList/index.html
After reading that it should make more sense. I glanced through it real quick and it mostly makes sense to me. I didn't have time to fool with MenuMadness like I thought I would but that should be a better source of info anyway. 
|
|
|
| |
|
|
|
 |
|
 |
|
Fresh-Faced Recruit
Join Date: Sep 2000
Location: Monopoli, Bary, ITALY
Status:
Offline
|
|
I have created a function that semplify the menu validation process. It can be usefull for someone. Here is the code:
BOOL AutoItemValidator( id obj,id menuItem ) {
NSString* itemActionString = NSStringFromSelector( [menuItem action] );
NSString* itemValidationString = [@"validate_" stringByAppendingString:itemActionString];
SEL itemValidationSelector = NSSelectorFromString( itemValidationString );
NSMethodSignature* signature = [[obj class] instanceMethodSignatureForSelector:itemValidationS elector];
BOOL result = YES;
if( signature ) {
NSInvocation* anInvocation = [NSInvocation invocationWithMethodSignature:signature];
[anInvocation setSelector:itemValidationSelector];
[anInvocation setTarget:obj];
[anInvocation setArgument:&menuItem atIndex:2];
[anInvocation invoke];
[anInvocation getReturnValue:&result];
}
return result;
}
In the object that respond to some menu action's methods:
- (IBAction)menuAction1:(id)sender
- (IBAction)menuAction2:(id)sender
- (IBAction)menuAction3:(id)sender
- etc...
define the validator methods:
- (BOOL)validate_menuAction1:(id)sender
- (BOOL)validate_menuAction2:(id)sender
- (BOOL)validate_menuAction3:(id)sender
- etc...
and define:
- (BOOL)validateMenuItem:(id <NSMenuItem>)menuItem
{
return AutoItemValidator( self,menuItem );
}
AutoItemValidor will call the corrected validator method.
For example, here are an action method tha toggle a panel and the validate_action method that change the menu item title:
- (IBAction)toggleMyPanel:(id)sender
{
MyPanelController* panelController = [MyPanelController getSharedUnitPanelController];
if( [[panelController window] isVisible] ) {
[panelController close];
}
else {
[panelController showWindow:self];
}
}
- (BOOL)validate_toggleMyPanel:(id)sender
{
MyPanelController* panelController = [MyPanelController getSharedMyPanelController];
if( [[panelController window] isVisible] ) {
[sender setTitle:NSLocalizedString(@"Hide Panel",nil)];
}
else
[sender setTitle:NSLocalizedString(@"Show Panel",nil)];
return YES;
}
You must use automatic menu enabling.
This is an example of the Objective-C power. We can take a method name string (menuAction, foo, ...) from a method, change it in some other string (validate_menuAction, validate_foo, validate_...), and than call the method with this new name.
I think that subitems can be added or modified in the validator method.
Sorry for my poor english.
(Last edited by antofic; Oct 18, 2002 at 08:57 AM.
)
|
|
|
| |
|
|
|
 |
|
 |
|
Mac Enthusiast
Join Date: Apr 2000
Location: Belgium
Status:
Offline
|
|
Originally posted by IamBob:
Quoted from: /Developer/Documentation/Cocoa/TasksAndConcepts/ProgrammingTopics/MenuList/index.html
After reading that it should make more sense. I glanced through it real quick and it mostly makes sense to me. I didn't have time to fool with MenuMadness like I thought I would but that should be a better source of info anyway.
Yeah, got validateMenuItem to work now, but as with the submenu message, this one too gets called for all submenus recursively when the root menu is opened. I think it is safe to conclude that what I'm trying to achieve is impossible with the current implementation.
|
|
|
| |
|
|
|
 |
|
 |
|
Senior User
Join Date: Nov 2000
Status:
Offline
|
|
I fooled around with MenuMadness a bit earlier. I tried all sorts of stuff. As far as I care, if it is possible, it's too damn hard to do.
Obviously, there's no notification or delegate methods that tells you before(or when) a submenu is opened. There's no way (besides extreme hackery?) to figure it out from a subclass either...
Quite the omission on Apple's part. I'd almost call it a bug! 
|
|
|
| |
|
|
|
 |
 |
|
 |
|
|
|
|
|

|
|
 |
Forum Rules
|
 |
 |
|
You may not post new threads
You may not post replies
You may not post attachments
You may not edit your posts
|
HTML code is Off
|
|
|
|
|
|
 |
 |
 |
 |
|
 |
|