Dynamic Menus

Discussion in 'Mac Programming' started by osici, Apr 24, 2009.

  1. osici macrumors newbie

    Joined:
    Apr 8, 2009
    #1
    I'm trying to create a submenu for one of my menu items that is dynamically generated (listing the items in an NSMutableDictionary) for the current document. I'm not completely sure how to go about this, but I'm guessing subclassing NSMenu is what I want to be doing and adding a new NSMenuItem for each object in the dictionary. I'd like the new menu items for the contents of the submenu to be generated every time the submenu is accessed (as in when the parent NSMenuItem/NSMenu is clicked).

    My main problems right now are figuring out whether it is NSMenu or NSMenuItem I want to subclass, and which method I want to override to add in the generation behaviour. Can anyone help?

    I'm also a bit confused about how my menu accesses the current document - is this via the first responder? How would I refer to the first responder in code?
     
  2. mesa macrumors newbie

    Joined:
    Jan 19, 2005
    Location:
    Washington, DC
    #2
    No subclassing is necessary, just make your document the menu's delegate, and implement

    Code:
    - (void)menuNeedsUpdate:(NSMenu *)menu;
    or

    Code:
    - (NSInteger)numberOfItemsInMenu:(NSMenu *)menu;
    - (BOOL)menu:(NSMenu *)menu updateItem:(NSMenuItem *)item atIndex:(NSInteger)index shouldCancel:(BOOL)shouldCancel;

    Use the first option if your menu build up is fast, the second option if it takes a lot of processing (otherwise you slow down the menu UI on selection).
     
  3. kpua macrumors 6502

    Joined:
    Jul 25, 2006
    #3
    No subclassing required. This is easily done with delegation. Take a look at the following delegate methods:

    Code:
    -menuNeedsUpdate:
    -numberOfItemsInMenu:
    -menu:updateItem:atIndex:shouldCancel:
    
    I believe you could also achieve this through bindings, but I'm not sure about all the details.

    Your menu (or its delegate) will probably access your document through a direct IB connection, or through some other controlling object that is connected to your document.

    NSDocument is not part of the responder chain—it is not a subclass of NSResponder and therefore never the 'first responder'.
     
  4. osici thread starter macrumors newbie

    Joined:
    Apr 8, 2009
    #4
    Thanks for your help. That seems like it will be simple enough but I'm still a bit confused. I've only been using Objective-C a couple of weeks and have only encountered delegation briefly once before (described below). If I were to set the delegate to be the document, would I do this in IB? If not, where?

    I think I'm getting mixed up between my NSView subclass (which is in the responder chain) and my NSDocument subclass. My NSDocument is a delegate of my NSView. I want to populate the menu with objects from the document I'm currently viewing in my NSView, so I'm guessing this would use the "First Responder" and then access the document via the view's delegate. In this case, would I want to set the menu delegate to be the NSView subclass rather than the NSDocument, would this use the "First Responder", and is it something I'd set in IB?

    Sorry for all the questions, but delegates are something I haven't wrapped my head around yet.
     
  5. sgopinath macrumors newbie

    Joined:
    Jul 6, 2010
    #5
    NSTextFieldCell : context Menu

    Hi,
    i like to display a context menu on user right-clicking a table cell.
    I created the menu in IB and connected the cells setMenu property to it(followed same as drag and drop example from apple).
    But , the menu does not display when i right click on the data cell. I did the same for tableview and it works, the only thing is the menu shows up on clicking any part of table which is not my requirement. i want the menu to be display only if a row or column is selected. Am i missing some settings?. please help.For your info, iam using NSOutlineView for my table

    thanks

    sgopinath
     
  6. misee macrumors member

    Joined:
    Jul 4, 2010
    #6
    No, you're not missing any settings. You just need to implement the delegate methods that mesa gave you. Also, you shouldn't need to connect the cells menu outlet, but the table views menu outlet.
    Then, this code should do the rest:
    Code:
    // tableView is of the type NSTableView (or one of it's subclasses) and connected to the interfaces table view.
    - (NSInteger)numberOfItemsInMenu:(NSMenu *)menu {
    	if ([tableView selection] == -1)
    		return 0;
    	NSInteger numberOfMenuItems;
    
    	// calculate the number of menu items
    
    	return numberOfMenuItems;
    }
    
    - (BOOL)menu:(NSMenu *)menu updateItem:(NSMenuItem *)item atIndex:(NSInteger)index shouldCancel:(BOOL)shouldCancel {
    	// set up the menu item (title, target, action, ...)
    	
    	return YES;
    }
    Note that a right click won't select a row in the table, so nothing will happen if you don't have a selection. You have to subclass NSTableView (or NSOutlineView in your case) to get the desired behavior. I did it this way (based on code from CocoaDev):
    Code:
    - (NSMenu *)menuForEvent:(NSEvent *)theEvent {
    	NSPoint mousePoint = [self convertPoint:[theEvent locationInWindow] fromView:nil];
    	int row = [self rowAtPoint:mousePoint];
    	if (row >= 0) {
    		if (![[self selectedRowIndexes] containsIndex:row])
    			[self selectRowIndexes:[NSIndexSet indexSetWithIndex:row] byExtendingSelection:NO];
    	} else {
    		// you can disable this if you don't want clicking on an empty space to deselect all rows
    		[self deselectAll:self];
    	}
    	
    	// make sure you return something!
    	return [super menuForEvent:theEvent];
    }
    If you don't want to deselect the selected item when clicked on an empty space but don't want to show a menu either, you can return nil in the else block (and remove [self deselectAll:self]).

    Of course, you could also create your menu in the menuForEvent: method, but I prefer to create the menu in a controller class (especially if it is context sensitive).
     

Share This Page