« How to Make a Dock Menu

Posted by Brian Christensen on October 27, 2001 [Feedback (0) & TrackBack (0)]

// Introduction

2001-10-27 changes: Added an additional section about dynamic dock menu items.

The recently released Mac OS X v10.1 update brings us many improvements, including the abillity to add custom menu items to an application's dock icon. To see an example of such a dock menu in action, launch iTunes and click-hold on its icon. Aside from the standard window list and quit items, you'll also see "Play", "Next Song" and "Previous Song", as well as the title of the currently playing song. Dock items like "Show In Finder", "Quit" and the list of open windows are provided automagically by the dock, so all we need to do is add our custom items. Doing this is exceptionally easy and pretty much only involves a bit of drag & drop in Interface Builder and a bit of code to implement your menu action.

The first section explains how to create a "static" dock menu (meaning the items don't change while your app is running) and the second section shows you how to make a "dynamic" dock menu (the same thing iTunes does to display the currently playing song in its menu).

iTunes Dock Menu
Figure 1: iTunes Dock Menu


// Creating a Static Menu

I'm going to assume you already have an application you'd like to add a dock menu to, but if you don't, create a new project. Open your "MainMenu.nib" file in Interface Builder. Add a new NSMenu from the Cocoa-Menus palette (the item that looks like a miniature menu) and rename it to "DockMenu" or whatever you'd like to call it. You'll need to add the DockMenu to the "File's Owner" instance's dockMenu outlet.

Once you've done that, double-click the newly created DockMenu to edit it and add an "About this app" item for our testing purposes. You can remove the standard "Item1" and "Item2" items from the menu if you'd like. Now, connect the newly created "About this app" item to the file's owner instance and select orderFrontStandardAboutPanel: as its target method.

If you'd like to test it now, save the changes you just made, switch to Project Builder, build your app and run it. Click-hold on your application's dock icon and you should be able to directly call up the about panel from there.


// Dynamic Menu Items

The latest version of iTunes, which debuted in Mac OS X 10.1, includes a handy feature in its dock menu: it shows us the currently playing song and its associated artist. As you know, this can't be done by simply adding some menu items in Interface Builder. If we did that, it would obviously remain static. But how do we accomplish this task? Instead of wiring up an NSMenu in the nib file, we need to implement the applicationDockMenu: method in our app delegate. In other words, we don't need to do anything in the nib file. Everything is done programmatically. This allows us to return a customly constructed NSMenu, which we can modify at will while our application is running. Just as with the nib approach above, items like "Quit" and "Show In Finder" are automatically inserted into the menu, so we don't need to worry about those. To get started, let's put together an interface for our test window, which we will use to tell our dynamic menu what to display.

Window interface
Figure 2: Let's put together an interface.

I wired up the text field to an outlet of my app controller called dynamicText and connected the button to an action called updateDockMenu:. Don't forget to put these additions in your app controller .h file, which should look like this:

#import <Cocoa/Cocoa.h>

@interface AppController : NSObject
    IBOutlet id dynamicText;
    NSMenu *dynamicMenu;
    NSMenuItem *dynamicItem;

- (IBAction)updateDockMenu:(id)sender;


And now for the actual code:

- (IBAction)updateDockMenu:(id)sender
    // Only update if the text field contains something
    if ([[dynamicText stringValue] length] > 0)
        // If dynamicMenu is nil, instantiate it
        if (dynamicMenu == nil)
            dynamicMenu = [[NSMenu alloc] init];

Below I use the addItemWithTitle:action:keyEquivalent: method. This returns the newly created NSMenuItem so that we can use it later to easily change the item title. Additionally, I specify nil as the action for our menu item. This gives us an iTunes-like effect: the item is greyed out and can be used for displaying information. If you want to have the item do something, supply a method to be messaged (ie. action:@selector(performMyMenuAction:)). Don't forget to set the menu item target as well using [dynamicItem setTarget:self] (this is assuming your action method is part of your app controller).

            dynamicItem = [dynamicMenu addItemWithTitle:[dynamicText stringValue] 
                                       action:nil keyEquivalent:@""];

Since our menu item already exists, we only need to change its title using NSMenuItem's setTitle: method.

            [dynamicItem setTitle:[dynamicText stringValue]];

And finally, if our text field is empty, we don't need the dynamic menu anymore, so we get rid of it.

        // Text field is empty, so if dynamicMenu isn't nil, kill it
        if (dynamicMenu != nil)
            [dynamicMenu release];
            dynamicMenu = nil;

To let the dock know what menu items to display, we need to implement the applicationDockMenu: method in our application delegate. Add the following code:

- (NSMenu *)applicationDockMenu:(NSApplication *)sender
    return dynamicMenu;

That's it! There are still some things you might need to know, so keep reading.


// Foreground and Background Actions

You might have noticed that our about command only opens the about panel in the background, without actually bringing our application to the foreground. For things like "Play" and "Stop" (in iTunes' case), that's the desired behavior. But if we're going to display some type of window, we want to be placed in the foreground.

iChem Dock Menu
Figure 2: Some applications need to be brought to the foreground.

Bringing an application to the foreground (which is sometimes referred to as activating an app) is fairly simple to do, but it involves a bit of code. Instead of connecting the about menu item directly to orderFrontStandardAboutPanel:, add a new method to your app controller (create one if you don't have one yet) called showAboutPanel: and connect our item to that. Add the following code to your controller's .m file:

- (void)showAboutPanel:(id)sender
    [[NSApplication sharedApplication] activateIgnoringOtherApps:YES];
    [[NSApplication sharedApplication] orderFrontStandardAboutPanel:sender];

The "ignoringOtherApps" part of the activateIgnoringOtherApps: method means that your application will be activated immediately, regardless of what other applications may be doing at the time. If you specify NO, your app most likely won't be brought to the front.


// Conclusion

That was quite easy, wasn't it? Since it's so trivial to do this, I hope to see future applications include this capability! As always, if you have any comments or questions, feel free to contact me.

Post a comment