Working with NSMenu and NSMenuItem

Discussion in 'Mac Programming' started by Littleodie914, Aug 1, 2007.

  1. macrumors 68000

    Littleodie914

    Joined:
    Jun 9, 2004
    Location:
    Rochester, NY
    #1
    Hey guys, I'm having trouble coming up with a solution for an NSMenu. To spill the beans, I've got an NSMenu that contains a list of subjects, based on the data of an NSArrayController. No problem there.

    The problem, however, is setting the actions for the items. Say, for example, I have menu items "Biology," "Chemistry," and "Math." I want to set their actions to something like:

    Code:
    - (bool)moveToSubject:(NSString *)newSubject;
    So that the action of, say, biology, would be:

    Code:
    performAction:@selector(moveToSubject:) withArgument:[self title]
    So then it would essentially call

    Code:
    moveToSubject:@"Biology"
    Problem is, I can only seem to link the action of a NSMenuItem to a single selector, which doesn't make the method calls specific enough to perform the necessary action. I could only perform something like "moveToSubject:" without the arguments. Any ideas? :confused:
     
  2. macrumors G4

    Eraserhead

    Joined:
    Nov 3, 2005
    Location:
    UK
    #2
    Can't you call a general method which then calls the specific method you want?
     
  3. thread starter macrumors 68000

    Littleodie914

    Joined:
    Jun 9, 2004
    Location:
    Rochester, NY
    #3
    I could, but the general method still isn't aware of which MenuItem was selected. That's the problem I keep running into. :eek:
     
  4. macrumors G4

    Eraserhead

    Joined:
    Nov 3, 2005
    Location:
    UK
    #4
    can't you find that out from the sender? i.e. get the NSMMenuItem to call the method -(IBAction)myMethod:(id)sender; and then use [sender title] to get the title of the menu item pressed.
     
  5. macrumors 6502

    Joined:
    Feb 16, 2007
    Location:
    Waterloo, Ontario
    #5
    Using represented objects is probably the best route in the long run (IMHO). The object is taken from the content binding or set via the setRepresentedObject: method.


    - (IBAction)myMethod:sender {
    [sender representedObject];
    }


    If you're using the NSMenu within an NSPopUpButton, you can also call one action for the button rather than setting actions for each NSMenuItem:


    - (IBAction)myMethod:sender {
    [[sender selectedItem] representedObject];
    }


    Bindings allow this to be done very easily if you're using the content (NSArrayController1.@arrangedObjects), contentValues (NSArrayController1.@arrangedObjects.keyPath), and selectedObject (NSArrayController2.@selection.relationshipKeyPath) bindings.

    Luke Gladding

    www.hourlyapp.com
     
  6. Moderator emeritus

    kainjow

    Joined:
    Jun 15, 2000
    #6
    I would not do this. The title is really a localized string, not some data. Use representedObject like lucasgladding said. That is what it is for.
     
  7. thread starter macrumors 68000

    Littleodie914

    Joined:
    Jun 9, 2004
    Location:
    Rochester, NY
    #7
    Alright, thanks for the replies, that seemed to work! One more question though.

    I have a menu extra, an NSMenu inserted into the NSStatusBar's systemStatusBar. This extra is created (and added to the menu) upon the creation of each new MyDocument.

    Problem is, I can't find a way to get rid of it. What types of methods should I use to remove the extra? I've tried putting a simple "removeStatusItem" in my NSWindow subclass's dealloc() method, and passing it the menu, but I get an "EXC_BAD_ACCESS" error every time. TIA! :cool:
     
  8. thread starter macrumors 68000

    Littleodie914

    Joined:
    Jun 9, 2004
    Location:
    Rochester, NY
    #8
    Hate to revive a dead thread, but the title still applies to my issue. :)

    Very simple, I've got an NSMenu with manually added NSMenuItems, but they aren't validating at all. It's currently a subclass of NSPopUpButton, but I think it will be easier to just use the default NSPopUpButton and add the items.

    Anyway, no matter what I do (autoEnabling, setting each item enabled, etc.) none of the menu items are selectable. I'm thinking my problem is with the selector/action portion, but I've never been good with that topic.

    Code:
    @implementation IPCalendarButton
    -(void)awakeFromNib {
    	
    	NSMenu *listOfCalendars = [[NSMenu alloc] initWithTitle:@"Calendar List"];
    	
    	CalCalendarStore *myStore = [CalCalendarStore defaultCalendarStore];
    	NSArray *calendars = [myStore calendars];
    	
    	int i;
    	
    	for (i = 0; i < [calendars count]; i++)
    	{
    		NSMenuItem *currentItem = [[NSMenuItem alloc] initWithTitle:[[calendars objectAtIndex:i] title] action:@selector(setNewDefaultCalendar) keyEquivalent:@""];
    		[listOfCalendars addItem:currentItem];		
    	}
    
    	[self setMenu:listOfCalendars];
    }
    
    - (void)setNewDefaultCalendar {
    	NSLog(@"Setting Default Calendar!");
    }
    @end
     
  9. Moderator emeritus

    kainjow

    Joined:
    Jun 15, 2000
    #9
    Try adding:
    Code:
    [currentItem setTarget:self];
     
  10. thread starter macrumors 68000

    Littleodie914

    Joined:
    Jun 9, 2004
    Location:
    Rochester, NY
    #10
    Ah, that did it! Thanks! I had no idea you had to do that. :confused:
     
  11. Moderator emeritus

    kainjow

    Joined:
    Jun 15, 2000
    #11
    Well anytime you're assigning an action via a @selector to an object, you have to think about how it knows which object to call that selector on. Usually there are two methods. setAction: and setTarget: but you already set the action in the initWith.. function so you just needed to set the target to self.

    Also as an alternative, you could give your popup button a target and action in the same way (as you do a normal button) and then use the normal popup button methods to access the selected item. The same thing can be done in many ways :)
     
  12. thread starter macrumors 68000

    Littleodie914

    Joined:
    Jun 9, 2004
    Location:
    Rochester, NY
    #12
    Ah thank you! That totally makes sense, I always wondered how it knew where to find the specified method. Thanks much. :D
     
  13. macrumors newbie

    Joined:
    Jul 26, 2010
    #13
    Hate to bring up an old thread, but its very relevant to an app I'm working on.

    I'm working on a small utility for my organization for internal-use only. Possibly open sourced in the future. It's to help quickly install/uninstall local printers through a Status Item. I've never done and Mac programming before, so I'm learning as I go..

    So Far I have a PrinterController and Printer class (in addition to the appdelegate class):

    Code:
    @interface PrinterController : NSObject {
    	NSMutableArray * printers;
    }
    
    - (PrinterController*) init;
    - (void) drawFavorites: (NSMenu*)statusMenu;
    - (NSArray*) favorites;
    
    @end
    Code:
    @interface Printer : NSObject {
    	NSString *displayName;
    	NSString *queueName;
    	NSString *locationName;
    	NSString *lpURI;
    	BOOL favorite;
    }
    
    @property (retain) NSString* displayName;
    @property (retain) NSString* queueName;
    @property (retain) NSString* locationName;
    @property (retain) NSString* lpURI;
    @property BOOL favorite;
    
    - (id)   initWithDisplayName: (NSString*)d
    				   queueName: (NSString*)q
    				locationName: (NSString*)l
    						 uri: (NSString*)u
    					favorite: (BOOL)f;
    
    - (void) install;
    The app reads printers from a property list file and adds the "favorited" printers to the NSMenu, and links them to the printer object directly. However, I think I'm supposed to link them to a controller method instead? But I can't figure out how to tell which object in the menu was clicked to handle the install or uninstall of the printer. Another plus to linking to the controller is I'd have access to the state and setState methods of the NSMenuItem.

    Code:
    - (PrinterController *) init
    {
    	[super init];
    	
    	printers = [[NSMutableArray alloc] init];
    	
    	NSString *nodePath = [[NSBundle mainBundle] pathForResource:@"Printers" ofType:@"plist"];
    	NSDictionary *printerDict = [NSDictionary dictionaryWithContentsOfFile:nodePath];
    	
    	if (printerDict) {
    		for (id key in printerDict) {
    			NSDictionary *p = [printerDict objectForKey:key];
    			Printer *p1 = [[Printer alloc] initWithDisplayName:[p valueForKey:@"displayName"]
    													 queueName:[p valueForKey:@"queueName"]
    												  locationName:[p valueForKey:@"locationName"]
    														   uri:[p valueForKey:@"lpURI"]
    													  favorite:[[p valueForKey:@"favorite"] boolValue]];
    			[printers addObject:p1];
    			[p1 release];
    		}
    	}
    	return self;
    }
    Any suggestions?
     
  14. macrumors 68000

    Sydde

    Joined:
    Aug 17, 2009
    #14
    I think the simplest approach would be to give the printer controller an instance variable that points either to the NSStatusItem or its menu. Then the controller can build the menu alongside the array of printers.

    After that, you need an action method (probably once again in the controller object) to point each menu item to. The action method is defined in the form

    Code:
    -(void)[I][COLOR="Blue"]actionMethodName[/COLOR][/I]:(id)sender;
    This method will be called when a menu item is selected, with sender = menuItem. Look through some of the above post for clues how to set up each menu item and retrieve which printer it represents.
     

Share This Page