Gap above NSMenuItem custom view
Asked Answered
A

1

19

I am using the setView: method on an NSMenuItem to set a custom view. In this custom view there is an image which takes the whole of the view. The NSMenuItem with this custom view is the first in the menu but the problem is it doesn't sit flush with the top of the menu, there is a big gap as you can see here:

alt text

Why is this happening and how can I stop it?


EDIT

I am using this code now but I am getting EXC_BAD_ACCESS on the line InstallControlEventHandler.

-(void)applicationDidFinishLaunching:(NSNotification *)aNotification {
    HIViewRef contentView;
    MenuRef menuRef = [statusMenu carbonMenuRef];

    HIMenuGetContentView(menuRef, kThemeMenuTypePullDown, &contentView);

    EventTypeSpec hsEventSpec[1] = {
        { kEventClassMenu, kEventMenuCreateFrameView }
    };

    InstallControlEventHandler(contentView,
                           NewEventHandlerUPP((EventHandlerProcPtr)hsMenuCreationEventHandler),
                           GetEventTypeCount(hsEventSpec),
                           hsEventSpec,
                           NULL,
                           NULL); // Get EXC_BAD_ACCESS here.
}

static OSStatus hsMenuContentEventHandler( EventHandlerCallRef caller, EventRef event, void* refcon )
{
    OSStatus  err;

    check( GetEventClass( event ) == kEventClassControl );
    check( GetEventKind( event ) == kEventControlGetFrameMetrics );

    err = CallNextEventHandler( caller, event );
    if ( err == noErr )
    {
        HIViewFrameMetrics  metrics;

        verify_noerr( GetEventParameter( event, kEventParamControlFrameMetrics, typeControlFrameMetrics, NULL,
                                        sizeof( metrics ), NULL, &metrics ) );

        metrics.top = 0;

        verify_noerr( SetEventParameter( event, kEventParamControlFrameMetrics, typeControlFrameMetrics,
                                        sizeof( metrics ), &metrics ) );
    }

    return err;
}

static OSStatus hsMenuCreationEventHandler( EventHandlerCallRef caller, EventRef event, void* refcon )
{
    OSStatus  err = eventNotHandledErr;

    if ( GetEventKind( event ) == kEventMenuCreateFrameView)
    {
        err = CallNextEventHandler( caller, event );
        if ( err == noErr )
        {
            static const EventTypeSpec  kContentEvents[] =
            {
                { kEventClassControl, kEventControlGetFrameMetrics }
            };

            HIViewRef          frame;
            HIViewRef          content;

            verify_noerr( GetEventParameter( event, kEventParamMenuFrameView, typeControlRef, NULL,
                                            sizeof( frame ), NULL, &frame ) );
            verify_noerr( HIViewFindByID( frame, kHIViewWindowContentID, &content ) );
            InstallControlEventHandler( content, hsMenuContentEventHandler, GetEventTypeCount( kContentEvents ),
                                       kContentEvents, 0, NULL );
        }
    }

    return err;
}

Also note the line metrics.top = 0 this is the line which should remove the gap at the top. However I cannot get it work that far. Does anyone know why I would be recieving an EXC_BAD_ACCESS there. I have already created and allocated statusMenu so surely it should work?

Acetometer answered 26/12, 2010 at 12:34 Comment(10)
It looks like there is a white spacer at the top and bottom of every menu. I'd also like to know if it's possible to avoid it.Bronwen
I assume the black portion is the image, not the gap? There is some padding between the top and the bottom of the menu, in addition to between separator items, for aesthetic reasons. I'm not sure if this is done is NSMenu or NSMenuItem, but you may need to subclass either one or the other to prevent it.Icefall
I've done some research and turned up this mail-archive.com/[email protected]/msg26997.html It looks like a custom NSMenu would be needed & some private API tinkering.Bronwen
That's interesting but how would you set the top metrics to zero and I wonder what the code would be like as I as I assume it would be Carbon.Acetometer
Hmm, just found this but I can't get it to work. #4064886 I get EXC_BAD_ACCESS on the line with InstallControlEventHandler.Acetometer
Why not draw an NSWindow instead of the NSMenu? You can fit much nicer things in it thenFestoon
What does HIMenuGetContentView() return? My guess is it is returning something else than noErr and you are then trying to install an event handler on a garbage pointer.Fda
Joshua : I make the above code working by replacing "InstallControlEventHandler" function call to "HIViewInstallEventHandler". let me know if that help.Pend
FYI: I have used the above code in 10.6.Pend
@Pend Thanks very much that worked perfectly, great to get a solution along the lines of my original attempt! Thanks again!Acetometer
A
16

Your post is tagged "Objective-C" and "Cocoa", although your sample code is C and Carbon. I assume you'd prefer a Cocoa solution?

It's actually pretty simple in Cocoa. The only trick is learning how to draw outside the lines. :-)

@interface FullMenuItemView : NSView
@end

@implementation FullMenuItemView
- (void) drawRect:(NSRect)dirtyRect
{
    NSRect fullBounds = [self bounds];
    fullBounds.size.height += 4;
    [[NSBezierPath bezierPathWithRect:fullBounds] setClip];

    // Then do your drawing, for example...
    [[NSColor blueColor] set];
    NSRectFill( fullBounds );
}
@end

Use it like this:

CGFloat menuItemHeight = 32;

NSRect viewRect = NSMakeRect(0, 0, /* width autoresizes */ 1, menuItemHeight);
NSView *menuItemView = [[[FullMenuItemView alloc] initWithFrame:viewRect] autorelease];
menuItemView.autoresizingMask = NSViewWidthSizable;

yourMenuItem.view = menuItemView;
Adagio answered 6/3, 2011 at 23:12 Comment(7)
Works superbly! Just found a typo in your code though you said initWithRect: when it should be initWithFrame:. Thanks very much, I never though it would be as easy as this!Acetometer
joshua: if still need the above code to work without EXC_BAD_ACCESS, please check post #6634343Pend
@Pend this solution avoids the issue completely by not using Carbon. Why would you use Carbon if you don't need to?Adagio
skue: I have used the above carbon code, and it can be useful. There is one problem with the above code is that we need to fill the rect using setClip, which is not recommended in apple documents. Second, we can't modify the original NSMenu frame metrics, like we can do in this carbon code. Let me know, if we have any easy way to modify NSMenu frame metrics in cocoa. ThanksPend
Don't care what AmitSri does, but to clarify for others... setClip is public and supported. Apple's only warning is that if you use it, views might draw outside their bounds, which is exactly what we're trying to do. In contrast, the Carbon code AmitSri cites uses deprecated (HIMenuGetContentView) and private (_NSGetCarbonMenu) APIs.Adagio
You shouldn't use the blue color—some people (like me) use the Graphite color scheme. (: It's better to use [NSColor selectedMenuItemColor], which should pick the right color for you.Fifield
Hey, this solution isn't working for me :( I have a custom NSView which i am using as NSMenuItem's view. In my custom NSView inside drawRect I have implemented like the way you suggested. But still the issue isn't fixed :( someone please help!!Burette

© 2022 - 2024 — McMap. All rights reserved.