Toggling NSMenuItems for a NSSearchField Menu Template

The Problem

Creating NSMenus for NSSearchFields involves specifying a particular NSMenu as the NSSearchField's 'Menu Template'. The problem with this is that the actual NSMenu is not used, a copy is made. The result is that its impossible to access the NSMenu or its NSMenuItems through IBOutlets. Thus, modification of the NSMenuItem's state, title, etc. attributes are not done in the "Standard Way".

The Solution

This wasn't immediately obvious to me from the documentation or API references and caused a good bit of confusion, digging, and googling before I turned up an answer. I'm sure this is in part due to my newness with The Cocoa Way®. Regardless, it deserves a post so I'll remember it.

The magic bullet is to set your controller to the delegate for the NSSearchField and implement the validateMenuItem:. Each time the NSMenu is refreshed, the validateMenuItem is called for each NSMenuItem at which time you can modify its state or attributes.

The Implementation

Here we do a quick implementation to demonstrate the validateMenuItem in action. I'm going to use PyObjc for brevity, but it should suffice to demonstrate the approach.

Create a PyObjc Cocoa Application with a NSSearchField using a Menu Template.

I've named my project ModifySearchMenu. The object is to create an IBAction that is assigned to the NSMenuItems action. When the item is clicked, we set it as the current menu item. We check this value in validateMenuItem and assign the appropriate state. validateMenuItem returns either YES or NO if the NSMenuItem should be active or not.

Drag a NSSearchField widget to the default NSWindow.

Screen shot 2010-10-07 at 4.11.22 PM.png

Screen shot 2010-10-07 at 4.11.22 PM.png

Create a NSMenu instance

Screen shot 2010-10-07 at 4.13.26 PM.png

Screen shot 2010-10-07 at 4.13.26 PM.png

Set the AppDelegate as the delegate for the NSSearchField

Screen shot 2010-10-07 at 4.14.24 PM.png

Screen shot 2010-10-07 at 4.14.24 PM.png

Implement the validateMenuItem method in your AppDelegate and create an IBAction to register the clicked menu item.

from Foundation import *
from AppKit import *
from Cocoa import *
from objc import IBAction
from objc import YES
class ModifySearchMenuAppDelegate(NSObject):
    currentItem = u"Item 1"def applicationDidFinishLaunching_(self, sender):
        NSLog("Application did finish launching.")    @IBAction
    def setCurrentMenuItem_(self, sender):
        self.currentItem = sender.title()def validateMenuItem_(self, item):
        if item.title() == self.currentItem:
            item.setState_(NSOnState)
        else:
            item.setState_(NSOffState)        return YES

Connect it up in IB. Don't forget to set your Menu Template.

Screen shot 2010-10-07 at 4.28.00 PM.png Screen shot 2010-10-07 at 4.30.02 PM.png

Once this is done, clicking one menu item will toggle it to the on state and all others to the off state on the next display.

Screen shot 2010-10-07 at 4.30.51 PM.png Screen shot 2010-10-07 at 4.31.00 PM.png Screen shot 2010-10-07 at 4.31.09 PM.png

Share on: TwitterFacebookGoogle+Email

Comments !