Cocoa Popup window in the Status bar – MonoMac Port

Vadim Shpakovski way back on July 2011 released code for a popup window in the status bar, and now in August 2012 I have ported it over to MonoMac with a few tweaks! If you don’t care about the guide below, you can access the source at Github.

The implementation is largely the same as used in Vadim’s implementation, with a few tweaks to make it simpler, more .NET-like and more extensible. There’s still more work that needs to be done to make it truly modular architecture-wise, but I’ve made the classes themselves easily extensible with clear extension points.

High Level Overview

The entry point is the StatusPanelController, this controller creates the icon in the status bar. It takes in a controller that inherits from PanelController so that you can create your own custom views if necessary. When the user clicks on the icon, the controller will call OpenPanel() and ClosePanel() in the PanelController.

The PanelController is the controller for the panel that opens – it’s responsible for all rendering.

BackgroundView is the background for the panel we display, you can think of it as the panel itself. It renders the arrow, border, and background. For this reason the panel you use must have a BackgroundView, additionally the panel itself should be effectively non-styled, with all standard window chrome removed.

That’s basically it!

BackgroundView

BackgroundView is a special view that represents the popup panel. At the top of the source file are various constants for controlling how the view should be rendered. ArrowX is necessary so that we know where the middle of the status bar icon is. The arrow will centre itself on this point.

To render the view we override DrawRect, and provide our own custom drawing using an NSBezierPath to do most of the work. Take note that the coords .NET use are reversed on the Y axis, .NET’s origin is in the top-left, while OS X’s origin is in the bottom left.

PanelController (and a little note on MonoMac Animation)

PanelController is the most substantial class in the entire project. This controller is responsible for the display of the panel itself, this means it needs to handle the animation of the panel, as well as updating the BackgroundView with the position of the status icon, should the window be resized. HandleWindowDidResize handles the arrow position, updating the BackgroundView whenever the panel is resized. Meanwhile we have two events, WillClose and DidResignKey that we use to know when to close the panel. We don’t want the panel just disappearing, we want a nice smooth animation!

OpenPanel() is responsible for displaying the panel in a smooth way. First of all it works out the position the panel needs to be, this is based on the icon position. Next we set up the panel for display by ensuring critical state, and adding a teeny bit of a debug Easter egg. If you press shift, or shift + option, you slow down the animation, and optionally output debug information relating to the upcoming display.

Finally we animate the panel into view. Note that we cast the Animator property of the window, this is because by default the Animator property is of type NSObject. The Animator is actually a proxy for the full object, so we can just cast it to the ‘real’ type, and set our new post-animation properties. We then finish our animations, and let OS X do the rest!

ClosePanel() is the opposite of OpenPanel() and just animates the panel away.

Panel

Panel is a very slightly customised implementation of NSPanel. We just add an extra property so that we can use controls in the panel that require the keyboard, such as text fields.

StatusItemView

This is the view that will be displayed in the status bar. It’s fairly self-explanatory, with different images depending on the state of the button. There’s a GlobalRect property so that we can locate the icon in the screen and position the panel.

For rendering we override DrawRect, and do it largely ourselves. We first draw the background into the view, optionally highlighted. Next we draw the requisite image (the icon) in the centre of the view. We use an updated method call over the one used by Vadim because the original has been deprecated – it’s not even available in MonoMac!

Finally we track the MouseDown and MouseUp events, rather than just MouseUp. A ‘click’ is considered when the user both mouses down and up, in the same view. If the user does this, we fire the StatusItemClicked event.

StatusPanelController

Finally we have the controller that actually brings it all together. The controller could do with a little more work to make it genuinely re-usable, since it uses hard-coded values for the images, as well as the view used for the status icon. Fortunately re-factoring these out into dependencies would be fairly trivial.

At construction we create all of our required controllers and views, this creates the icon in the status bar.

We hook into the clicked event from StatusItemView, and toggle the panel as neccesary.

Finally we implement IDisposable so that we never leave an icon in the status bar by accident – that would be very bad! We also have a finalizer so that no matter what, the icon will end up removed from the status bar when the controller is garbage collected.

6 comments

Hi Dan, I want to make an app that runs out of a Popup in the status bar, and your app is a natural fit, so I’ve grabbed the project and compiled, but I have two questions: when I run it I don’t see the star in the status bar, instead I see a script in my doc that says MacMenuBarPanel, also, why do we set _statusItem = null when it’s a read only attribute?

Hi Chris. Good questions! _statusItem = null is a mistake and you can safely remove the line. I used the code in another project and some back-porting of the code clearly went wrong. The image not appearing is a similar project movement issue. The images need to be in the root of the project, with a build action of ‘Content’, and a copy option of ‘Always Copy’. I’ve updated the github project accordingly.

sorry to ask such a simple question. How can I use this in xcode, I was looking for a .xcodeproj file or similar. Being new to this all, could you help with a few lines for newbies, please?

Hello Dan,
I am really happy I found your work here; I’ve trying to figure out how to use your code on github in a new xcode project. Once I download your files from github, where do I use them in xcode.
So first I create an OS X project, a Cocoa application. Normally I’ll then see AppDelegate.h & AppDelegate.m, but your files contain AppDelegate.cs etc…
I don’t know what to do with the .cs files, I guess they are mono related, So I’m wondering if I can still use your code in a standard Cocoa application.
So I’m a little confused about how to start using your files (as you can guess I’m new to xcode)
Can you advise me on where to start with a simple example, please?
thanks.

Hi guys. Unfortunately this is a MonoMac (now Xamarin.Mac) project which means you need Xamarin Studio to use the code: http://xamarin.com/

The free version should be enough to actually use the code, open the .sln file and you should be able to just run the solution as-is. The code is all C# instead of Obj-C. If you want to stick with Obj-C you can still use the original version from Vadim: http://blog.shpakovski.com/2011/07/cocoa-popup-window-in-status-bar.html

Hey Dan,

Even with MonoMac and Xamarin Studio, the example project doesn’t build.

/Users/powella/Downloads/MacMenuBarPanel-master/MacMenuBarPanel/MenuBarPanel/PanelController.cs(51,51): Error CS0165: Use of unassigned local variable `statusRect’ (CS0165) (MacMenuBarPanel)

/Users/powella/Downloads/MacMenuBarPanel-master/MacMenuBarPanel/MenuBarPanel/StatusItemView.cs(25,25): Error CS1061: Type `MonoMac.AppKit.NSStatusItem’ does not contain a definition for `DrawStatusBarBackgroundInRectwithHighlight’ and no extension method `DrawStatusBarBackgroundInRectwithHighlight’ of type `MonoMac.AppKit.NSStatusItem’ could be found. Are you missing an assembly reference? (CS1061) (MacMenuBarPanel)

Are you in an environment with Xamarin.Mac rather than MonoMac? (Xamarin.Mac is paid only now and the licenses are quite high for indie devs.)

Leave a Reply to Carlos Cancel reply