Cannot seem to setEnabled:NO on NSMenuItem - cocoa

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.

Related

How to create an NSMenu containing an NSMenuItem which only appears while holding a keyboard modifier key?

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.

how to make a macOS app have two main menu

I know some mac application has two main menu.
main menu means top left menu in macOS screen.
When a windowA show, the main menuA show.
As I click some window (name it windowB) in the application
and another menuB is replaced menuA.
And as I click windowA, the main menuA reappear.
Does someone know how to implement this behavior?
You have to associate a menu to each window. For this, you can copy the MainMenu of the Application and paste it into the appropriate Window Controller Scene. Then select the connections of the window in the scene and link the item "menu" to the new menu of the scene. Repeat this for all your window controllers/windows.
Then you need to add some code. Create a new Window class:
class Window : NSWindow {
override func becomeKey() {
NSLog("become2")
NSApplication.shared().mainMenu = self.menu
super.becomeKey()
}
}
and then set the class property of each window to this Window class.
You can then edit menus accordingly to your needs. Be aware that you need to reconnect each menu item to the appropriate first responder action...
I'm not sure it is the best solution, but it works well for me. In fact I don't really understand why I need to subclass NSWindow this way...

Cocoa validateUserInterfaceItem and menu item hiding

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

Binding an NSMenuItem's title breaks enabled/disabled validation

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.

Alternative Menu Items in NSMenu

I have an NSMenu that contains NSMenuItems with custom views. I want it so that when the alt button is pressed, the menu items would change part of their look (through their view). I found setAlternative in the NSMenuItem docs, however, in practice I could only get it to work with NSMenuItems without custom views. As soon as I set a custom view, all of the menu items would be displayed.
Also, I tried getting keypress events while the menu was open. Due to the other run loop, NSApplication's sendEvent: doesn't receive events until after the menu is closed. Therefore, I can't just intercept the event coming in.
Does anyone know how I can get notified, whether through delegation or subclassing, of when the alt key is pressed when a menu is opened?
You should set an object as the delegate of your menu and then implement the delegate method -menu:updateItem:atIndex:shouldCancel:.
This will allow you to change the state of your custom view before the menu item is displayed, based on the current modifier state.
You can get the current modifiers by asking for [[NSApp currentEvent] modifierFlags].
If you need to be notified if the modifier flags change while your menu is open, implement the -flagsChanged: method in your custom view:
- (void)flagsChanged:(NSEvent*)event
{
if ([event modifierFlags] & NSAlternateKeyMask) // check for option key
{
//do something
}
else
{
//do something else
}
}

Resources