How do I route menu actions to an NSViewController inside a window?
Asked Answered
I

2

9

I have a menu item inside the main app menu and I’d like to route its action to a view controller (NSViewController). The interface hierarchy looks like this: There’s a main app window controller by an NSWindowController. Inside the window there’s a split view, and the right view in the split view is controlled by the NSViewController.

Window + NSWindowController
    `-- NSSplitView
           `-- NSView
           `-- NSView + NSViewController

The menu item is connected to First Responder in the Interface Builder. The view controller in question implements the appropriate method, but the menu item stays disabled. When I move the method to the NSWindowController, the menu item gets enabled.

I figured I need to get the view controller to the responder chain, so I set it as the nextResponder for the window controller; no cigar. What am I doing wrong?

Imidazole answered 12/7, 2012 at 13:33 Comment(4)
You can add an object in IB, representing your Controller. Then link the menu action to the IBAction of your controller.Contrary
Unfortunately that’s not possible, the view controllers change according to what’s selected in the left split view pane.Imidazole
Then going to have to reasign the menu action each time the view get's focus. To access te menu : [[[[[NSApp mainMenu] itemWithTitle:@"ItemName"] menu] itemWithTitle@"ItemName] setAction:@"Selector(theSelector)]Contrary
Yes, that’s a possible workaround (thank you), but I’d like to get it working the official way through the responder chain, because reassigning the target for my menu items by hand sucks.Imidazole
I
3

In the end I added a base class for my window controllers and made it forward calls to the “child” controllers:

- (id) childControllerForSelector: (SEL) selector
{
    for (id controller in [childControllers copy])
        if ([controller respondsToSelector:selector])
            return controller;
    return nil;
}

- (BOOL) respondsToSelector: (SEL) selector
{
    return [super respondsToSelector:selector] ? YES :
        [self childControllerForSelector:selector] ? YES :
            NO;
}

- (void) forwardInvocation: (NSInvocation*) invocation
{
    id child = [self childControllerForSelector:[invocation selector]];
    [invocation invokeWithTarget:child];
}

- (NSMethodSignature*) methodSignatureForSelector: (SEL) selector
{
    NSMethodSignature *signature = [super methodSignatureForSelector:selector];
    if (!signature) {
        id child = [self childControllerForSelector:selector];
        signature = [child methodSignatureForSelector:selector];
    }
    return signature;
}

It’s a lot of code, but it’s a general solution that keeps the controller code free from ad-hoc forwarding. Hopefully it’s not too much magic.

Imidazole answered 13/7, 2012 at 9:49 Comment(0)
B
2

You can set the window controller as the delegate of the window so it will now be a part of the responder chain.

Assuming that you have your own subclass of NSWindowController, you can then simply catch the menu event there and call your appropriate methods in your controllers.

Unfortunately, the docs advice against trying to insert anything in the responder chain between the various views and subviews, so you can't just squeeze your view controller in there.

More here, but I take it you have already consulted that.

Barbershop answered 12/7, 2012 at 17:8 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.