PDA

View Full Version : Working with NSMenu and NSMenuItem




Littleodie914
Aug 1, 2007, 07:41 AM
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:

- (bool)moveToSubject:(NSString *)newSubject;

So that the action of, say, biology, would be:

performAction:@selector(moveToSubject:) withArgument:[self title]

So then it would essentially call

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:



Eraserhead
Aug 1, 2007, 08:33 AM
Can't you call a general method which then calls the specific method you want?

Littleodie914
Aug 1, 2007, 08:51 AM
Can't you call a general method which then calls the specific method you want?I could, but the general method still isn't aware of which MenuItem was selected. That's the problem I keep running into. :o

Eraserhead
Aug 1, 2007, 08:55 AM
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.

lucasgladding
Aug 1, 2007, 09:34 AM
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.

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

kainjow
Aug 1, 2007, 01:59 PM
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.

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.

Littleodie914
Aug 4, 2007, 12:11 PM
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:

Littleodie914
Jan 23, 2008, 08:24 PM
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.

@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

kainjow
Jan 23, 2008, 09:43 PM
Try adding: [currentItem setTarget:self];

Littleodie914
Jan 24, 2008, 07:24 AM
Try adding: [currentItem setTarget:self];Ah, that did it! Thanks! I had no idea you had to do that. :confused:

kainjow
Jan 24, 2008, 07:30 AM
Ah, that did it! Thanks! I had no idea you had to do that. :confused:

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 :)

Littleodie914
Jan 24, 2008, 07:32 AM
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 :)Ah thank you! That totally makes sense, I always wondered how it knew where to find the specified method. Thanks much. :D

joraff
Jan 28, 2011, 01:56 PM
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):

@interface PrinterController : NSObject {
NSMutableArray * printers;
}

- (PrinterController*) init;
- (void) drawFavorites: (NSMenu*)statusMenu;
- (NSArray*) favorites;

@end

@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.

- (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?

Sydde
Jan 28, 2011, 03:36 PM
Any suggestions?

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

-(void)actionMethodName:(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.