I have a menu where some of the menu items use bindings to get their title. These items are always enabled, and don't neither automatically enable/disable like they should NOR do they cause a call to validateUserInterfaceItem:. If you remove the binding on title, then that starts working again. The menu items have the target set to nil (First Responder). If you click on one, it does execute the selector (action).
Bug? What to do?
For some reason when you set a menu item's title with bindings, the menu item becomes enabled
even if the target/action are nil.
If you want to permanently disable the menu item you can workaround this by binding the menu item's enabled status to a constant NO:
NSNumber *alwaysNo = [NSNumber numberWithBool:NO];
[menuItem bind:#"enabled" toObject:alwaysNo withKeyPath:#"boolValue" options:nil];
Note that this isn't the most elegant workaround, but in my case it was still cleaner than not using bindings for the title.
Related
I'd like to create an NSMenu containing an NSMenuItem which is hidden by default, and only appears while the user is holding a keyboard modifier key.
Basically, I'm looking for the same behaviour as the 'Library' option in the Finder's 'Go' Menu:
Without holding Option (⌥):
While holding Option (⌥):
I already tried installing a key listener using [NSEvent addGlobalMonitorForEventsMatchingMask: handler:] to hide and unhide the NSMenuItem programmatically by setting it's hidden property. This kind of worked, but the problem is that the hiding/unhiding wouldn't work while the NSMenu was open. Apparently an NSMenu completely takes over the event processing loop while it's open, preventing the key listener from working.
I could probably use a CGEventTap to still receive events while the NSMenu is open, but that seems like complete overkill.
Another thing I discovered which does a similar thing to what I want is the 'alternate' mechanism of NSMenu. But I could only get it to switch out NSMenuItems, not hide/unhide them.
Any help would be greatly appreciated. Thanks!
Let's say your option-only menu item's action is (in Swift) performOptionOnlyMenuItem(_:) and its target is your AppDelegate.
The first thing you need to do is make sure AppDelegate conforms to the NSMenuItemValidation protocol.
The second thing you need to do is implement the validateMenuItem(_:) method, and have it check whether the menu item sends the performOptionOnlyMenuItem(_:) action. If so, set the item's isHidden property based on whether the option key is currently pressed.
If you don't need to validate any other menu items, the code can look like this:
extension AppDelegate: NSMenuItemValidation {
func validateMenuItem(_ menuItem: NSMenuItem) -> Bool {
switch menuItem.action {
case #selector(performOptionOnlyMenuItem(_:)):
let flags = NSApp.currentEvent?.modifierFlags ?? []
menuItem.isHidden = !flags.contains(.option)
return true
default:
return true
}
}
}
If the action is sent to some other target, you need to implement the validation (including the protocol conformance) on that target. Each menu item is validated only by the item's target.
I found a solution that behaves perfectly!
On the NSMenuItem you want hidable, set the alternate property to YES, and set the keyEquivalentModifierMask property to the keyboard modifiers which you want to unhide the item.
In your NSMenu, right before the NSMenuItem which you want to be hideable, insert another NSMenuItem that has height 0.
In Objc, you can create an NSMenuItem with height 0 like this:
NSMenuItem *i = [[NSMenuItem alloc] init];
i.view = [[NSView alloc] initWithFrame:NSZeroRect];
The hideable NSMenuItem will now be 'alternate' to the zero-height NSMenuItem preceding it. The zero-height item will display by default, but while you hold the keyboard modifier(s) you specified, the zero-height item will be swapped out with the hideable item. Because the zero-height item is invisible, this has the effect of unhiding the hideable item.
I have subclassed NSMenu and connected a bunch of NSMenuItem's via Interface Builder. I have tested via the debugger to see that they really get initialized.
The menu is set to not auto enable items. Still when I set any of my NSMenuItem's to [myMenuItem setEnabled:NO] they continue to be enabled. Even if I call [self update] from within the NSMenu subclass.
What am I missing?
Had the same issue, so I thought I'd post my solution. NSMenu auto enables NSMenuButtons, so we have to override that.
In IB:
Or programmatically:
// Disable auto enable
[myMenu setAutoenablesItems:NO];
// Test it
[myMenuButton setEnabled:NO];
[myMenuButton setEnabled:YES];
I solved it with the help of a colleague, so I post it here for others that experience the same issue.
You should set your NSMenu-sublass to auto-enable items (default behaviour) and then implement this method in the NSMenu-sublass.
- (BOOL)validateMenuItem:(NSMenuItem *)menuItem {
return [menuItem isEnabled];
}
You should uncheck Auto Enables Items on the closest parent NSMenu
You can solve this problem without subclassing.
If only you need is standard menu + some custom NSMenuItems that can be disabled on your control, than you can just:
Add one more menu node - just drag and drop Submenu Menu Item from Object library to your menu.
Add all NSMenuItems you want to manage to this new Menu node.
Open Attributes inspector for your New Menu node, and switch off Auto Enables Items option:
configure any other options of your menu & items.
Now you can write a code like:
#property (weak) IBOutlet NSMenuItem *hidePlateMenuItem;
...
[self.hidePlateMenuItem setEnabled:NO];
and it will works well.
Adding to the response of itsdavyh, if the menu item is located inside one or more submenus you only have to uncheck the 'Auto enables items'-property on the submenu of the menu item and not on any other parentmenus.
I tried all these solution but finally i found the real problem that also make more sense to me.
It also is the simplest way to handle disabled nsmenuitem that no needs to subclass or do code.
The nsmenuitem before to be child of menu itself is child of the main item for example "Save as..." is child of "File". Just select the parent item (in this example is File) and set off "auto enable menu items" in the menu ispector panel, and here you go!
Swift 3 answer:
I have a submenu item under the standard "View" menu called "Enable System Setup On Launch". I use the function below to enable or disable the menu item. Note: the view menu does need the "Auto Enable Items" in IB to be turned off.
func enableSystemSetupMenuItem(enabled:Bool) {
//set view menu item to enabled: value
//requires "Auto Enable Items" of "View" menu item to be turned off in IB
//because "View" menu is now turned off for "Auto Enable" we have to handle all
//of the "View" menu items ourselves
//just to avoid operating on menu separators I set all other menu items to TAG = -1
let main = NSApplication.shared().menu?.item(withTitle: "View")
let subMenuItems = main?.submenu?.items
for item in subMenuItems! {
if item.title == "Enable System Setup On Launch" {
item.isEnabled = enabled
} else if item.tag == -1 {
item.isEnabled = true
}
}
}
Try calling [myMenuItem setEnabled:NO] from a different place and making sure that it happens after the menu-containing nib is loaded. Maybe do it right in the subclassed NSMenu's awakeFromNib.
I am working in a Cocoa based Mac OS X project and facing one issue with internationalize MainMenu.xib.
In the menu items, all titles are need to be internationalized programmatically. All the menu items like “cut”, ”copy”, ”paste” can be internationalized using setTitle except the undo and redo menu item title. Adding to this, after typing anything in the text fields of the project forms, the undo menu item title dynamically changed to “Undo Typing”. The same happens for “Redo” also.
I can set the titles of other menu and menuitems' title using,
[[[[NSApp mainMenu] itemAtIndex:1] submenu]setTitle:#"Edit_Test"]
for MainMenu.xib "Edit" menu and similarly,
[[[[[NSApp mainMenu] itemAtIndex:1] submenu]itemAtIndex:4]setTitle:#"Copy_Test"]
for NSMenuItem "Copy" which is in under "Edit" menu.
But If I use the same piece of code,
[[[[[NSApp mainMenu] itemAtIndex:1] submenu]itemAtIndex:0]setTitle:#"Undo_Test"]
the menuItem title still remain as "Undo"
NSUndoManager provides the methods undoMenuItemTitle and redoMenuItemTitle, but NSUndoManager does not send the -setTitle: messages to the "Undo" and "Redo" menu items.
So how can I track that dynamic change in title and make that "Undo Typing" internationalized also?
Is it possible to manually get the First responder of the MainMenu.xib and from that get the undomanager object? So that i can unbind the undo action that is currently present in the first responder with the undo menu item and perform undo operation manually or is it possible to just change the title programmatically without doing all these.
Please let me know if any one had come across this problem and resolved the issue.
Make a subclass of NSUndoManager and override the undoMenuTitleForUndoActionName: method and the redoMenuTitleForUndoActionName: method. Create instances of this subclass for each document (or managed object context, or other thing) that needs an undo manager.
Using validateUserInterfaceItem it is possible to disable/enable a menu item but I need a similar behaviour to hide menu items, have you some hints?
- (BOOL)validateMenuItem:(NSMenuItem *)menuItem
Some details in the docs.
Update:
NSMenus do not hide NSMenuItems if validateMenuItem: returns NO. The according NSMenuItem gets disabled (Which tells the user that the functionality is there, but currently not applicable).
This behavior is also suggested in Apple's HIG. (See the second paragraph in the "Naming Menu Items" section)
Two other notes about NSMenuItem's enabled state:
setEnabled only works if autoenablesItems: of the hosting NSMenu is set to YES
The default implementation of validateMenuItem: seems to traverse the responder chain to check if the target/action of a NSMenuItem is available.
You could use -(BOOL) validateMenuItem: to remove a menu item when it was unneeded and insert it when required.
See the Menu documentation
I am making my own NSMenu programmatically and popping it up when a button is pressed.
I create NSMenuItems and add them with the NSMenu method insertItem:atIndex:.
For some reason whatever item is at position zero in the menu does not show up. Is this how the menu is supposed to work?
Yes, this is how the menu is supposed to work, if the NSPopUpButton pullsDown. The first item corresponds to the title of the button; just insert "" or whatever you want. It won't show up.
So you're building your menu in reverse order (by iteratively calling insertItem:anItem atIndex:0)? Why not just build it from the top down and successively call addItem:? I've done this lots and never had issues with items disappearing.